@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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React19 from 'react';
2
- import React19__default, { createContext, useRef, useCallback, useState, useMemo, useEffect, memo, useContext, useLayoutEffect, useId, Children, isValidElement, useInsertionEffect, forwardRef, Fragment as Fragment$1, createElement, Component } from 'react';
2
+ import React19__default, { createContext, useRef, useCallback, useState, useMemo, useEffect, memo, forwardRef, useImperativeHandle, useContext, useLayoutEffect, useId, Children, isValidElement, useInsertionEffect, Fragment as Fragment$1, createElement, Component } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useRouter } from 'next/router';
5
5
  import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
@@ -13,15 +13,16 @@ import { noop, warning, invariant, progress, secondsToMilliseconds, milliseconds
13
13
  import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
14
14
  import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ReferenceLine, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
15
15
  import { Slot } from '@radix-ui/react-slot';
16
- import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, Loader2, AlertCircle, Edit2, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight, Activity, Sun, Moon, MessageSquare, Trash2, RefreshCw, Menu, Send, Copy, UserCheck, LogOut, Package, TrendingUp, TrendingDown, Settings, LifeBuoy, EyeOff, Eye, Zap, UserCircle } from 'lucide-react';
16
+ import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, Loader2, AlertCircle, Edit2, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight, Activity, Sun, Moon, MousePointer, ArrowRight, MessageSquare, Trash2, RefreshCw, Menu, Send, Copy, UserCheck, LogOut, Package, TrendingUp, TrendingDown, Building2, FolderOpen, Folder, Settings, LifeBuoy, EyeOff, Eye, Zap, UserCircle } from 'lucide-react';
17
17
  import { DayPicker, useNavigation as useNavigation$1 } from 'react-day-picker';
18
- import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
18
+ import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, UsersIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
19
19
  import { CheckIcon } from '@heroicons/react/24/solid';
20
20
  import html2canvas from 'html2canvas';
21
21
  import jsPDF, { jsPDF as jsPDF$1 } from 'jspdf';
22
22
  import * as SelectPrimitive from '@radix-ui/react-select';
23
23
  import videojs from 'video.js';
24
24
  import 'video.js/dist/video-js.css';
25
+ import { createPortal } from 'react-dom';
25
26
  import { toast } from 'sonner';
26
27
  import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
27
28
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
@@ -4807,8 +4808,347 @@ var S3ClipsService = class {
4807
4808
  return this.apiClient.getStats();
4808
4809
  }
4809
4810
  };
4810
-
4811
- // src/lib/services/videoPrefetchManager.ts
4811
+ var getSupabaseClient2 = () => {
4812
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
4813
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
4814
+ if (!url || !key) {
4815
+ throw new Error("Supabase configuration missing");
4816
+ }
4817
+ return createClient(url, key);
4818
+ };
4819
+ var getAuthToken2 = async () => {
4820
+ try {
4821
+ const supabase = getSupabaseClient2();
4822
+ const { data: { session } } = await supabase.auth.getSession();
4823
+ console.log("[S3ClipsSupabase] Auth session exists:", !!session, "has token:", !!session?.access_token);
4824
+ return session?.access_token || null;
4825
+ } catch (error) {
4826
+ console.error("[S3ClipsSupabase] Error getting auth token:", error);
4827
+ return null;
4828
+ }
4829
+ };
4830
+ var S3ClipsSupabaseService = class {
4831
+ constructor(config) {
4832
+ this.requestCache = /* @__PURE__ */ new Map();
4833
+ // Flags for compatibility
4834
+ this.isIndexBuilding = false;
4835
+ this.isPrefetching = false;
4836
+ this.currentMetadataFetches = 0;
4837
+ this.MAX_CONCURRENT_METADATA = 3;
4838
+ this.config = config;
4839
+ if (!config.s3Config) {
4840
+ throw new Error("S3 configuration is required");
4841
+ }
4842
+ const processing = config.s3Config.processing || {};
4843
+ this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
4844
+ this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
4845
+ this.concurrencyLimit = processing.concurrencyLimit || 10;
4846
+ this.maxInitialFetch = processing.maxInitialFetch || 60;
4847
+ console.log("[S3ClipsSupabase] \u2705 Initialized with Supabase backend - Direct database queries!");
4848
+ }
4849
+ /**
4850
+ * Fetch with authentication and error handling
4851
+ */
4852
+ async fetchWithAuth(endpoint, body) {
4853
+ const token = await getAuthToken2();
4854
+ if (!token) {
4855
+ throw new Error("Authentication required");
4856
+ }
4857
+ const apiEndpoint = "/api/clips/supabase";
4858
+ const requestBody = {
4859
+ ...body,
4860
+ action: endpoint.replace("/api/clips/supabase/", "")
4861
+ };
4862
+ console.log(`[S3ClipsSupabase] Making request to ${apiEndpoint} with action: ${requestBody.action}, body:`, requestBody);
4863
+ const response = await fetch(apiEndpoint, {
4864
+ method: "POST",
4865
+ headers: {
4866
+ "Authorization": `Bearer ${token}`,
4867
+ "Content-Type": "application/json"
4868
+ },
4869
+ body: JSON.stringify(requestBody)
4870
+ });
4871
+ console.log(`[S3ClipsSupabase] Response status: ${response.status}`);
4872
+ if (!response.ok) {
4873
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
4874
+ console.error(`[S3ClipsSupabase] API error:`, error);
4875
+ throw new Error(error.error || `API error: ${response.status}`);
4876
+ }
4877
+ const data = await response.json();
4878
+ if (requestBody.action === "by-index" || requestBody.action === "batch") {
4879
+ console.log(`[S3ClipsSupabase] API Response for ${requestBody.action}:`, {
4880
+ action: requestBody.action,
4881
+ hasData: !!data,
4882
+ dataKeys: Object.keys(data || {}),
4883
+ video: requestBody.action === "by-index" ? data?.video : void 0,
4884
+ videosCount: requestBody.action === "batch" ? data?.videos?.length : void 0
4885
+ });
4886
+ } else if (requestBody.action === "clip-types") {
4887
+ console.log(`[S3ClipsSupabase] API Response for clip-types:`, {
4888
+ action: requestBody.action,
4889
+ hasData: !!data,
4890
+ dataKeys: Object.keys(data || {}),
4891
+ clipTypesCount: data?.clipTypes?.length,
4892
+ clipTypes: data?.clipTypes
4893
+ });
4894
+ } else if (requestBody.action === "count") {
4895
+ console.log(`[S3ClipsSupabase] API Response for count:`, {
4896
+ action: requestBody.action,
4897
+ hasData: !!data,
4898
+ counts: data?.counts
4899
+ });
4900
+ }
4901
+ return data;
4902
+ }
4903
+ /**
4904
+ * Deduplicate requests to prevent multiple API calls
4905
+ */
4906
+ async deduplicate(key, factory) {
4907
+ if (this.requestCache.has(key)) {
4908
+ console.log(`[S3ClipsSupabase] Deduplicating request: ${key}`);
4909
+ return this.requestCache.get(key);
4910
+ }
4911
+ const promise = factory().finally(() => {
4912
+ this.requestCache.delete(key);
4913
+ });
4914
+ this.requestCache.set(key, promise);
4915
+ return promise;
4916
+ }
4917
+ /**
4918
+ * Lists clips using Supabase API
4919
+ */
4920
+ async listS3Clips(params) {
4921
+ const { workspaceId, date, shiftId } = params;
4922
+ if (!isValidShiftId(shiftId)) {
4923
+ console.error(`[S3ClipsSupabase] Invalid shift ID: ${shiftId}`);
4924
+ return [];
4925
+ }
4926
+ console.log(`[S3ClipsSupabase] Listing clips via Supabase for workspace: ${workspaceId}`);
4927
+ try {
4928
+ const response = await this.fetchWithAuth("list", {
4929
+ workspaceId,
4930
+ date,
4931
+ shift: shiftId,
4932
+ sopCategories: this.config.s3Config?.sopCategories?.default
4933
+ });
4934
+ return response.clips.map((clip) => clip.originalUri || `clips:${clip.id}`);
4935
+ } catch (error) {
4936
+ console.error("[S3ClipsSupabase] Error listing clips:", error);
4937
+ return [];
4938
+ }
4939
+ }
4940
+ /**
4941
+ * Get metadata cycle time
4942
+ */
4943
+ async getMetadataCycleTime(clipId) {
4944
+ const id3 = clipId.startsWith("clips:") ? clipId.substring(6) : clipId;
4945
+ try {
4946
+ console.log(`[S3ClipsSupabase] Metadata cycle time requested for clip: ${id3}`);
4947
+ return null;
4948
+ } catch (error) {
4949
+ console.error("[S3ClipsSupabase] Error fetching metadata cycle time:", error);
4950
+ return null;
4951
+ }
4952
+ }
4953
+ /**
4954
+ * Control prefetch mode
4955
+ */
4956
+ setPrefetchMode(enabled) {
4957
+ this.isPrefetching = enabled;
4958
+ console.log(`[S3ClipsSupabase] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
4959
+ }
4960
+ /**
4961
+ * Get full metadata
4962
+ */
4963
+ async getFullMetadata(clipId) {
4964
+ if (this.isIndexBuilding || this.isPrefetching) {
4965
+ console.warn("[S3ClipsSupabase] Skipping metadata - operation in progress");
4966
+ return null;
4967
+ }
4968
+ return null;
4969
+ }
4970
+ /**
4971
+ * Get clip counts with optional video index
4972
+ */
4973
+ async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex = false) {
4974
+ const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
4975
+ return this.deduplicate(cacheKey, async () => {
4976
+ console.log(`[S3ClipsSupabase] Fetching clip counts from Supabase for:`, {
4977
+ workspaceId,
4978
+ date,
4979
+ shift: shiftId
4980
+ });
4981
+ const response = await this.fetchWithAuth("count", {
4982
+ workspaceId,
4983
+ date,
4984
+ shift: shiftId.toString()
4985
+ });
4986
+ console.log(`[S3ClipsSupabase] Count API response:`, response);
4987
+ const counts = response.counts || {};
4988
+ console.log(`[S3ClipsSupabase] Extracted counts:`, counts);
4989
+ if (buildIndex) {
4990
+ const videoIndex = {
4991
+ byCategory: /* @__PURE__ */ new Map(),
4992
+ allVideos: [],
4993
+ counts,
4994
+ workspaceId,
4995
+ date,
4996
+ shiftId: shiftId.toString(),
4997
+ lastUpdated: /* @__PURE__ */ new Date(),
4998
+ _debugId: `supabase_${Date.now()}_${Math.random().toString(36).substring(7)}`
4999
+ };
5000
+ if (buildIndex) {
5001
+ const categories = Object.keys(counts).filter((k) => k !== "total");
5002
+ for (const category of categories) {
5003
+ if (counts[category] > 0) {
5004
+ const categoryResponse = await this.fetchWithAuth("list", {
5005
+ workspaceId,
5006
+ date,
5007
+ shift: shiftId.toString(),
5008
+ category,
5009
+ sopCategories: this.config.s3Config?.sopCategories?.default
5010
+ });
5011
+ const entries = categoryResponse.clips.map((clip) => ({
5012
+ uri: clip.originalUri || `clips:${clip.id}`,
5013
+ category,
5014
+ timestamp: clip.timestamp,
5015
+ videoId: clip.id,
5016
+ workspaceId,
5017
+ date,
5018
+ shiftId: shiftId.toString()
5019
+ }));
5020
+ videoIndex.byCategory.set(category, entries);
5021
+ videoIndex.allVideos.push(...entries);
5022
+ }
5023
+ }
5024
+ }
5025
+ return {
5026
+ counts,
5027
+ videoIndex
5028
+ };
5029
+ }
5030
+ return counts;
5031
+ });
5032
+ }
5033
+ /**
5034
+ * Get clip counts (simplified version)
5035
+ */
5036
+ async getClipCounts(workspaceId, date, shiftId) {
5037
+ const result = await this.getClipCountsCacheFirst(workspaceId, date, shiftId, false);
5038
+ if (typeof result === "object" && "counts" in result) {
5039
+ return result.counts;
5040
+ }
5041
+ return result;
5042
+ }
5043
+ /**
5044
+ * Get clip by index
5045
+ */
5046
+ async getClipByIndex(workspaceId, date, shiftId, category, index) {
5047
+ const cacheKey = `clip:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
5048
+ return this.deduplicate(cacheKey, async () => {
5049
+ console.log(`[S3ClipsSupabase] Fetching clip by index from Supabase for category: ${category}, index: ${index}`);
5050
+ const response = await this.fetchWithAuth("by-index", {
5051
+ workspaceId,
5052
+ date,
5053
+ shift: shiftId.toString(),
5054
+ category,
5055
+ index,
5056
+ sopCategories: this.config.s3Config?.sopCategories?.default
5057
+ });
5058
+ const video = response.video;
5059
+ console.log("[S3ClipsSupabase] getClipByIndex response:", {
5060
+ hasVideo: !!video,
5061
+ hasSrc: !!video?.src,
5062
+ srcType: typeof video?.src,
5063
+ srcPreview: video?.src ? video.src.substring(0, 50) : "no src",
5064
+ isProxyUrl: video?.src?.includes("/api/clips/stream/")
5065
+ });
5066
+ return video || null;
5067
+ });
5068
+ }
5069
+ /**
5070
+ * Get first clip for all categories
5071
+ */
5072
+ async getFirstClipsForAllCategories(workspaceId, date, shiftId) {
5073
+ console.log(`[S3ClipsSupabase] Getting first clips for all categories`);
5074
+ const counts = await this.getClipCounts(workspaceId, date, shiftId);
5075
+ const categories = Object.keys(counts).filter((k) => k !== "total" && counts[k] > 0);
5076
+ const promises = categories.map(async (category) => {
5077
+ const clip = await this.getClipByIndex(workspaceId, date, shiftId, category, 0);
5078
+ return { category, clip };
5079
+ });
5080
+ const results = await Promise.all(promises);
5081
+ const firstClips = {};
5082
+ for (const { category, clip } of results) {
5083
+ firstClips[category] = clip;
5084
+ }
5085
+ return firstClips;
5086
+ }
5087
+ /**
5088
+ * Get first clip for a specific category
5089
+ */
5090
+ async getFirstClipForCategory(workspaceId, date, shiftId, category) {
5091
+ return this.getClipByIndex(workspaceId, date, shiftId, category, 0);
5092
+ }
5093
+ /**
5094
+ * Batch fetch videos (alias for batchFetchClips for compatibility)
5095
+ */
5096
+ async batchFetchVideos(workspaceId, date, shiftId, requests) {
5097
+ return this.batchFetchClips(workspaceId, date, shiftId, requests);
5098
+ }
5099
+ /**
5100
+ * Batch fetch clips
5101
+ */
5102
+ async batchFetchClips(workspaceId, date, shiftId, requests) {
5103
+ const cacheKey = `batch:${workspaceId}:${date}:${shiftId}:${JSON.stringify(requests)}`;
5104
+ return this.deduplicate(cacheKey, async () => {
5105
+ console.log(`[S3ClipsSupabase] Batch fetching ${requests.length} clips from Supabase`);
5106
+ const response = await this.fetchWithAuth("batch", {
5107
+ workspaceId,
5108
+ date,
5109
+ shift: shiftId.toString(),
5110
+ requests,
5111
+ sopCategories: this.config.s3Config?.sopCategories?.default
5112
+ });
5113
+ console.log("[S3ClipsSupabase] batchFetchClips response:", {
5114
+ videoCount: response.videos?.length,
5115
+ firstVideo: response.videos?.[0],
5116
+ hasProxyUrls: response.videos?.[0]?.video?.src?.includes("/api/clips/stream/")
5117
+ });
5118
+ return response.videos.map((v) => v.video);
5119
+ });
5120
+ }
5121
+ /**
5122
+ * Get all clip types from Supabase
5123
+ */
5124
+ async getClipTypes() {
5125
+ const cacheKey = "clip-types:all";
5126
+ return this.deduplicate(cacheKey, async () => {
5127
+ console.log(`[S3ClipsSupabase] Fetching clip types from Supabase`);
5128
+ const response = await this.fetchWithAuth("clip-types", {});
5129
+ console.log(`[S3ClipsSupabase] Fetched ${response.clipTypes?.length || 0} clip types:`, response.clipTypes);
5130
+ return response.clipTypes || [];
5131
+ });
5132
+ }
5133
+ /**
5134
+ * Ensure videos are loaded for navigation
5135
+ */
5136
+ async ensureVideosLoaded(workspaceId, date, shiftId, category, currentIndex) {
5137
+ const rangeBefore = 1;
5138
+ const rangeAfter = 3;
5139
+ const requests = [];
5140
+ for (let i = Math.max(0, currentIndex - rangeBefore); i < currentIndex; i++) {
5141
+ requests.push({ category, index: i });
5142
+ }
5143
+ for (let i = currentIndex; i <= currentIndex + rangeAfter; i++) {
5144
+ requests.push({ category, index: i });
5145
+ }
5146
+ if (requests.length > 0) {
5147
+ await this.batchFetchClips(workspaceId, date, shiftId, requests);
5148
+ }
5149
+ }
5150
+ };
5151
+ var S3ClipsService2 = S3ClipsSupabaseService ;
4812
5152
  var VideoPrefetchManager = class extends EventEmitter {
4813
5153
  constructor() {
4814
5154
  super();
@@ -4837,7 +5177,7 @@ var VideoPrefetchManager = class extends EventEmitter {
4837
5177
  getS3Service(dashboardConfig) {
4838
5178
  const configKey = JSON.stringify(dashboardConfig.s3Config);
4839
5179
  if (!this.s3Services.has(configKey)) {
4840
- this.s3Services.set(configKey, new S3ClipsService(dashboardConfig));
5180
+ this.s3Services.set(configKey, new S3ClipsService2(dashboardConfig));
4841
5181
  }
4842
5182
  return this.s3Services.get(configKey);
4843
5183
  }
@@ -5221,25 +5561,387 @@ if (typeof window !== "undefined") {
5221
5561
  videoPrefetchManager.destroy();
5222
5562
  });
5223
5563
  }
5564
+
5565
+ // src/lib/services/linesService.ts
5566
+ var LinesService = class {
5567
+ constructor(supabase) {
5568
+ this.supabase = supabase;
5569
+ }
5570
+ /**
5571
+ * Fetch all active lines for a given company
5572
+ * @param companyId - The company ID to fetch lines for
5573
+ * @returns Promise<Line[]> - Array of lines for the company
5574
+ */
5575
+ async getLinesByCompanyId(companyId) {
5576
+ if (!companyId) {
5577
+ throw new Error("Company ID is required");
5578
+ }
5579
+ 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 });
5580
+ if (error) {
5581
+ console.error("Error fetching lines:", error);
5582
+ throw new Error(`Failed to fetch lines: ${error.message}`);
5583
+ }
5584
+ if (!data) {
5585
+ return [];
5586
+ }
5587
+ return data.map((line) => ({
5588
+ id: line.id,
5589
+ name: line.line_name,
5590
+ companyId: line.company_id,
5591
+ isActive: line.enable,
5592
+ createdAt: line.created_at,
5593
+ factoryId: line.factory_id
5594
+ }));
5595
+ }
5596
+ /**
5597
+ * Fetch all active lines (for all companies)
5598
+ * @returns Promise<Line[]> - Array of all active lines
5599
+ */
5600
+ async getAllLines() {
5601
+ 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 });
5602
+ if (error) {
5603
+ console.error("Error fetching all lines:", error);
5604
+ throw new Error(`Failed to fetch lines: ${error.message}`);
5605
+ }
5606
+ if (!data) {
5607
+ return [];
5608
+ }
5609
+ return data.map((line) => ({
5610
+ id: line.id,
5611
+ name: line.line_name,
5612
+ companyId: line.company_id,
5613
+ isActive: line.enable,
5614
+ createdAt: line.created_at,
5615
+ factoryId: line.factory_id
5616
+ }));
5617
+ }
5618
+ /**
5619
+ * Fetch a single line by ID
5620
+ * @param lineId - The line ID to fetch
5621
+ * @returns Promise<Line | null> - The line data or null if not found
5622
+ */
5623
+ async getLineById(lineId) {
5624
+ if (!lineId) {
5625
+ throw new Error("Line ID is required");
5626
+ }
5627
+ 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();
5628
+ if (error) {
5629
+ if (error.code === "PGRST116") {
5630
+ return null;
5631
+ }
5632
+ console.error("Error fetching line:", error);
5633
+ throw new Error(`Failed to fetch line: ${error.message}`);
5634
+ }
5635
+ if (!data) {
5636
+ return null;
5637
+ }
5638
+ return {
5639
+ id: data.id,
5640
+ name: data.line_name,
5641
+ companyId: data.company_id,
5642
+ isActive: data.enable,
5643
+ createdAt: data.created_at,
5644
+ factoryId: data.factory_id
5645
+ };
5646
+ }
5647
+ };
5648
+ var createLinesService = (supabase) => {
5649
+ return new LinesService(supabase);
5650
+ };
5651
+ var linesService = {
5652
+ create: createLinesService
5653
+ };
5654
+
5655
+ // src/lib/services/userService.ts
5656
+ var UserService = class {
5657
+ constructor(supabase) {
5658
+ this.supabase = supabase;
5659
+ }
5660
+ /**
5661
+ * Fetch all users mapped to a specific company
5662
+ * @param companyId - The company ID to fetch users for
5663
+ * @returns Promise<CompanyUser[]> - Array of users in the company
5664
+ */
5665
+ async getUsersByCompanyId(companyId) {
5666
+ if (!companyId) {
5667
+ throw new Error("Company ID is required");
5668
+ }
5669
+ try {
5670
+ console.log(`[UserService] getUsersByCompanyId called with companyId: ${companyId}`);
5671
+ const result = await this.getUsersByCompanyIdFallback(companyId);
5672
+ console.log(`[UserService] getUsersByCompanyId returning ${result.length} users:`, result.map((u) => ({ id: u.id, name: u.name })));
5673
+ return result;
5674
+ } catch (error) {
5675
+ console.error("Error in getUsersByCompanyId:", error);
5676
+ throw error;
5677
+ }
5678
+ }
5679
+ /**
5680
+ * Fallback method to fetch users by company ID using basic queries
5681
+ * Gets real user emails from the database
5682
+ * @param companyId - The company ID to fetch users for
5683
+ * @returns Promise<CompanyUser[]> - Array of users in the company
5684
+ */
5685
+ async getUsersByCompanyIdFallback(companyId) {
5686
+ try {
5687
+ console.log(`[UserService] About to query user_company_mapping with companyId: ${companyId}`);
5688
+ const { data: mappings, error: mappingError } = await this.supabase.from("user_company_mapping").select("user_id, created_at, updated_at").eq("company_id", companyId);
5689
+ console.log(`[UserService] Supabase query result:`, {
5690
+ data: mappings,
5691
+ error: mappingError,
5692
+ dataLength: mappings?.length || 0
5693
+ });
5694
+ if (mappingError) {
5695
+ console.error("Error fetching user company mappings:", mappingError);
5696
+ throw new Error(`Failed to fetch user mappings: ${mappingError.message}`);
5697
+ }
5698
+ if (!mappings || mappings.length === 0) {
5699
+ console.log(`[UserService] No mappings found for companyId: ${companyId}`);
5700
+ return [];
5701
+ }
5702
+ console.log(`[UserService] Found ${mappings.length} mappings for companyId: ${companyId}`);
5703
+ console.log(`[UserService] Raw mappings:`, mappings.map((m) => m.user_id));
5704
+ const users = mappings.map((mapping) => {
5705
+ const shortId = mapping.user_id.substring(0, 8);
5706
+ return {
5707
+ id: mapping.user_id,
5708
+ name: mapping.user_id,
5709
+ // Show the full user ID
5710
+ email: `${shortId}@company.com`,
5711
+ // Simple email format
5712
+ isActive: true,
5713
+ createdAt: mapping.created_at,
5714
+ updatedAt: mapping.updated_at
5715
+ };
5716
+ });
5717
+ console.log(`[UserService] Created ${users.length} user objects`);
5718
+ return users;
5719
+ } catch (error) {
5720
+ console.error("Error in getUsersByCompanyIdFallback:", error);
5721
+ throw error;
5722
+ }
5723
+ }
5724
+ /**
5725
+ * Check if a user is mapped to a specific company
5726
+ * @param userId - The user ID to check
5727
+ * @param companyId - The company ID to check against
5728
+ * @returns Promise<boolean> - Whether the user is mapped to the company
5729
+ */
5730
+ async isUserInCompany(userId, companyId) {
5731
+ if (!userId || !companyId) {
5732
+ return false;
5733
+ }
5734
+ try {
5735
+ const { data, error } = await this.supabase.from("user_company_mapping").select("id").eq("user_id", userId).eq("company_id", companyId).single();
5736
+ if (error) {
5737
+ if (error.code === "PGRST116") {
5738
+ return false;
5739
+ }
5740
+ console.error("Error checking user company mapping:", error);
5741
+ return false;
5742
+ }
5743
+ return !!data;
5744
+ } catch (error) {
5745
+ console.error("Error in isUserInCompany:", error);
5746
+ return false;
5747
+ }
5748
+ }
5749
+ /**
5750
+ * Get user details by user ID (if they exist in auth.users)
5751
+ * @param userId - The user ID to fetch
5752
+ * @returns Promise<CompanyUser | null> - The user data or null if not found
5753
+ */
5754
+ async getUserById(userId) {
5755
+ if (!userId) {
5756
+ return null;
5757
+ }
5758
+ try {
5759
+ const { data, error } = await this.supabase.from("users").select("id, email, raw_user_meta_data").eq("id", userId).single();
5760
+ if (error) {
5761
+ if (error.code === "PGRST116") {
5762
+ return null;
5763
+ }
5764
+ console.error("Error fetching user by ID:", error);
5765
+ return null;
5766
+ }
5767
+ if (!data) {
5768
+ return null;
5769
+ }
5770
+ const fullName = data.raw_user_meta_data?.full_name || data.raw_user_meta_data?.name || data.email?.split("@")[0];
5771
+ return {
5772
+ id: data.id,
5773
+ name: fullName,
5774
+ email: data.email,
5775
+ isActive: true,
5776
+ createdAt: void 0,
5777
+ updatedAt: void 0
5778
+ };
5779
+ } catch (error) {
5780
+ console.error("Error in getUserById:", error);
5781
+ return null;
5782
+ }
5783
+ }
5784
+ };
5785
+ var createUserService = (supabase) => {
5786
+ return new UserService(supabase);
5787
+ };
5788
+ var userService = {
5789
+ create: createUserService
5790
+ };
5791
+
5792
+ // src/lib/services/supervisorService.ts
5793
+ var SupervisorService = class {
5794
+ constructor(supabase) {
5795
+ this.supabase = supabase;
5796
+ this.linesService = new LinesService(supabase);
5797
+ this.userService = new UserService(supabase);
5798
+ }
5799
+ /**
5800
+ * Get supervisor management data for a specific company
5801
+ * Uses real lines data and real user data from the database
5802
+ * @param companyId - The company ID to fetch data for
5803
+ * @returns Promise<SupervisorManagementData>
5804
+ */
5805
+ async getSupervisorManagementData(companyId) {
5806
+ try {
5807
+ const lines = await this.linesService.getLinesByCompanyId(companyId);
5808
+ console.log(`[SupervisorService] Fetching users for companyId: ${companyId}`);
5809
+ const companyUsers = await this.userService.getUsersByCompanyId(companyId);
5810
+ console.log(`[SupervisorService] Received ${companyUsers.length} users from UserService`);
5811
+ const availableSupervisors = companyUsers.map((user) => ({
5812
+ id: user.id,
5813
+ name: user.name,
5814
+ email: user.email,
5815
+ isActive: user.isActive,
5816
+ createdAt: user.createdAt,
5817
+ updatedAt: user.updatedAt
5818
+ }));
5819
+ console.log(`[SupervisorService] Created ${availableSupervisors.length} available supervisors`);
5820
+ const assignments = lines.map((line) => {
5821
+ const currentSupervisor = this.getRandomSupervisorForLine(line.id, availableSupervisors);
5822
+ const assignment = {
5823
+ lineId: line.id,
5824
+ lineName: line.name,
5825
+ currentSupervisor,
5826
+ availableSupervisors
5827
+ };
5828
+ console.log(`[SupervisorService] Assignment for line ${line.name}: ${assignment.availableSupervisors.length} available supervisors`);
5829
+ return assignment;
5830
+ });
5831
+ console.log(`[SupervisorService] Final result: ${assignments.length} assignments, ${availableSupervisors.length} total supervisors`);
5832
+ return {
5833
+ assignments,
5834
+ allSupervisors: availableSupervisors,
5835
+ loading: false,
5836
+ error: void 0
5837
+ };
5838
+ } catch (error) {
5839
+ console.error("Error fetching supervisor management data:", error);
5840
+ return {
5841
+ assignments: [],
5842
+ allSupervisors: [],
5843
+ loading: false,
5844
+ error: error instanceof Error ? error.message : "Failed to fetch supervisor management data"
5845
+ };
5846
+ }
5847
+ }
5848
+ /**
5849
+ * Get all supervisors for a specific company (real data from database)
5850
+ * @param companyId - The company ID to fetch supervisors for
5851
+ * @returns Promise<Supervisor[]>
5852
+ */
5853
+ async getAllSupervisors(companyId) {
5854
+ try {
5855
+ const companyUsers = await this.userService.getUsersByCompanyId(companyId);
5856
+ return companyUsers.map((user) => ({
5857
+ id: user.id,
5858
+ name: user.name,
5859
+ email: user.email,
5860
+ isActive: user.isActive,
5861
+ createdAt: user.createdAt,
5862
+ updatedAt: user.updatedAt
5863
+ }));
5864
+ } catch (error) {
5865
+ console.error("Error fetching all supervisors:", error);
5866
+ return [];
5867
+ }
5868
+ }
5869
+ /**
5870
+ * Get active supervisors only for a specific company
5871
+ * @param companyId - The company ID to fetch active supervisors for
5872
+ * @returns Promise<Supervisor[]>
5873
+ */
5874
+ async getActiveSupervisors(companyId) {
5875
+ const allSupervisors = await this.getAllSupervisors(companyId);
5876
+ return allSupervisors.filter((sup) => sup.isActive);
5877
+ }
5878
+ /**
5879
+ * Assign a supervisor to a line
5880
+ * This is a mock implementation - in production, this would update the database
5881
+ * @param lineId - The line ID
5882
+ * @param supervisorId - The supervisor ID to assign
5883
+ * @returns Promise<boolean> - Success status
5884
+ */
5885
+ async assignSupervisorToLine(lineId, supervisorId) {
5886
+ try {
5887
+ console.log(`Assigning supervisor ${supervisorId} to line ${lineId}`);
5888
+ await new Promise((resolve) => setTimeout(resolve, 500));
5889
+ return true;
5890
+ } catch (error) {
5891
+ console.error("Error assigning supervisor to line:", error);
5892
+ return false;
5893
+ }
5894
+ }
5895
+ /**
5896
+ * Helper method to simulate supervisor assignments
5897
+ * In production, this would be fetched from the database
5898
+ * @param lineId - The line ID
5899
+ * @param supervisors - Available supervisors
5900
+ * @returns Supervisor or undefined
5901
+ */
5902
+ getRandomSupervisorForLine(lineId, supervisors) {
5903
+ const hash = lineId.split("").reduce((a, b) => {
5904
+ a = (a << 5) - a + b.charCodeAt(0);
5905
+ return a & a;
5906
+ }, 0);
5907
+ const index = Math.abs(hash) % (supervisors.length + 1);
5908
+ return index < supervisors.length ? supervisors[index] : void 0;
5909
+ }
5910
+ };
5911
+ var createSupervisorService = (supabase) => {
5912
+ return new SupervisorService(supabase);
5913
+ };
5914
+ var simulateApiDelay = (ms = 1e3) => {
5915
+ return new Promise((resolve) => setTimeout(resolve, ms));
5916
+ };
5224
5917
  var AuthContext = createContext({
5225
5918
  session: null,
5226
5919
  user: null,
5227
5920
  loading: true,
5228
5921
  error: null,
5229
5922
  signOut: async () => {
5923
+ },
5924
+ markFirstLoginCompleted: async () => false,
5925
+ showOnboarding: false,
5926
+ setShowOnboarding: () => {
5927
+ },
5928
+ completeOnboarding: async () => {
5230
5929
  }
5231
5930
  });
5232
5931
  var useAuth = () => useContext(AuthContext);
5233
5932
  var AuthProvider = ({ children }) => {
5234
5933
  const supabase = useSupabase();
5235
5934
  const { authConfig } = useDashboardConfig();
5935
+ const entityConfig = useEntityConfig();
5236
5936
  const [session, setSession] = useState(null);
5237
5937
  const [user, setUser] = useState(null);
5238
5938
  const [loading, setLoading] = useState(true);
5239
5939
  const [error, setError] = useState(null);
5940
+ const [showOnboarding, setShowOnboarding] = useState(false);
5240
5941
  const router = useRouter();
5241
5942
  authConfig?.userProfileTable;
5242
5943
  authConfig?.roleColumn || "role";
5944
+ const dashboardCompanyId = entityConfig?.companyId;
5243
5945
  const fetchUserDetails = useCallback(async (supabaseUser) => {
5244
5946
  console.log("[fetchUserDetails] Called for user:", supabaseUser.id, {
5245
5947
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5258,29 +5960,66 @@ var AuthProvider = ({ children }) => {
5258
5960
  reject(new Error("Profile fetch timeout"));
5259
5961
  }, 2e3)
5260
5962
  );
5261
- const rolePromise = supabase.from("user_roles").select("role_level").eq("user_id", supabaseUser.id).single();
5262
- const roleResult = await Promise.race([
5263
- rolePromise,
5963
+ const [rolePromise, companyPromise, onboardingPromise] = [
5964
+ supabase.from("user_roles").select("role_level").eq("user_id", supabaseUser.id).single(),
5965
+ supabase.from("user_company_mapping").select("company_id").eq("user_id", supabaseUser.id).single(),
5966
+ supabase.from("user_onboarding_tracking").select("first_login_completed").eq("user_id", supabaseUser.id).single()
5967
+ ];
5968
+ const [roleResult, companyResult, onboardingResult] = await Promise.race([
5969
+ Promise.all([rolePromise, companyPromise, onboardingPromise]),
5264
5970
  timeoutPromise
5265
- // Fixed: removed .then() which was causing the bug
5266
5971
  ]);
5267
5972
  let roleLevel = void 0;
5268
5973
  if (roleResult && !roleResult.error && roleResult.data) {
5269
5974
  roleLevel = roleResult.data.role_level;
5270
5975
  } else if (roleResult?.error && roleResult.error.code !== "PGRST116") {
5271
- console.log("Error fetching role_level:", roleResult.error.message);
5976
+ console.log("Error fetching user_roles data:", roleResult.error.message);
5977
+ }
5978
+ let firstLoginCompleted = false;
5979
+ if (onboardingResult && !onboardingResult.error && onboardingResult.data) {
5980
+ firstLoginCompleted = onboardingResult.data.first_login_completed ?? false;
5981
+ } else if (onboardingResult?.error && onboardingResult.error.code === "PGRST116") {
5982
+ console.log("[fetchUserDetails] Creating onboarding record for new user");
5983
+ const { error: insertError } = await supabase.from("user_onboarding_tracking").insert({
5984
+ user_id: supabaseUser.id,
5985
+ first_login_completed: false
5986
+ });
5987
+ if (insertError) {
5988
+ console.log("Error creating onboarding record:", insertError.message);
5989
+ }
5990
+ } else if (onboardingResult?.error) {
5991
+ console.log("Error fetching onboarding data:", onboardingResult.error.message);
5992
+ }
5993
+ let userCompanyId = void 0;
5994
+ if (companyResult && !companyResult.error && companyResult.data) {
5995
+ userCompanyId = companyResult.data.company_id;
5996
+ } else if (companyResult?.error && companyResult.error.code !== "PGRST116") {
5997
+ console.log("Error fetching company_id:", companyResult.error.message);
5998
+ }
5999
+ if (dashboardCompanyId && userCompanyId && userCompanyId !== dashboardCompanyId) {
6000
+ console.warn("[Auth] Company access denied:", {
6001
+ userCompanyId,
6002
+ dashboardCompanyId,
6003
+ userEmail: supabaseUser.email
6004
+ });
6005
+ throw new Error("COMPANY_ACCESS_DENIED");
5272
6006
  }
5273
6007
  return {
5274
6008
  ...basicUser,
5275
- role_level: roleLevel
6009
+ role_level: roleLevel,
6010
+ company_id: userCompanyId,
6011
+ first_login_completed: firstLoginCompleted
5276
6012
  };
5277
6013
  } catch (err) {
6014
+ if (err instanceof Error && err.message === "COMPANY_ACCESS_DENIED") {
6015
+ throw err;
6016
+ }
5278
6017
  if (err instanceof Error && err.message.includes("timeout")) {
5279
6018
  console.warn("Auth fetch timeout - using basic user info");
5280
6019
  }
5281
6020
  return basicUser;
5282
6021
  }
5283
- }, [supabase]);
6022
+ }, [supabase, dashboardCompanyId]);
5284
6023
  useEffect(() => {
5285
6024
  if (!supabase) return;
5286
6025
  let mounted = true;
@@ -5321,6 +6060,13 @@ var AuthProvider = ({ children }) => {
5321
6060
  } catch (err) {
5322
6061
  console.error("Error fetching user details during init:", err);
5323
6062
  if (mounted) {
6063
+ if (err instanceof Error && err.message === "COMPANY_ACCESS_DENIED") {
6064
+ setError(new Error("You do not have access to this dashboard. Please contact your administrator."));
6065
+ await supabase.auth.signOut();
6066
+ setSession(null);
6067
+ setUser(null);
6068
+ return;
6069
+ }
5324
6070
  setUser({
5325
6071
  id: initialSession.user.id,
5326
6072
  email: initialSession.user.email
@@ -5401,6 +6147,73 @@ var AuthProvider = ({ children }) => {
5401
6147
  subscription?.unsubscribe();
5402
6148
  };
5403
6149
  }, [supabase, fetchUserDetails]);
6150
+ const markFirstLoginCompleted = useCallback(async () => {
6151
+ console.log("[markFirstLoginCompleted] Starting update for user:", user?.id);
6152
+ if (!supabase || !user?.id) {
6153
+ console.error("[markFirstLoginCompleted] Missing requirements:", {
6154
+ hasSupabase: !!supabase,
6155
+ userId: user?.id
6156
+ });
6157
+ return false;
6158
+ }
6159
+ try {
6160
+ console.log("[markFirstLoginCompleted] Updating onboarding tracking for user_id:", user.id);
6161
+ const { data: existingData, error: checkError } = await supabase.from("user_onboarding_tracking").select("id").eq("user_id", user.id).single();
6162
+ let data, error2;
6163
+ if (checkError && checkError.code === "PGRST116") {
6164
+ console.log("[markFirstLoginCompleted] Creating onboarding record with completed status");
6165
+ const result = await supabase.from("user_onboarding_tracking").insert({
6166
+ user_id: user.id,
6167
+ first_login_completed: true
6168
+ }).select();
6169
+ data = result.data;
6170
+ error2 = result.error;
6171
+ } else {
6172
+ console.log("[markFirstLoginCompleted] Updating existing onboarding record");
6173
+ const result = await supabase.from("user_onboarding_tracking").update({
6174
+ first_login_completed: true,
6175
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
6176
+ }).eq("user_id", user.id).select();
6177
+ data = result.data;
6178
+ error2 = result.error;
6179
+ }
6180
+ if (error2) {
6181
+ console.error("[markFirstLoginCompleted] Database update error:", {
6182
+ error: error2,
6183
+ message: error2.message,
6184
+ details: error2.details,
6185
+ hint: error2.hint,
6186
+ code: error2.code
6187
+ });
6188
+ return false;
6189
+ }
6190
+ console.log("[markFirstLoginCompleted] Database update successful:", data);
6191
+ setUser((prevUser) => {
6192
+ if (!prevUser) return prevUser;
6193
+ const updatedUser = {
6194
+ ...prevUser,
6195
+ first_login_completed: true
6196
+ };
6197
+ console.log("[markFirstLoginCompleted] Local state updated:", updatedUser);
6198
+ return updatedUser;
6199
+ });
6200
+ console.log("[markFirstLoginCompleted] Successfully marked first login as completed");
6201
+ return true;
6202
+ } catch (err) {
6203
+ console.error("[markFirstLoginCompleted] Unexpected error:", err);
6204
+ return false;
6205
+ }
6206
+ }, [supabase, user?.id]);
6207
+ const completeOnboarding = useCallback(async () => {
6208
+ console.log("[completeOnboarding] Completing onboarding tour");
6209
+ const success = await markFirstLoginCompleted();
6210
+ if (success) {
6211
+ setShowOnboarding(false);
6212
+ console.log("[completeOnboarding] Onboarding completed successfully");
6213
+ } else {
6214
+ console.error("[completeOnboarding] Failed to mark first login as completed");
6215
+ }
6216
+ }, [markFirstLoginCompleted]);
5404
6217
  const signOut = async () => {
5405
6218
  if (!supabase) return;
5406
6219
  setLoading(true);
@@ -5411,7 +6224,17 @@ var AuthProvider = ({ children }) => {
5411
6224
  router.replace(logoutRedirectPath);
5412
6225
  }
5413
6226
  };
5414
- return /* @__PURE__ */ jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
6227
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: {
6228
+ session,
6229
+ user,
6230
+ loading,
6231
+ error,
6232
+ signOut,
6233
+ markFirstLoginCompleted,
6234
+ showOnboarding,
6235
+ setShowOnboarding,
6236
+ completeOnboarding
6237
+ }, children });
5415
6238
  };
5416
6239
  var defaultContextValue = {
5417
6240
  components: {},
@@ -8894,7 +9717,7 @@ var useSKUs = (companyId) => {
8894
9717
  // src/lib/hooks/useCanSaveTargets.ts
8895
9718
  var useCanSaveTargets = () => {
8896
9719
  const { user } = useAuth();
8897
- return user?.role_level === "owner";
9720
+ return true;
8898
9721
  };
8899
9722
  function useTicketHistory(companyId) {
8900
9723
  const [tickets, setTickets] = useState([]);
@@ -12468,6 +13291,100 @@ var useHourlyTargetMisses = ({
12468
13291
  clearMiss
12469
13292
  };
12470
13293
  };
13294
+ function useClipTypes() {
13295
+ const [clipTypes, setClipTypes] = useState([]);
13296
+ const [isLoading, setIsLoading] = useState(true);
13297
+ const [error, setError] = useState(null);
13298
+ const dashboardConfig = useDashboardConfig();
13299
+ const s3Service = dashboardConfig?.s3Config ? videoPrefetchManager.getS3Service(dashboardConfig) : null;
13300
+ const fetchClipTypes = useCallback(async () => {
13301
+ console.log("[useClipTypes] Starting fetchClipTypes, s3Service:", !!s3Service);
13302
+ if (!s3Service) {
13303
+ console.log("[useClipTypes] S3 service not initialized");
13304
+ setError("S3 service not initialized");
13305
+ setIsLoading(false);
13306
+ return;
13307
+ }
13308
+ setIsLoading(true);
13309
+ setError(null);
13310
+ try {
13311
+ console.log("[useClipTypes] Fetching clip types from S3Service...");
13312
+ const types = await s3Service.getClipTypes();
13313
+ console.log(`[useClipTypes] Fetched ${types?.length || 0} clip types:`, types);
13314
+ setClipTypes(types || []);
13315
+ } catch (err) {
13316
+ console.error("[useClipTypes] Error fetching clip types:", err);
13317
+ setError("Failed to load clip types");
13318
+ const fallbackTypes = [
13319
+ { id: "idle_time", type: "idle_time", label: "Low Value Moments", color: "purple", description: "Idle time activities" },
13320
+ { id: "best_cycle_time", type: "best_cycle_time", label: "Best Cycle Time", color: "green", description: "Fastest cycle today" },
13321
+ { id: "worst_cycle_time", type: "worst_cycle_time", label: "Worst Cycle Time", color: "red", description: "Slowest cycle today" },
13322
+ { id: "long_cycle_time", type: "long_cycle_time", label: "Long Cycle Time", color: "orange", description: "Above standard cycle times" }
13323
+ ];
13324
+ console.log("[useClipTypes] Using fallback clip types:", fallbackTypes);
13325
+ setClipTypes(fallbackTypes);
13326
+ } finally {
13327
+ setIsLoading(false);
13328
+ }
13329
+ }, [s3Service]);
13330
+ useEffect(() => {
13331
+ fetchClipTypes();
13332
+ }, [fetchClipTypes]);
13333
+ return {
13334
+ clipTypes,
13335
+ isLoading,
13336
+ error,
13337
+ refresh: fetchClipTypes
13338
+ };
13339
+ }
13340
+ function useClipTypesWithCounts(workspaceId, date, shiftId) {
13341
+ const { clipTypes, isLoading: typesLoading, error: typesError, refresh } = useClipTypes();
13342
+ const [counts, setCounts] = useState({});
13343
+ const [countsLoading, setCountsLoading] = useState(false);
13344
+ const dashboardConfig = useDashboardConfig();
13345
+ const s3Service = dashboardConfig?.s3Config ? videoPrefetchManager.getS3Service(dashboardConfig) : null;
13346
+ useEffect(() => {
13347
+ console.log("[useClipTypesWithCounts] Dependencies:", {
13348
+ s3Service: !!s3Service,
13349
+ workspaceId,
13350
+ date,
13351
+ shiftId,
13352
+ shiftIdType: typeof shiftId
13353
+ });
13354
+ if (!s3Service || !workspaceId || !date || shiftId === void 0) {
13355
+ console.log("[useClipTypesWithCounts] Skipping counts fetch - missing dependencies");
13356
+ return;
13357
+ }
13358
+ const fetchCounts = async () => {
13359
+ setCountsLoading(true);
13360
+ try {
13361
+ console.log("[useClipTypesWithCounts] Fetching counts...");
13362
+ const clipCounts = await s3Service.getClipCounts(
13363
+ workspaceId,
13364
+ date,
13365
+ shiftId.toString()
13366
+ );
13367
+ console.log("[useClipTypesWithCounts] Received counts:", clipCounts);
13368
+ setCounts(clipCounts);
13369
+ } catch (err) {
13370
+ console.error("[useClipTypesWithCounts] Error fetching counts:", err);
13371
+ } finally {
13372
+ setCountsLoading(false);
13373
+ }
13374
+ };
13375
+ fetchCounts();
13376
+ }, [s3Service, workspaceId, date, shiftId]);
13377
+ return {
13378
+ clipTypes: clipTypes.map((type) => ({
13379
+ ...type,
13380
+ count: counts[type.type] || 0
13381
+ })),
13382
+ isLoading: typesLoading || countsLoading,
13383
+ error: typesError,
13384
+ refresh,
13385
+ counts
13386
+ };
13387
+ }
12471
13388
  var MAX_RETRIES = 10;
12472
13389
  var RETRY_DELAY = 500;
12473
13390
  function useNavigation(customNavigate) {
@@ -12812,6 +13729,57 @@ var useFormatNumber = () => {
12812
13729
  );
12813
13730
  return { formatNumber };
12814
13731
  };
13732
+ function useAccessControl() {
13733
+ const { user } = useAuth();
13734
+ const userRole = useMemo(() => {
13735
+ if (!user?.role_level) return null;
13736
+ const roleLevel = user.role_level;
13737
+ if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor") {
13738
+ return roleLevel;
13739
+ }
13740
+ return "supervisor";
13741
+ }, [user?.role_level]);
13742
+ const allPages = [
13743
+ "/",
13744
+ "/leaderboard",
13745
+ "/kpis",
13746
+ "/targets",
13747
+ "/shifts",
13748
+ "/supervisor-management",
13749
+ "/skus",
13750
+ "/ai-agent",
13751
+ "/help",
13752
+ "/health",
13753
+ "/profile",
13754
+ "/workspace",
13755
+ "/factory-view"
13756
+ ];
13757
+ const accessiblePages = useMemo(() => {
13758
+ return allPages;
13759
+ }, []);
13760
+ const hasAccess = useMemo(() => {
13761
+ return (path) => {
13762
+ return true;
13763
+ };
13764
+ }, []);
13765
+ const isPageVisible = useMemo(() => {
13766
+ return (path) => {
13767
+ return true;
13768
+ };
13769
+ }, []);
13770
+ const canAccessPage = useMemo(() => {
13771
+ return (path) => {
13772
+ return true;
13773
+ };
13774
+ }, []);
13775
+ return {
13776
+ userRole,
13777
+ hasAccess,
13778
+ accessiblePages,
13779
+ isPageVisible,
13780
+ canAccessPage
13781
+ };
13782
+ }
12815
13783
  function useSupabaseClient() {
12816
13784
  const { supabaseUrl, supabaseKey } = useDashboardConfig();
12817
13785
  const supabase = useMemo(() => createClient(supabaseUrl, supabaseKey), [supabaseUrl, supabaseKey]);
@@ -20263,6 +21231,19 @@ var LoadingPage = ({
20263
21231
  ] }) });
20264
21232
  };
20265
21233
  var LoadingPage_default = LoadingPage;
21234
+ var AccessDeniedPage = () => /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center bg-gray-50", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md w-full bg-white shadow-lg rounded-lg p-6 text-center", children: [
21235
+ /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx("svg", { className: "mx-auto h-12 w-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ 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" }) }) }),
21236
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900 mb-2", children: "Access Denied" }),
21237
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-4", children: "You do not have access to this dashboard. Please contact your administrator for assistance." }),
21238
+ /* @__PURE__ */ jsx(
21239
+ "button",
21240
+ {
21241
+ onClick: () => window.location.href = "/login",
21242
+ className: "bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors",
21243
+ children: "Return to Login"
21244
+ }
21245
+ )
21246
+ ] }) });
20266
21247
  var withAuth = (WrappedComponent2, options) => {
20267
21248
  const defaultOptions = {
20268
21249
  redirectTo: "/login",
@@ -20270,7 +21251,7 @@ var withAuth = (WrappedComponent2, options) => {
20270
21251
  ...options
20271
21252
  };
20272
21253
  const WithAuthComponent = React19.memo(function WithAuthComponent2(props) {
20273
- const { session, loading } = useAuth();
21254
+ const { session, loading, error } = useAuth();
20274
21255
  const router = useRouter();
20275
21256
  React19.useEffect(() => {
20276
21257
  if (process.env.NODE_ENV === "development" && process.env.DEBUG_AUTH === "true") {
@@ -20278,14 +21259,17 @@ var withAuth = (WrappedComponent2, options) => {
20278
21259
  }
20279
21260
  }, [session, loading]);
20280
21261
  React19.useEffect(() => {
20281
- if (!loading && defaultOptions.requireAuth && !session) {
21262
+ if (!loading && defaultOptions.requireAuth && !session && !error) {
20282
21263
  console.log("Redirecting to login from withAuth");
20283
21264
  router.replace(defaultOptions.redirectTo);
20284
21265
  }
20285
- }, [session, loading, router]);
21266
+ }, [session, loading, router, error]);
20286
21267
  if (loading) {
20287
21268
  return /* @__PURE__ */ jsx(LoadingPage, { message: "Authenticating..." });
20288
21269
  }
21270
+ if (error && error.message.includes("You do not have access to this dashboard")) {
21271
+ return /* @__PURE__ */ jsx(AccessDeniedPage, {});
21272
+ }
20289
21273
  if (defaultOptions.requireAuth && !session) {
20290
21274
  return null;
20291
21275
  }
@@ -20294,6 +21278,31 @@ var withAuth = (WrappedComponent2, options) => {
20294
21278
  WithAuthComponent.displayName = `withAuth(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
20295
21279
  return WithAuthComponent;
20296
21280
  };
21281
+ function withAccessControl(WrappedComponent2, options = {}) {
21282
+ const {
21283
+ requiredPath,
21284
+ redirectTo = "/",
21285
+ UnauthorizedComponent
21286
+ } = options;
21287
+ const WithAccessControlComponent = (props) => {
21288
+ const router = useRouter();
21289
+ const { user, loading: authLoading } = useAuth();
21290
+ const { canAccessPage, userRole } = useAccessControl();
21291
+ requiredPath || router.pathname;
21292
+ if (authLoading) {
21293
+ return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
21294
+ /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }),
21295
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600", children: "Loading..." })
21296
+ ] }) });
21297
+ }
21298
+ if (!user) {
21299
+ return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-4", children: "Please log in to continue" }) }) });
21300
+ }
21301
+ return /* @__PURE__ */ jsx(WrappedComponent2, { ...props });
21302
+ };
21303
+ WithAccessControlComponent.displayName = `withAccessControl(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
21304
+ return WithAccessControlComponent;
21305
+ }
20297
21306
  var LoginPage = ({
20298
21307
  onRateLimitCheck,
20299
21308
  logoSrc = "/optifye-logo.png",
@@ -21872,7 +22881,7 @@ var HourlyOutputChartComponent = ({
21872
22881
  ticks.push(maxYValue);
21873
22882
  return [...new Set(ticks)].sort((a, b) => a - b);
21874
22883
  };
21875
- const renderLegend = () => /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
22884
+ const renderLegend = () => /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
21876
22885
  /* @__PURE__ */ jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
21877
22886
  /* @__PURE__ */ jsxs("span", { children: [
21878
22887
  "Target: ",
@@ -22572,6 +23581,89 @@ var VideoGridView = React19__default.memo(({
22572
23581
  ) }) });
22573
23582
  });
22574
23583
  VideoGridView.displayName = "VideoGridView";
23584
+ var Card2 = (props) => {
23585
+ const { Card: RegisteredCard } = useRegistry();
23586
+ return /* @__PURE__ */ jsx(RegisteredCard, { ...props });
23587
+ };
23588
+ var CardHeader2 = (props) => {
23589
+ const { CardHeader: RegisteredCardHeader } = useRegistry();
23590
+ return /* @__PURE__ */ jsx(RegisteredCardHeader, { ...props });
23591
+ };
23592
+ var CardTitle2 = (props) => {
23593
+ const { CardTitle: RegisteredCardTitle } = useRegistry();
23594
+ return /* @__PURE__ */ jsx(RegisteredCardTitle, { ...props });
23595
+ };
23596
+ var CardDescription2 = (props) => {
23597
+ const { CardDescription: RegisteredCardDescription } = useRegistry();
23598
+ return /* @__PURE__ */ jsx(RegisteredCardDescription, { ...props });
23599
+ };
23600
+ var CardContent2 = (props) => {
23601
+ const { CardContent: RegisteredCardContent } = useRegistry();
23602
+ return /* @__PURE__ */ jsx(RegisteredCardContent, { ...props });
23603
+ };
23604
+ var CardFooter2 = (props) => {
23605
+ const { CardFooter: RegisteredCardFooter } = useRegistry();
23606
+ return /* @__PURE__ */ jsx(RegisteredCardFooter, { ...props });
23607
+ };
23608
+ var WorkspaceMetricCardsImpl = ({
23609
+ workspace,
23610
+ className
23611
+ }) => {
23612
+ return /* @__PURE__ */ 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: [
23613
+ /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
23614
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Pieces Lost" }) }),
23615
+ /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23616
+ /* @__PURE__ */ 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)}` }),
23617
+ /* @__PURE__ */ 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}` })
23618
+ ] }) })
23619
+ ] }),
23620
+ /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
23621
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Efficiency" }) }),
23622
+ /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23623
+ /* @__PURE__ */ jsxs("p", { className: `text-5xl font-bold ${workspace.avg_efficiency >= 80 ? "text-green-500" : "text-red-500"}`, children: [
23624
+ workspace.avg_efficiency.toFixed(1),
23625
+ "%"
23626
+ ] }),
23627
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Target: 80%" })
23628
+ ] }) })
23629
+ ] }),
23630
+ /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
23631
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "PPH" }) }),
23632
+ /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23633
+ /* @__PURE__ */ 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) }),
23634
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
23635
+ "Standard: ",
23636
+ workspace.pph_threshold.toFixed(1)
23637
+ ] })
23638
+ ] }) })
23639
+ ] }),
23640
+ /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
23641
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
23642
+ /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23643
+ /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
23644
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
23645
+ "Standard: ",
23646
+ workspace.ideal_cycle_time?.toFixed(1) || 0,
23647
+ "s"
23648
+ ] })
23649
+ ] }) })
23650
+ ] }),
23651
+ /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
23652
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Idle Time" }) }),
23653
+ /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23654
+ /* @__PURE__ */ jsx("p", { className: `text-4xl font-bold ${!workspace.idle_time || workspace.idle_time <= 0 ? "text-green-500" : workspace.idle_time <= 300 ? "text-yellow-500" : (
23655
+ // 5 minutes or less
23656
+ "text-red-500"
23657
+ )}`, children: formatIdleTime(workspace.idle_time) }),
23658
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Total idle time" })
23659
+ ] }) })
23660
+ ] })
23661
+ ] });
23662
+ };
23663
+ var WorkspaceMetricCards = (props) => {
23664
+ const { WorkspaceMetricCards: RegisteredComponent } = useRegistry();
23665
+ return /* @__PURE__ */ jsx(RegisteredComponent, { ...props });
23666
+ };
22575
23667
  var defaults = {
22576
23668
  Card,
22577
23669
  CardHeader,
@@ -22581,7 +23673,8 @@ var defaults = {
22581
23673
  CardFooter,
22582
23674
  Button,
22583
23675
  HourlyOutputChart,
22584
- VideoGridView
23676
+ VideoGridView,
23677
+ WorkspaceMetricCards: WorkspaceMetricCardsImpl
22585
23678
  };
22586
23679
  var RegistryCtx = createContext(defaults);
22587
23680
  var useRegistry = () => {
@@ -24053,30 +25146,6 @@ var ISTTimer = memo(() => {
24053
25146
  });
24054
25147
  ISTTimer.displayName = "ISTTimer";
24055
25148
  var ISTTimer_default = ISTTimer;
24056
- var Card2 = (props) => {
24057
- const { Card: RegisteredCard } = useRegistry();
24058
- return /* @__PURE__ */ jsx(RegisteredCard, { ...props });
24059
- };
24060
- var CardHeader2 = (props) => {
24061
- const { CardHeader: RegisteredCardHeader } = useRegistry();
24062
- return /* @__PURE__ */ jsx(RegisteredCardHeader, { ...props });
24063
- };
24064
- var CardTitle2 = (props) => {
24065
- const { CardTitle: RegisteredCardTitle } = useRegistry();
24066
- return /* @__PURE__ */ jsx(RegisteredCardTitle, { ...props });
24067
- };
24068
- var CardDescription2 = (props) => {
24069
- const { CardDescription: RegisteredCardDescription } = useRegistry();
24070
- return /* @__PURE__ */ jsx(RegisteredCardDescription, { ...props });
24071
- };
24072
- var CardContent2 = (props) => {
24073
- const { CardContent: RegisteredCardContent } = useRegistry();
24074
- return /* @__PURE__ */ jsx(RegisteredCardContent, { ...props });
24075
- };
24076
- var CardFooter2 = (props) => {
24077
- const { CardFooter: RegisteredCardFooter } = useRegistry();
24078
- return /* @__PURE__ */ jsx(RegisteredCardFooter, { ...props });
24079
- };
24080
25149
  var TicketHistory = ({ companyId }) => {
24081
25150
  const { tickets, loading, error } = useTicketHistory(companyId);
24082
25151
  const [expandedTickets, setExpandedTickets] = useState(/* @__PURE__ */ new Set());
@@ -26647,61 +27716,6 @@ var WorkspaceMonthlyPdfGenerator = ({
26647
27716
  }
26648
27717
  );
26649
27718
  };
26650
- var WorkspaceMetricCards = ({
26651
- workspace,
26652
- className
26653
- }) => {
26654
- return /* @__PURE__ */ 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: [
26655
- /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
26656
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Pieces Lost" }) }),
26657
- /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
26658
- /* @__PURE__ */ 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)}` }),
26659
- /* @__PURE__ */ 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}` })
26660
- ] }) })
26661
- ] }),
26662
- /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
26663
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Efficiency" }) }),
26664
- /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
26665
- /* @__PURE__ */ jsxs("p", { className: `text-5xl font-bold ${workspace.avg_efficiency >= 80 ? "text-green-500" : "text-red-500"}`, children: [
26666
- workspace.avg_efficiency.toFixed(1),
26667
- "%"
26668
- ] }),
26669
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Target: 80%" })
26670
- ] }) })
26671
- ] }),
26672
- /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
26673
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "PPH" }) }),
26674
- /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
26675
- /* @__PURE__ */ 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) }),
26676
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
26677
- "Standard: ",
26678
- workspace.pph_threshold.toFixed(1)
26679
- ] })
26680
- ] }) })
26681
- ] }),
26682
- /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
26683
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
26684
- /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
26685
- /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
26686
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
26687
- "Standard: ",
26688
- workspace.ideal_cycle_time?.toFixed(1) || 0,
26689
- "s"
26690
- ] })
26691
- ] }) })
26692
- ] }),
26693
- /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
26694
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Idle Time" }) }),
26695
- /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
26696
- /* @__PURE__ */ jsx("p", { className: `text-4xl font-bold ${!workspace.idle_time || workspace.idle_time <= 0 ? "text-green-500" : workspace.idle_time <= 300 ? "text-yellow-500" : (
26697
- // 5 minutes or less
26698
- "text-red-500"
26699
- )}`, children: formatIdleTime(workspace.idle_time) }),
26700
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Total idle time" })
26701
- ] }) })
26702
- ] })
26703
- ] });
26704
- };
26705
27719
  var LiveTimer = () => {
26706
27720
  const [time2, setTime] = useState(/* @__PURE__ */ new Date());
26707
27721
  useEffect(() => {
@@ -27169,7 +28183,7 @@ var VideoPlayer = React19__default.forwardRef(({
27169
28183
  muted,
27170
28184
  loop,
27171
28185
  poster,
27172
- // Enhanced HLS configuration
28186
+ // Optimized HLS configuration for VOD content - PERFORMANCE OPTIMIZED
27173
28187
  html5: {
27174
28188
  vhs: {
27175
28189
  // VHS (Video HTTP Streaming) options for HLS
@@ -27179,12 +28193,59 @@ var VideoPlayer = React19__default.forwardRef(({
27179
28193
  // Use native HLS on Safari
27180
28194
  enableLowInitialPlaylist: true,
27181
28195
  smoothQualityChange: true,
27182
- // Error recovery
28196
+ // Optimized bandwidth and buffering for VOD
28197
+ bandwidth: 5e7,
28198
+ // Start with high bandwidth assumption (50 Mbps)
28199
+ initialBandwidth: 5e7,
28200
+ // Assume good connection initially
28201
+ limitRenditionByPlayerDimensions: true,
28202
+ // Buffer configuration for optimal VOD playback
28203
+ maxBufferLength: 30,
28204
+ // Don't over-buffer (30 seconds)
28205
+ maxMaxBufferLength: 60,
28206
+ // Absolute max buffer (60 seconds)
28207
+ maxBufferSize: 60 * 1e3 * 1e3,
28208
+ // 60MB max buffer size
28209
+ maxBufferHole: 0.5,
28210
+ // Allow 0.5s holes in buffer
28211
+ bufferBasedABR: true,
28212
+ // Segment loading optimization
27183
28213
  maxPlaylistRetries: 3,
28214
+ playlistRetryDelay: 500,
28215
+ // 500ms between retries
27184
28216
  playlistExclusionDuration: 60,
27185
- // Buffer configuration
27186
- maxMaxBufferLength: 30,
27187
- bufferBasedABR: true
28217
+ segmentLoadingRetryAttempts: 3,
28218
+ segmentLoadingRetryDelay: 1e3,
28219
+ // 1s between segment retries
28220
+ segmentLoadingTimeOut: 2e4,
28221
+ // 20s timeout for segments
28222
+ manifestLoadingTimeOut: 1e4,
28223
+ // 10s timeout for manifest
28224
+ // Performance optimizations
28225
+ experimentalBufferBasedCodecSwitching: true,
28226
+ experimentalCacheEncryptionKeys: true,
28227
+ handlePartialData: true,
28228
+ allowSeeksWithinUnsafeLiveWindow: false,
28229
+ // VOD content
28230
+ experimentalLLHLS: false,
28231
+ // Disable Low Latency HLS for VOD
28232
+ // Connection settings
28233
+ enableWorker: true,
28234
+ // Use web worker for better performance
28235
+ progressive: true,
28236
+ // Progressive download
28237
+ // Adaptive bitrate settings (if multi-quality available)
28238
+ abrEwmaFastLive: 3,
28239
+ abrEwmaSlowLive: 9,
28240
+ abrBandWidthFactor: 0.95,
28241
+ abrBandWidthUpFactor: 0.7,
28242
+ abrMaxWithRealBitrate: false,
28243
+ // Request options for better network handling
28244
+ requestOptions: {
28245
+ timeout: 3e4,
28246
+ // 30s overall timeout
28247
+ maxRetry: 3
28248
+ }
27188
28249
  },
27189
28250
  nativeVideoTracks: false,
27190
28251
  nativeAudioTracks: false,
@@ -27249,9 +28310,17 @@ var VideoPlayer = React19__default.forwardRef(({
27249
28310
  onError?.(player, error);
27250
28311
  });
27251
28312
  if (src) {
28313
+ const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
28314
+ let videoSrc = src;
28315
+ if (src.startsWith("#EXTM3U")) {
28316
+ const blob = new Blob([src], { type: "application/x-mpegURL" });
28317
+ videoSrc = URL.createObjectURL(blob);
28318
+ console.log("[VideoPlayer] Created blob URL for HLS playlist content");
28319
+ player._blobUrl = videoSrc;
28320
+ }
27252
28321
  player.src({
27253
- src,
27254
- type: src.endsWith(".m3u8") ? "application/x-mpegURL" : "video/mp4"
28322
+ src: videoSrc,
28323
+ type: isHLS ? "application/x-mpegURL" : "video/mp4"
27255
28324
  });
27256
28325
  }
27257
28326
  }, [
@@ -27271,16 +28340,34 @@ var VideoPlayer = React19__default.forwardRef(({
27271
28340
  ]);
27272
28341
  useEffect(() => {
27273
28342
  if (playerRef.current && src) {
28343
+ const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
28344
+ let videoSrc = src;
28345
+ let blobUrl = null;
28346
+ if (src.startsWith("#EXTM3U")) {
28347
+ const blob = new Blob([src], { type: "application/x-mpegURL" });
28348
+ blobUrl = URL.createObjectURL(blob);
28349
+ videoSrc = blobUrl;
28350
+ console.log("[VideoPlayer] Created blob URL for HLS playlist content (source update)");
28351
+ }
27274
28352
  playerRef.current.src({
27275
- src,
27276
- type: src.endsWith(".m3u8") ? "application/x-mpegURL" : "video/mp4"
28353
+ src: videoSrc,
28354
+ type: isHLS ? "application/x-mpegURL" : "video/mp4"
27277
28355
  });
28356
+ return () => {
28357
+ if (blobUrl) {
28358
+ URL.revokeObjectURL(blobUrl);
28359
+ }
28360
+ };
27278
28361
  }
27279
28362
  }, [src]);
27280
28363
  useEffect(() => {
27281
28364
  initializePlayer();
27282
28365
  return () => {
27283
28366
  if (playerRef.current) {
28367
+ const blobUrl = playerRef.current._blobUrl;
28368
+ if (blobUrl) {
28369
+ URL.revokeObjectURL(blobUrl);
28370
+ }
27284
28371
  playerRef.current.dispose();
27285
28372
  playerRef.current = null;
27286
28373
  setIsReady(false);
@@ -27352,6 +28439,233 @@ var VideoPlayer = React19__default.forwardRef(({
27352
28439
  ] });
27353
28440
  });
27354
28441
  VideoPlayer.displayName = "VideoPlayer";
28442
+ var CroppedVideoPlayer = forwardRef(({
28443
+ crop,
28444
+ debug = false,
28445
+ ...videoProps
28446
+ }, ref) => {
28447
+ const videoContainerRef = useRef(null);
28448
+ const hiddenVideoRef = useRef(null);
28449
+ const canvasRef = useRef(null);
28450
+ const animationFrameRef = useRef(null);
28451
+ const videoElementRef = useRef(null);
28452
+ const [isVideoReady, setIsVideoReady] = useState(false);
28453
+ const [canvasDimensions, setCanvasDimensions] = useState({ width: 0, height: 0 });
28454
+ const [isProcessing, setIsProcessing] = useState(false);
28455
+ const stopCanvasRendering = useCallback(() => {
28456
+ if (animationFrameRef.current) {
28457
+ cancelAnimationFrame(animationFrameRef.current);
28458
+ animationFrameRef.current = null;
28459
+ }
28460
+ }, []);
28461
+ useImperativeHandle(ref, () => ({
28462
+ player: hiddenVideoRef.current?.player || null,
28463
+ play: () => hiddenVideoRef.current?.play() || void 0,
28464
+ pause: () => hiddenVideoRef.current?.pause(),
28465
+ currentTime: (time2) => {
28466
+ if (time2 !== void 0 && hiddenVideoRef.current) {
28467
+ return hiddenVideoRef.current.currentTime(time2);
28468
+ }
28469
+ return hiddenVideoRef.current?.currentTime() || 0;
28470
+ },
28471
+ duration: () => hiddenVideoRef.current?.duration() || 0,
28472
+ paused: () => hiddenVideoRef.current?.paused() || true,
28473
+ mute: (isMuted) => hiddenVideoRef.current?.mute(isMuted) || false,
28474
+ volume: (level) => hiddenVideoRef.current?.volume(level) || 0,
28475
+ dispose: () => {
28476
+ hiddenVideoRef.current?.dispose();
28477
+ stopCanvasRendering();
28478
+ },
28479
+ isReady: hiddenVideoRef.current?.isReady || false
28480
+ }), [stopCanvasRendering]);
28481
+ const calculateCanvasDimensions = useCallback(() => {
28482
+ if (!crop || !videoContainerRef.current) return;
28483
+ const container = videoContainerRef.current;
28484
+ const containerWidth = container.clientWidth;
28485
+ const containerHeight = container.clientHeight;
28486
+ const cropAspectRatio = crop.width / crop.height;
28487
+ let canvasWidth;
28488
+ let canvasHeight;
28489
+ if (containerWidth / containerHeight > cropAspectRatio) {
28490
+ canvasHeight = containerHeight;
28491
+ canvasWidth = canvasHeight * cropAspectRatio;
28492
+ } else {
28493
+ canvasWidth = containerWidth;
28494
+ canvasHeight = canvasWidth / cropAspectRatio;
28495
+ }
28496
+ setCanvasDimensions({
28497
+ width: Math.floor(canvasWidth),
28498
+ height: Math.floor(canvasHeight)
28499
+ });
28500
+ }, [crop]);
28501
+ const renderFrameToCanvas = useCallback(() => {
28502
+ if (!canvasRef.current || !videoElementRef.current || !crop) {
28503
+ return;
28504
+ }
28505
+ const canvas = canvasRef.current;
28506
+ const video = videoElementRef.current;
28507
+ const ctx = canvas.getContext("2d");
28508
+ if (!ctx || video.paused || video.ended) {
28509
+ return;
28510
+ }
28511
+ const videoWidth = video.videoWidth;
28512
+ const videoHeight = video.videoHeight;
28513
+ if (videoWidth === 0 || videoHeight === 0) {
28514
+ animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
28515
+ return;
28516
+ }
28517
+ const cropX = crop.x / 100 * videoWidth;
28518
+ const cropY = crop.y / 100 * videoHeight;
28519
+ const cropWidth = crop.width / 100 * videoWidth;
28520
+ const cropHeight = crop.height / 100 * videoHeight;
28521
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
28522
+ ctx.drawImage(
28523
+ video,
28524
+ cropX,
28525
+ cropY,
28526
+ cropWidth,
28527
+ cropHeight,
28528
+ // Source rectangle (crop area)
28529
+ 0,
28530
+ 0,
28531
+ canvas.width,
28532
+ canvas.height
28533
+ // Destination (full canvas)
28534
+ );
28535
+ animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
28536
+ }, [crop]);
28537
+ const handleVideoReady = useCallback((player) => {
28538
+ console.log("[CroppedVideoPlayer] Video player ready");
28539
+ const videoEl = player.el().querySelector("video");
28540
+ if (videoEl) {
28541
+ videoElementRef.current = videoEl;
28542
+ setIsVideoReady(true);
28543
+ }
28544
+ videoProps.onReady?.(player);
28545
+ }, [videoProps]);
28546
+ const handleVideoPlay = useCallback((player) => {
28547
+ console.log("[CroppedVideoPlayer] Video playing, starting canvas rendering");
28548
+ if (crop && canvasRef.current) {
28549
+ setIsProcessing(true);
28550
+ renderFrameToCanvas();
28551
+ }
28552
+ videoProps.onPlay?.(player);
28553
+ }, [crop, renderFrameToCanvas, videoProps]);
28554
+ const handleVideoPause = useCallback((player) => {
28555
+ console.log("[CroppedVideoPlayer] Video paused, stopping canvas rendering");
28556
+ stopCanvasRendering();
28557
+ setIsProcessing(false);
28558
+ videoProps.onPause?.(player);
28559
+ }, [stopCanvasRendering, videoProps]);
28560
+ const handleVideoEnded = useCallback((player) => {
28561
+ console.log("[CroppedVideoPlayer] Video ended");
28562
+ stopCanvasRendering();
28563
+ setIsProcessing(false);
28564
+ videoProps.onEnded?.(player);
28565
+ }, [stopCanvasRendering, videoProps]);
28566
+ const handleSeeking = useCallback((player) => {
28567
+ console.log("[CroppedVideoPlayer] Video seeking");
28568
+ if (crop && !player.paused()) {
28569
+ renderFrameToCanvas();
28570
+ }
28571
+ videoProps.onSeeking?.(player);
28572
+ }, [crop, renderFrameToCanvas, videoProps]);
28573
+ const handleSeeked = useCallback((player) => {
28574
+ console.log("[CroppedVideoPlayer] Video seeked");
28575
+ if (crop && !player.paused()) {
28576
+ renderFrameToCanvas();
28577
+ }
28578
+ videoProps.onSeeked?.(player);
28579
+ }, [crop, renderFrameToCanvas, videoProps]);
28580
+ const handleLoadedMetadata = useCallback((player) => {
28581
+ console.log("[CroppedVideoPlayer] Video metadata loaded");
28582
+ calculateCanvasDimensions();
28583
+ videoProps.onLoadedMetadata?.(player);
28584
+ }, [calculateCanvasDimensions, videoProps]);
28585
+ useEffect(() => {
28586
+ calculateCanvasDimensions();
28587
+ const handleResize = () => {
28588
+ calculateCanvasDimensions();
28589
+ };
28590
+ window.addEventListener("resize", handleResize);
28591
+ return () => {
28592
+ window.removeEventListener("resize", handleResize);
28593
+ };
28594
+ }, [calculateCanvasDimensions]);
28595
+ useEffect(() => {
28596
+ return () => {
28597
+ stopCanvasRendering();
28598
+ };
28599
+ }, [stopCanvasRendering]);
28600
+ if (!crop) {
28601
+ return /* @__PURE__ */ jsx(VideoPlayer, { ref, ...videoProps });
28602
+ }
28603
+ return /* @__PURE__ */ jsxs(
28604
+ "div",
28605
+ {
28606
+ ref: videoContainerRef,
28607
+ className: `relative w-full h-full flex items-center justify-center bg-black ${videoProps.className || ""}`,
28608
+ children: [
28609
+ /* @__PURE__ */ jsx("div", { className: "hidden", children: /* @__PURE__ */ jsx(
28610
+ VideoPlayer,
28611
+ {
28612
+ ref: hiddenVideoRef,
28613
+ ...videoProps,
28614
+ onReady: handleVideoReady,
28615
+ onPlay: handleVideoPlay,
28616
+ onPause: handleVideoPause,
28617
+ onEnded: handleVideoEnded,
28618
+ onSeeking: handleSeeking,
28619
+ onSeeked: handleSeeked,
28620
+ onLoadedMetadata: handleLoadedMetadata
28621
+ }
28622
+ ) }),
28623
+ /* @__PURE__ */ jsx(
28624
+ "canvas",
28625
+ {
28626
+ ref: canvasRef,
28627
+ width: canvasDimensions.width,
28628
+ height: canvasDimensions.height,
28629
+ className: "max-w-full max-h-full",
28630
+ style: {
28631
+ display: isVideoReady ? "block" : "none",
28632
+ width: `${canvasDimensions.width}px`,
28633
+ height: `${canvasDimensions.height}px`,
28634
+ pointerEvents: "none"
28635
+ // Allow clicks to pass through to controls
28636
+ }
28637
+ }
28638
+ ),
28639
+ !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
28640
+ debug && isVideoReady && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
28641
+ /* @__PURE__ */ jsxs("div", { children: [
28642
+ "Crop: ",
28643
+ crop.x,
28644
+ ",",
28645
+ crop.y,
28646
+ " ",
28647
+ crop.width,
28648
+ "x",
28649
+ crop.height,
28650
+ "%"
28651
+ ] }),
28652
+ /* @__PURE__ */ jsxs("div", { children: [
28653
+ "Canvas: ",
28654
+ canvasDimensions.width,
28655
+ "x",
28656
+ canvasDimensions.height,
28657
+ "px"
28658
+ ] }),
28659
+ /* @__PURE__ */ jsxs("div", { children: [
28660
+ "Processing: ",
28661
+ isProcessing ? "Yes" : "No"
28662
+ ] })
28663
+ ] })
28664
+ ]
28665
+ }
28666
+ );
28667
+ });
28668
+ CroppedVideoPlayer.displayName = "CroppedVideoPlayer";
27355
28669
  var BackButton = ({
27356
28670
  onClick,
27357
28671
  text = "Back",
@@ -27661,6 +28975,244 @@ var InlineEditableText = ({
27661
28975
  }
27662
28976
  );
27663
28977
  };
28978
+ var getSupabaseClient3 = () => {
28979
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
28980
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
28981
+ if (!url || !key) {
28982
+ throw new Error("Supabase configuration missing");
28983
+ }
28984
+ return createClient(url, key);
28985
+ };
28986
+ var getAuthToken3 = async () => {
28987
+ try {
28988
+ const supabase = getSupabaseClient3();
28989
+ const { data: { session } } = await supabase.auth.getSession();
28990
+ return session?.access_token || null;
28991
+ } catch (error) {
28992
+ console.error("[useWorkspaceCrop] Error getting auth token:", error);
28993
+ return null;
28994
+ }
28995
+ };
28996
+ function useWorkspaceCrop(workspaceId) {
28997
+ const [crop, setCrop] = useState(null);
28998
+ const [isLoading, setIsLoading] = useState(true);
28999
+ const [error, setError] = useState(null);
29000
+ useEffect(() => {
29001
+ if (!workspaceId) {
29002
+ setIsLoading(false);
29003
+ return;
29004
+ }
29005
+ const fetchCrop = async () => {
29006
+ setIsLoading(true);
29007
+ setError(null);
29008
+ try {
29009
+ const token = await getAuthToken3();
29010
+ if (!token) {
29011
+ throw new Error("Authentication required");
29012
+ }
29013
+ const response = await fetch("/api/clips/supabase", {
29014
+ method: "POST",
29015
+ headers: {
29016
+ "Content-Type": "application/json",
29017
+ "Authorization": `Bearer ${token}`
29018
+ },
29019
+ body: JSON.stringify({
29020
+ action: "crop",
29021
+ workspaceId
29022
+ })
29023
+ });
29024
+ if (!response.ok) {
29025
+ throw new Error(`Failed to fetch crop: ${response.statusText}`);
29026
+ }
29027
+ const data = await response.json();
29028
+ console.log(`[useWorkspaceCrop] Fetched crop for workspace ${workspaceId}:`, data.crop);
29029
+ setCrop(data.crop);
29030
+ } catch (err) {
29031
+ console.error("[useWorkspaceCrop] Error fetching crop:", err);
29032
+ setError(err instanceof Error ? err.message : "Failed to fetch crop configuration");
29033
+ setCrop(null);
29034
+ } finally {
29035
+ setIsLoading(false);
29036
+ }
29037
+ };
29038
+ fetchCrop();
29039
+ }, [workspaceId]);
29040
+ return { crop, isLoading, error };
29041
+ }
29042
+ var getSeverityIcon = (severity) => {
29043
+ switch (severity) {
29044
+ case "high":
29045
+ return /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3 text-red-500" });
29046
+ case "medium":
29047
+ return /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3 text-yellow-500" });
29048
+ case "low":
29049
+ return /* @__PURE__ */ jsx(CheckCircle, { className: "h-3 w-3 text-green-500" });
29050
+ default:
29051
+ return /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3 text-gray-400" });
29052
+ }
29053
+ };
29054
+ var getColorClasses = (color2) => {
29055
+ const colorMap = {
29056
+ "red": { bg: "bg-red-50", text: "text-red-700", border: "border-red-200" },
29057
+ "yellow": { bg: "bg-yellow-50", text: "text-yellow-700", border: "border-yellow-200" },
29058
+ "orange": { bg: "bg-orange-50", text: "text-orange-700", border: "border-orange-200" },
29059
+ "green": { bg: "bg-green-50", text: "text-green-700", border: "border-green-200" },
29060
+ "blue": { bg: "bg-blue-50", text: "text-blue-700", border: "border-blue-200" },
29061
+ "purple": { bg: "bg-purple-50", text: "text-purple-700", border: "border-purple-200" },
29062
+ "gray": { bg: "bg-gray-50", text: "text-gray-700", border: "border-gray-200" }
29063
+ };
29064
+ return colorMap[color2] || colorMap["gray"];
29065
+ };
29066
+ var FileManagerFilters = ({
29067
+ categories,
29068
+ videos,
29069
+ activeFilter,
29070
+ currentVideoId,
29071
+ counts,
29072
+ onFilterChange,
29073
+ onVideoSelect,
29074
+ className = ""
29075
+ }) => {
29076
+ const [expandedNodes, setExpandedNodes] = useState(/* @__PURE__ */ new Set());
29077
+ const [searchTerm, setSearchTerm] = useState("");
29078
+ const filterTree = useMemo(() => {
29079
+ const tree = [];
29080
+ categories.forEach((category) => {
29081
+ const categoryCount = counts?.[category.id] || 0;
29082
+ let categoryVideos = videos.filter((video) => video.type === category.id);
29083
+ if (searchTerm.trim()) {
29084
+ categoryVideos = categoryVideos.filter(
29085
+ (video) => video.description.toLowerCase().includes(searchTerm.toLowerCase()) || video.timestamp.includes(searchTerm) || video.severity.toLowerCase().includes(searchTerm.toLowerCase())
29086
+ );
29087
+ }
29088
+ if (categoryCount > 0 || categoryVideos.length > 0) {
29089
+ const colorClasses = getColorClasses(category.color);
29090
+ const videoNodes = categoryVideos.map((video, index) => ({
29091
+ id: video.id,
29092
+ label: `${video.timestamp.substring(11, 19)} - ${video.description}`,
29093
+ type: "video",
29094
+ icon: getSeverityIcon(video.severity),
29095
+ timestamp: video.timestamp,
29096
+ severity: video.severity
29097
+ }));
29098
+ tree.push({
29099
+ id: category.id,
29100
+ label: category.label,
29101
+ type: "category",
29102
+ count: categoryCount || categoryVideos.length,
29103
+ // Use API count if available
29104
+ videos: categoryVideos,
29105
+ children: videoNodes,
29106
+ icon: expandedNodes.has(category.id) ? /* @__PURE__ */ jsx(FolderOpen, { className: `h-4 w-4 ${colorClasses.text}` }) : /* @__PURE__ */ jsx(Folder, { className: `h-4 w-4 ${colorClasses.text}` }),
29107
+ color: category.color
29108
+ });
29109
+ }
29110
+ });
29111
+ return tree;
29112
+ }, [categories, videos, expandedNodes, searchTerm, counts]);
29113
+ const toggleExpanded = (nodeId) => {
29114
+ const newExpanded = new Set(expandedNodes);
29115
+ if (newExpanded.has(nodeId)) {
29116
+ newExpanded.delete(nodeId);
29117
+ } else {
29118
+ newExpanded.add(nodeId);
29119
+ }
29120
+ setExpandedNodes(newExpanded);
29121
+ };
29122
+ const handleNodeClick = (node) => {
29123
+ if (node.type === "category") {
29124
+ toggleExpanded(node.id);
29125
+ onFilterChange(node.id);
29126
+ } else if (node.type === "video") {
29127
+ const videoIndex = videos.findIndex((v) => v.id === node.id);
29128
+ if (videoIndex !== -1) {
29129
+ onVideoSelect(videoIndex);
29130
+ }
29131
+ }
29132
+ };
29133
+ const renderNode = (node, depth = 0) => {
29134
+ const isExpanded = expandedNodes.has(node.id);
29135
+ const isActive = activeFilter === node.id;
29136
+ const isCurrentVideo = currentVideoId === node.id;
29137
+ const hasChildren = node.children && node.children.length > 0;
29138
+ const colorClasses = node.color ? getColorClasses(node.color) : null;
29139
+ return /* @__PURE__ */ jsxs("div", { className: "select-none animate-in", children: [
29140
+ /* @__PURE__ */ jsxs(
29141
+ "div",
29142
+ {
29143
+ 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" : ""}`,
29144
+ onClick: () => handleNodeClick(node),
29145
+ children: [
29146
+ hasChildren && /* @__PURE__ */ jsx(
29147
+ "button",
29148
+ {
29149
+ 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"}`,
29150
+ onClick: (e) => {
29151
+ e.stopPropagation();
29152
+ toggleExpanded(node.id);
29153
+ },
29154
+ children: isExpanded ? /* @__PURE__ */ jsx(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__ */ jsx(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"}` })
29155
+ }
29156
+ ),
29157
+ /* @__PURE__ */ 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 }),
29158
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 flex items-center justify-between", children: [
29159
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
29160
+ /* @__PURE__ */ 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 }),
29161
+ node.type === "video" && node.severity && /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 capitalize mt-0.5 font-medium", children: /* @__PURE__ */ 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: [
29162
+ node.severity,
29163
+ " priority"
29164
+ ] }) })
29165
+ ] }),
29166
+ node.count !== void 0 && node.type === "category" && /* @__PURE__ */ jsx("div", { className: "flex items-center ml-2", children: /* @__PURE__ */ 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 }) })
29167
+ ] })
29168
+ ]
29169
+ }
29170
+ ),
29171
+ hasChildren && isExpanded && /* @__PURE__ */ 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)) })
29172
+ ] }, node.id);
29173
+ };
29174
+ return /* @__PURE__ */ 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: [
29175
+ /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex items-center", children: [
29176
+ /* @__PURE__ */ jsx("div", { className: "mr-3", children: /* @__PURE__ */ jsx(Folder, { className: "h-5 w-5 text-slate-700" }) }),
29177
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-slate-900 tracking-tight", children: "Clips Explorer" }) })
29178
+ ] }) }),
29179
+ /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-slate-100/80", children: /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
29180
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none", children: /* @__PURE__ */ jsx(Search, { className: "h-5 w-5 text-slate-400 group-focus-within:text-blue-500 transition-colors duration-200" }) }),
29181
+ /* @__PURE__ */ jsx(
29182
+ "input",
29183
+ {
29184
+ type: "text",
29185
+ placeholder: "Show me idle clips from 10am",
29186
+ value: searchTerm,
29187
+ onChange: (e) => setSearchTerm(e.target.value),
29188
+ 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"
29189
+ }
29190
+ ),
29191
+ searchTerm && /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 right-0 pr-4 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "h-2 w-2 bg-blue-500 rounded-full animate-pulse" }) })
29192
+ ] }) }),
29193
+ /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 h-[calc(100%-14rem)] overflow-y-auto scrollbar-thin", children: [
29194
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: filterTree.map((node) => renderNode(node)) }),
29195
+ filterTree.length === 0 && searchTerm && /* @__PURE__ */ jsxs("div", { className: "text-center py-12", children: [
29196
+ /* @__PURE__ */ jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsx(Search, { className: "h-12 w-12 mx-auto" }) }),
29197
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clips found" }),
29198
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-500 mb-4", children: [
29199
+ 'No clips match "',
29200
+ searchTerm,
29201
+ '"'
29202
+ ] }),
29203
+ /* @__PURE__ */ jsx(
29204
+ "button",
29205
+ {
29206
+ onClick: () => setSearchTerm(""),
29207
+ 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",
29208
+ children: "Clear search"
29209
+ }
29210
+ )
29211
+ ] })
29212
+ ] })
29213
+ ] });
29214
+ };
29215
+ var USE_SUPABASE_CLIPS2 = true;
27664
29216
  var BottlenecksContent = ({
27665
29217
  workspaceId,
27666
29218
  workspaceName,
@@ -27669,17 +29221,9 @@ var BottlenecksContent = ({
27669
29221
  className
27670
29222
  }) => {
27671
29223
  const dashboardConfig = useDashboardConfig();
27672
- const sopCategories = React19__default.useMemo(() => {
27673
- const sopConfig = dashboardConfig?.s3Config?.sopCategories;
27674
- if (!sopConfig) return null;
27675
- if (sopConfig.workspaceOverrides && sopConfig.workspaceOverrides[workspaceId]) {
27676
- return sopConfig.workspaceOverrides[workspaceId];
27677
- }
27678
- return sopConfig.default;
27679
- }, [dashboardConfig, workspaceId]);
29224
+ const { crop: workspaceCrop} = useWorkspaceCrop(workspaceId);
27680
29225
  const videoRef = useRef(null);
27681
- const timestampFilterRef = useRef(null);
27682
- const initialFilter = sopCategories && sopCategories.length > 0 ? sopCategories[0].id : "low_value";
29226
+ const [initialFilter, setInitialFilter] = useState("");
27683
29227
  const currentIndexRef = useRef(0);
27684
29228
  const activeFilterRef = useRef(initialFilter);
27685
29229
  const isMountedRef = useRef(true);
@@ -27709,22 +29253,6 @@ var BottlenecksContent = ({
27709
29253
  useEffect(() => {
27710
29254
  currentIndexRef.current = currentIndex;
27711
29255
  }, [currentIndex]);
27712
- const [showTimestampFilter, setShowTimestampFilter] = useState(false);
27713
- const [timestampStart, setTimestampStart] = useState("");
27714
- const [timestampEnd, setTimestampEnd] = useState("");
27715
- useEffect(() => {
27716
- const handleClickOutside = (event) => {
27717
- if (timestampFilterRef.current && !timestampFilterRef.current.contains(event.target)) {
27718
- setShowTimestampFilter(false);
27719
- }
27720
- };
27721
- if (showTimestampFilter) {
27722
- document.addEventListener("mousedown", handleClickOutside);
27723
- }
27724
- return () => {
27725
- document.removeEventListener("mousedown", handleClickOutside);
27726
- };
27727
- }, [showTimestampFilter]);
27728
29256
  const s3ClipsService = useMemo(() => {
27729
29257
  if (!dashboardConfig?.s3Config) {
27730
29258
  console.warn("S3 configuration not found in dashboard config");
@@ -27732,6 +29260,39 @@ var BottlenecksContent = ({
27732
29260
  }
27733
29261
  return videoPrefetchManager.getS3Service(dashboardConfig);
27734
29262
  }, [dashboardConfig]);
29263
+ const {
29264
+ clipTypes,
29265
+ isLoading: clipTypesLoading,
29266
+ error: clipTypesError,
29267
+ counts: dynamicCounts
29268
+ } = useClipTypesWithCounts(
29269
+ workspaceId,
29270
+ date || getOperationalDate(),
29271
+ shift || "0"
29272
+ // Use shift || '0' as in the original working code
29273
+ );
29274
+ console.log("[BottlenecksContent] Clip types data:", {
29275
+ clipTypes,
29276
+ clipTypesLength: clipTypes?.length,
29277
+ clipTypesLoading,
29278
+ clipTypesError,
29279
+ dynamicCounts,
29280
+ workspaceId,
29281
+ date: date || getOperationalDate(),
29282
+ shift: shift || "0",
29283
+ USE_SUPABASE_CLIPS: USE_SUPABASE_CLIPS2
29284
+ });
29285
+ useEffect(() => {
29286
+ if (clipTypes.length > 0 && !initialFilter) {
29287
+ const firstWithCounts = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29288
+ const firstType = firstWithCounts || clipTypes[0];
29289
+ if (firstType) {
29290
+ setInitialFilter(firstType.type);
29291
+ setActiveFilter(firstType.type);
29292
+ activeFilterRef.current = firstType.type;
29293
+ }
29294
+ }
29295
+ }, [clipTypes, dynamicCounts, initialFilter]);
27735
29296
  const effectiveShift = useMemo(() => {
27736
29297
  if (shift !== void 0 && shift !== null) {
27737
29298
  const shiftStr = shift.toString();
@@ -27750,6 +29311,9 @@ var BottlenecksContent = ({
27750
29311
  return currentShift.shiftId.toString();
27751
29312
  }
27752
29313
  }, [shift, date, dashboardConfig]);
29314
+ const mergedCounts = useMemo(() => {
29315
+ return { ...clipCounts, ...dynamicCounts };
29316
+ }, [clipCounts, dynamicCounts]);
27753
29317
  const {
27754
29318
  data: prefetchData,
27755
29319
  isFullyIndexed,
@@ -27784,17 +29348,22 @@ var BottlenecksContent = ({
27784
29348
  setHasInitialLoad(true);
27785
29349
  return;
27786
29350
  }
27787
- console.log(`[BottlenecksContent] Fetching clip counts from S3`);
29351
+ console.log(`[BottlenecksContent] Fetching clip counts directly with params:`, {
29352
+ workspaceId,
29353
+ operationalDate,
29354
+ shiftStr
29355
+ });
27788
29356
  const fullResult = await s3ClipsService.getClipCounts(
27789
29357
  workspaceId,
27790
29358
  operationalDate,
27791
29359
  shiftStr
27792
29360
  // Don't build index - use pagination for cost efficiency
27793
29361
  );
29362
+ console.log(`[BottlenecksContent] Direct fetch result:`, fullResult);
27794
29363
  if (fullResult) {
27795
29364
  const counts = fullResult;
27796
29365
  updateClipCounts(counts);
27797
- console.log(`[BottlenecksContent] Fetched and cached clip counts`);
29366
+ console.log(`[BottlenecksContent] Updated clip counts:`, counts);
27798
29367
  }
27799
29368
  setIsLoading(false);
27800
29369
  setHasInitialLoad(true);
@@ -27815,13 +29384,7 @@ var BottlenecksContent = ({
27815
29384
  const ensureVideosLoaded = useCallback(async (centerIndex) => {
27816
29385
  if (!s3ClipsService || !workspaceId || !isMountedRef.current) return;
27817
29386
  const currentFilter = activeFilterRef.current;
27818
- let effectiveFilter = currentFilter;
27819
- if (sopCategories && sopCategories.length > 0) {
27820
- const category = sopCategories.find((cat) => cat.id === currentFilter);
27821
- if (category && category.s3FolderName) {
27822
- effectiveFilter = category.s3FolderName;
27823
- }
27824
- }
29387
+ const effectiveFilter = currentFilter;
27825
29388
  const cacheKey = `${effectiveFilter}:${date}:${shift}`;
27826
29389
  if (!loadedVideosMapRef.current.has(cacheKey)) {
27827
29390
  loadedVideosMapRef.current.set(cacheKey, /* @__PURE__ */ new Set());
@@ -27830,7 +29393,7 @@ var BottlenecksContent = ({
27830
29393
  const indicesToLoad = [];
27831
29394
  const rangeBefore = 1;
27832
29395
  const rangeAfter = 3;
27833
- for (let i = Math.max(0, centerIndex - rangeBefore); i <= Math.min(clipCounts[effectiveFilter] - 1, centerIndex + rangeAfter); i++) {
29396
+ for (let i = Math.max(0, centerIndex - rangeBefore); i <= Math.min(mergedCounts[effectiveFilter] - 1, centerIndex + rangeAfter); i++) {
27834
29397
  if (!loadedIndices.has(i) && !loadingVideosRef.current.has(i)) {
27835
29398
  indicesToLoad.push(i);
27836
29399
  }
@@ -27905,7 +29468,7 @@ var BottlenecksContent = ({
27905
29468
  } finally {
27906
29469
  indicesToLoad.forEach((idx) => loadingVideosRef.current.delete(idx));
27907
29470
  }
27908
- }, [s3ClipsService, workspaceId, clipCounts, sopCategories, date, effectiveShift]);
29471
+ }, [s3ClipsService, workspaceId, clipCounts, date, effectiveShift]);
27909
29472
  const loadFirstVideoForCategory = useCallback(async (category) => {
27910
29473
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
27911
29474
  const targetCategory = category || activeFilterRef.current;
@@ -27922,7 +29485,7 @@ var BottlenecksContent = ({
27922
29485
  try {
27923
29486
  const operationalDate = date || getOperationalDate();
27924
29487
  const shiftStr = effectiveShift;
27925
- if (!clipCounts[targetCategory]) {
29488
+ if (!mergedCounts[targetCategory]) {
27926
29489
  const cacheKey = `clip-counts:${workspaceId}:${operationalDate}:${shiftStr}`;
27927
29490
  const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
27928
29491
  if (cachedResult && cachedResult.counts[targetCategory] > 0) {
@@ -27958,7 +29521,7 @@ var BottlenecksContent = ({
27958
29521
  } catch (directError) {
27959
29522
  console.warn(`[BottlenecksContent] Direct S3 loading failed, trying video index:`, directError);
27960
29523
  }
27961
- if (clipCounts[targetCategory] > 0) {
29524
+ if (mergedCounts[targetCategory] > 0) {
27962
29525
  try {
27963
29526
  const operationalDate2 = date || getOperationalDate();
27964
29527
  const shiftStr2 = effectiveShift;
@@ -28012,47 +29575,27 @@ var BottlenecksContent = ({
28012
29575
  }
28013
29576
  }, [workspaceId, date, effectiveShift, s3ClipsService, fetchClipCounts, updateClipCounts, prefetchData]);
28014
29577
  useEffect(() => {
28015
- if (prefetchData && prefetchData.counts) {
28016
- console.log(`[BottlenecksContent] Received prefetch update - status: ${prefetchStatus}`);
28017
- updateClipCounts(prefetchData.counts);
28018
- if (!hasInitialLoad) {
28019
- setIsLoading(false);
28020
- setHasInitialLoad(true);
29578
+ console.log(`[BottlenecksContent] prefetchData update:`, {
29579
+ hasPrefetchData: !!prefetchData,
29580
+ prefetchStatus,
29581
+ prefetchData
29582
+ });
29583
+ if (prefetchData) {
29584
+ const counts = prefetchData.counts || prefetchData;
29585
+ console.log(`[BottlenecksContent] Extracted counts from prefetch:`, counts);
29586
+ if (counts && Object.keys(counts).length > 0) {
29587
+ updateClipCounts(counts);
29588
+ if (!hasInitialLoad) {
29589
+ setIsLoading(false);
29590
+ setHasInitialLoad(true);
29591
+ }
28021
29592
  }
28022
29593
  }
28023
29594
  }, [prefetchData, prefetchStatus, updateClipCounts, hasInitialLoad]);
28024
29595
  useEffect(() => {
28025
- if (s3ClipsService && clipCounts[activeFilter] > 0) {
29596
+ if (s3ClipsService && (mergedCounts[activeFilter] || 0) > 0) {
28026
29597
  const hasVideosForCurrentFilter = allVideos.some((video) => {
28027
- if (sopCategories && sopCategories.length > 0) {
28028
- const selectedCategory = sopCategories.find((cat) => cat.id === activeFilter);
28029
- if (selectedCategory) {
28030
- return video.type === selectedCategory.id;
28031
- }
28032
- }
28033
- if (activeFilter === "all") return true;
28034
- if (activeFilter === "low_value") {
28035
- return video.type === "low_value";
28036
- }
28037
- if (activeFilter === "idle_time") {
28038
- return video.type === "idle_time";
28039
- }
28040
- if (activeFilter === "sop_deviations") {
28041
- return video.type === "missing_quality_check";
28042
- }
28043
- if (activeFilter === "best_cycle_time") {
28044
- return video.type === "best_cycle_time";
28045
- }
28046
- if (activeFilter === "worst_cycle_time") {
28047
- return video.type === "worst_cycle_time";
28048
- }
28049
- if (activeFilter === "cycle_completion") {
28050
- return video.type === "cycle_completion";
28051
- }
28052
- if (activeFilter === "long_cycle_time") {
28053
- return video.type === "long_cycle_time";
28054
- }
28055
- return video.type === "bottleneck" && video.severity === activeFilter;
29598
+ return video.type === activeFilter;
28056
29599
  });
28057
29600
  if (!hasVideosForCurrentFilter) {
28058
29601
  loadFirstVideoForCategory(activeFilter);
@@ -28060,7 +29603,7 @@ var BottlenecksContent = ({
28060
29603
  setIsCategoryLoading(false);
28061
29604
  }
28062
29605
  }
28063
- }, [activeFilter, s3ClipsService, clipCounts, loadFirstVideoForCategory, allVideos, sopCategories]);
29606
+ }, [activeFilter, s3ClipsService, mergedCounts, loadFirstVideoForCategory, allVideos]);
28064
29607
  useEffect(() => {
28065
29608
  if (previousFilterRef.current !== activeFilter) {
28066
29609
  console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
@@ -28072,15 +29615,7 @@ var BottlenecksContent = ({
28072
29615
  previousFilterRef.current = activeFilter;
28073
29616
  const filtered = allVideos.filter((video) => {
28074
29617
  if (activeFilter === "all") return true;
28075
- if (activeFilter === "low_value") return video.type === "low_value";
28076
- if (activeFilter === "sop_deviations") return video.type === "missing_quality_check";
28077
- if (activeFilter === "best_cycle_time") return video.type === "best_cycle_time";
28078
- if (activeFilter === "worst_cycle_time") return video.type === "worst_cycle_time";
28079
- if (activeFilter === "cycle_completion") return video.type === "cycle_completion";
28080
- if (activeFilter === "long_cycle_time") {
28081
- return video.type === "long_cycle_time";
28082
- }
28083
- return video.type === "bottleneck" && video.severity === activeFilter;
29618
+ return video.type === activeFilter;
28084
29619
  });
28085
29620
  if (filtered.length > 0) {
28086
29621
  preloadVideoUrl(filtered[0].src);
@@ -28089,46 +29624,25 @@ var BottlenecksContent = ({
28089
29624
  setIsCategoryLoading(false);
28090
29625
  }
28091
29626
  }, 150);
28092
- } else if (clipCounts[activeFilter] === 0) {
29627
+ } else if ((mergedCounts[activeFilter] || 0) === 0) {
28093
29628
  if (isMountedRef.current) {
28094
29629
  setIsCategoryLoading(false);
28095
29630
  }
28096
29631
  }
28097
29632
  }
28098
- }, [activeFilter, allVideos, clipCounts]);
29633
+ }, [activeFilter, allVideos, mergedCounts]);
28099
29634
  const filteredVideos = useMemo(() => {
28100
29635
  if (!allVideos) return [];
28101
29636
  let filtered = [];
28102
29637
  if (activeFilter === "all") {
28103
29638
  filtered = [...allVideos];
28104
29639
  } else {
28105
- if (sopCategories && sopCategories.length > 0) {
28106
- const selectedCategory = sopCategories.find((cat) => cat.id === activeFilter);
28107
- if (selectedCategory) {
28108
- filtered = allVideos.filter((video) => video.type === selectedCategory.id);
28109
- }
28110
- } else {
28111
- if (activeFilter === "low_value") {
28112
- filtered = allVideos.filter((video) => video.type === "low_value");
28113
- } else if (activeFilter === "sop_deviations") {
28114
- filtered = allVideos.filter((video) => video.type === "missing_quality_check");
28115
- } else if (activeFilter === "best_cycle_time") {
28116
- filtered = allVideos.filter((video) => video.type === "best_cycle_time");
28117
- } else if (activeFilter === "worst_cycle_time") {
28118
- filtered = allVideos.filter((video) => video.type === "worst_cycle_time");
28119
- } else if (activeFilter === "cycle_completion") {
28120
- filtered = allVideos.filter((video) => video.type === "cycle_completion");
28121
- } else if (activeFilter === "long_cycle_time") {
28122
- filtered = allVideos.filter((video) => video.type === "long_cycle_time");
28123
- } else {
28124
- filtered = allVideos.filter((video) => video.type === "bottleneck" && video.severity === activeFilter);
28125
- }
28126
- }
29640
+ filtered = allVideos.filter((video) => video.type === activeFilter);
28127
29641
  }
28128
29642
  return filtered.sort((a, b) => {
28129
29643
  return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
28130
29644
  });
28131
- }, [activeFilter, allVideos, sopCategories]);
29645
+ }, [activeFilter, allVideos]);
28132
29646
  useEffect(() => {
28133
29647
  if (isNavigating && currentIndex < filteredVideos.length) {
28134
29648
  setIsNavigating(false);
@@ -28153,15 +29667,9 @@ var BottlenecksContent = ({
28153
29667
  const currentIdx = currentIndexRef.current;
28154
29668
  const currentFilter = activeFilterRef.current;
28155
29669
  const nextIndex = currentIdx + 1;
28156
- let effectiveFilter = currentFilter;
28157
- if (sopCategories && sopCategories.length > 0) {
28158
- const category = sopCategories.find((cat) => cat.id === currentFilter);
28159
- if (category && category.s3FolderName) {
28160
- effectiveFilter = category.s3FolderName;
28161
- }
28162
- }
28163
- console.log(`[handleNext] Navigation check: nextIndex=${nextIndex}, currentFilter='${currentFilter}', effectiveFilter='${effectiveFilter}', clipCounts[effectiveFilter]=${clipCounts[effectiveFilter]}, clipCounts keys:`, Object.keys(clipCounts));
28164
- if (nextIndex < clipCounts[effectiveFilter]) {
29670
+ const effectiveFilter = currentFilter;
29671
+ console.log(`[handleNext] Navigation check: nextIndex=${nextIndex}, currentFilter='${currentFilter}', effectiveFilter='${effectiveFilter}', mergedCounts[effectiveFilter]=${mergedCounts[effectiveFilter]}, mergedCounts keys:`, Object.keys(mergedCounts));
29672
+ if (nextIndex < mergedCounts[effectiveFilter]) {
28165
29673
  if (isMountedRef.current) {
28166
29674
  setCurrentIndex(nextIndex);
28167
29675
  setError(null);
@@ -28315,17 +29823,6 @@ var BottlenecksContent = ({
28315
29823
  player.pause();
28316
29824
  }
28317
29825
  };
28318
- const mappedCounts = useMemo(() => {
28319
- const counts = { ...clipCounts };
28320
- counts.lowValue = counts.low_value || counts.lowValue || 0;
28321
- counts.bestCycleTimes = counts.best_cycle_time || counts.bestCycleTimes || 0;
28322
- counts.worstCycleTimes = counts.worst_cycle_time || counts.worstCycleTimes || 0;
28323
- counts.longCycleTimes = counts.long_cycle_time || counts.longCycleTimes || 0;
28324
- counts.cycleCompletions = counts.cycle_completion || counts.cycleCompletion || 0;
28325
- counts.sopDeviations = counts.missing_quality_check || counts.sopDeviations || 0;
28326
- counts.bottlenecks = counts.bottleneck || counts.bottlenecks || 0;
28327
- return counts;
28328
- }, [clipCounts]);
28329
29826
  const getClipTypeLabel = (video) => {
28330
29827
  if (!video) return "";
28331
29828
  switch (video.type) {
@@ -28348,33 +29845,6 @@ var BottlenecksContent = ({
28348
29845
  return "";
28349
29846
  }
28350
29847
  };
28351
- const getColorClasses = (color2) => {
28352
- const colorMap = {
28353
- purple: { text: "text-purple-500", bg: "bg-purple-500", dot: "bg-purple-500" },
28354
- green: { text: "text-green-600", bg: "bg-green-600", dot: "bg-green-600" },
28355
- red: { text: "text-red-700", bg: "bg-red-700", dot: "bg-red-700" },
28356
- "red-dark": { text: "text-red-500", bg: "bg-red-500", dot: "bg-red-500" },
28357
- blue: { text: "text-blue-600", bg: "bg-blue-600", dot: "bg-blue-600" },
28358
- orange: { text: "text-orange-600", bg: "bg-orange-600", dot: "bg-orange-600" },
28359
- yellow: { text: "text-yellow-600", bg: "bg-yellow-600", dot: "bg-yellow-600" },
28360
- teal: { text: "text-teal-600", bg: "bg-teal-600", dot: "bg-teal-600" },
28361
- amber: { text: "text-amber-600", bg: "bg-amber-600", dot: "bg-amber-600" },
28362
- gray: { text: "text-gray-600", bg: "bg-gray-600", dot: "bg-gray-600" }
28363
- };
28364
- return colorMap[color2] || colorMap.gray;
28365
- };
28366
- const formatTimeOnly = (time2) => {
28367
- if (!time2) return "";
28368
- try {
28369
- const [hours, minutes] = time2.split(":");
28370
- const hour = parseInt(hours);
28371
- const ampm = hour >= 12 ? "PM" : "AM";
28372
- const displayHour = hour % 12 || 12;
28373
- return `${displayHour}:${minutes} ${ampm}`;
28374
- } catch {
28375
- return time2;
28376
- }
28377
- };
28378
29848
  if (!dashboardConfig?.s3Config) {
28379
29849
  return /* @__PURE__ */ jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
28380
29850
  /* @__PURE__ */ jsx(XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
@@ -28382,202 +29852,80 @@ var BottlenecksContent = ({
28382
29852
  /* @__PURE__ */ jsx("p", { className: "text-gray-600 max-w-md", children: "S3 configuration is required to load video clips. Please check your dashboard configuration." })
28383
29853
  ] });
28384
29854
  }
28385
- if (isLoading && allVideos.length === 0 && Object.keys(clipCounts).length === 0) {
29855
+ if ((isLoading || clipTypesLoading) && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
28386
29856
  return /* @__PURE__ */ jsx("div", { className: "flex-grow p-4 flex items-center justify-center h-[calc(100vh-12rem)]", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading clips..." }) });
28387
29857
  }
28388
- if (error) {
29858
+ if (error || clipTypesError) {
28389
29859
  return /* @__PURE__ */ jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
28390
29860
  /* @__PURE__ */ jsx(XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
28391
29861
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-red-700 mb-1", children: "Error Loading Clips" }),
28392
- /* @__PURE__ */ jsx("p", { className: "text-gray-600 max-w-md", children: error })
29862
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 max-w-md", children: error || clipTypesError })
28393
29863
  ] });
28394
29864
  }
28395
- const categoriesToShow = sopCategories && sopCategories.length > 0 ? sopCategories : [
28396
- // Default hardcoded categories if no configuration
28397
- { id: "low_value", label: "Idle Moments", color: "purple", subtitle: "Idle time detected" },
28398
- { id: "best_cycle_time", label: "Best Cycle Time", color: "green", subtitle: "Fastest cycle today" },
28399
- { id: "worst_cycle_time", label: "Worst Cycle Time", color: "red", subtitle: "Slowest cycle today" },
28400
- { id: "long_cycle_time", label: "Long Cycle Time", color: "red-dark", subtitle: "Above standard cycle times" },
28401
- { id: "cycle_completion", label: "Cycle Completions", color: "blue", subtitle: "Completed production cycles" }
28402
- ];
28403
- return /* @__PURE__ */ jsxs("div", { className: "flex-grow p-1.5 sm:p-2 lg:p-4 h-[calc(100vh-12rem)]", children: [
28404
- /* @__PURE__ */ 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) => {
28405
- const colorClasses = getColorClasses(category.color);
28406
- const count = mappedCounts[category.id] || 0;
28407
- return /* @__PURE__ */ jsxs(
28408
- Card2,
28409
- {
28410
- onClick: () => {
28411
- updateActiveFilter(category.id);
28412
- trackCoreEvent(`${category.label} Filter Clicked`, {
28413
- workspaceId,
28414
- workspaceName,
28415
- date,
28416
- filterType: category.id,
28417
- clipCount: count
28418
- });
28419
- },
28420
- 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" : ""}`,
28421
- "aria-label": `Filter by ${category.label} (${count} clips)`,
28422
- role: "button",
28423
- tabIndex: 0,
28424
- onKeyDown: (e) => e.key === "Enter" && updateActiveFilter(category.id),
28425
- children: [
28426
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2", children: /* @__PURE__ */ jsx(CardTitle2, { className: `text-lg ${activeFilter === category.id ? "text-blue-600" : ""}`, children: category.label }) }),
28427
- /* @__PURE__ */ jsx(CardContent2, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center", children: [
28428
- /* @__PURE__ */ jsx("p", { className: `text-3xl font-bold ${colorClasses.text}`, children: count }),
28429
- /* @__PURE__ */ jsxs("div", { className: "flex items-center text-sm text-gray-500 mt-1", children: [
28430
- /* @__PURE__ */ jsx("div", { className: `h-2 w-2 rounded-full ${colorClasses.dot} mr-1.5` }),
28431
- /* @__PURE__ */ jsx("span", { children: category.subtitle || category.description || category.label })
28432
- ] })
28433
- ] }) })
28434
- ]
28435
- },
28436
- category.id
28437
- );
28438
- }) }),
28439
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-sm overflow-hidden", style: { height: "calc(100% - 8.5rem)" }, children: [
29865
+ const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
29866
+ console.log("[BottlenecksContent] Categories to show:", {
29867
+ categoriesToShow,
29868
+ categoriesToShowLength: categoriesToShow?.length,
29869
+ firstCategory: categoriesToShow?.[0],
29870
+ clipTypesLength: clipTypes?.length,
29871
+ clipTypesError,
29872
+ mergedCounts
29873
+ });
29874
+ return /* @__PURE__ */ jsx("div", { className: "flex-grow p-1.5 sm:p-2 lg:p-4 h-[calc(100vh-12rem)]", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col lg:flex-row gap-4 h-full", children: [
29875
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 lg:flex-[3]", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-sm overflow-hidden h-full", children: [
28440
29876
  /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-gray-100", children: /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
28441
29877
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-800", children: (() => {
28442
29878
  if (activeFilter === "all") {
28443
- return `All Clips (${mappedCounts.total || 0})`;
29879
+ return `All Clips (${mergedCounts.total || 0})`;
28444
29880
  }
28445
- const activeCategory = categoriesToShow.find((cat) => cat.id === activeFilter);
29881
+ const activeCategory = categoriesToShow.find((cat) => cat.type === activeFilter);
28446
29882
  if (activeCategory) {
28447
- return `${activeCategory.label} (${mappedCounts[activeCategory.id] || 0})`;
29883
+ return `${activeCategory.label} (${mergedCounts[activeCategory.type] || 0})`;
28448
29884
  }
28449
- 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})`;
29885
+ return `${activeFilter.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} (${mergedCounts[activeFilter] || 0})`;
28450
29886
  })() }),
28451
29887
  /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
28452
- /* @__PURE__ */ jsxs("div", { className: "relative", ref: timestampFilterRef, children: [
28453
- /* @__PURE__ */ jsx(
28454
- "button",
28455
- {
28456
- onClick: () => setShowTimestampFilter(!showTimestampFilter),
28457
- 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"}`,
28458
- "aria-label": "Filter by time",
28459
- title: "Filter by time",
28460
- children: /* @__PURE__ */ jsx(Clock, { className: "h-5 w-5" })
28461
- }
28462
- ),
28463
- showTimestampFilter && /* @__PURE__ */ 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: [
28464
- /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Filter by Time" }),
28465
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
28466
- /* @__PURE__ */ jsxs("div", { children: [
28467
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-600 mb-1", children: "Start Time" }),
28468
- /* @__PURE__ */ jsx(
28469
- TimePickerDropdown,
28470
- {
28471
- value: timestampStart,
28472
- onChange: (value) => setTimestampStart(value),
28473
- placeholder: "Select start time",
28474
- className: "w-full text-sm"
28475
- }
28476
- )
28477
- ] }),
28478
- /* @__PURE__ */ jsxs("div", { children: [
28479
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-600 mb-1", children: "End Time" }),
28480
- /* @__PURE__ */ jsx(
28481
- TimePickerDropdown,
28482
- {
28483
- value: timestampEnd,
28484
- onChange: (value) => setTimestampEnd(value),
28485
- placeholder: "Select end time",
28486
- className: "w-full text-sm"
28487
- }
28488
- )
28489
- ] }),
28490
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between pt-2", children: [
28491
- /* @__PURE__ */ jsx(
28492
- "button",
28493
- {
28494
- onClick: () => {
28495
- setTimestampStart("");
28496
- setTimestampEnd("");
28497
- setShowTimestampFilter(false);
28498
- },
28499
- className: "px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 transition-colors",
28500
- children: "Clear"
28501
- }
28502
- ),
28503
- /* @__PURE__ */ jsx(
28504
- "button",
28505
- {
28506
- onClick: () => {
28507
- setShowTimestampFilter(false);
28508
- loadFirstVideoForCategory();
28509
- },
28510
- className: "px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
28511
- children: "Apply Filter"
28512
- }
28513
- )
28514
- ] })
28515
- ] })
28516
- ] })
28517
- ] }),
28518
29888
  /* @__PURE__ */ jsx(
28519
29889
  "button",
28520
29890
  {
28521
29891
  onClick: handlePrevious,
28522
- disabled: currentIndex === 0 || clipCounts[activeFilter] === 0,
28523
- 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"}`,
29892
+ disabled: currentIndex === 0 || (mergedCounts[activeFilter] || 0) === 0,
29893
+ 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"}`,
28524
29894
  "aria-label": "Previous video",
28525
29895
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
28526
29896
  }
28527
29897
  ),
28528
29898
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
28529
- /* @__PURE__ */ 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" }),
29899
+ /* @__PURE__ */ 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" }),
28530
29900
  error && /* @__PURE__ */ jsx("span", { className: "text-xs text-red-600 font-medium", children: error })
28531
29901
  ] }),
28532
29902
  /* @__PURE__ */ jsx(
28533
29903
  "button",
28534
29904
  {
28535
29905
  onClick: handleNext,
28536
- disabled: currentIndex >= clipCounts[activeFilter] - 1 || clipCounts[activeFilter] === 0,
28537
- 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"}`,
29906
+ disabled: currentIndex >= (mergedCounts[activeFilter] || 0) - 1 || (mergedCounts[activeFilter] || 0) === 0,
29907
+ 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"}`,
28538
29908
  "aria-label": "Next video",
28539
29909
  children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
28540
29910
  }
28541
29911
  )
28542
29912
  ] })
28543
29913
  ] }) }),
28544
- (timestampStart || timestampEnd) && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-blue-50 border-b border-blue-100 flex items-center justify-between", children: [
28545
- /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2 text-sm text-blue-700", children: [
28546
- /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4" }),
28547
- /* @__PURE__ */ jsxs("span", { children: [
28548
- "Filtered by time: ",
28549
- timestampStart ? formatTimeOnly(timestampStart) : "Any",
28550
- " - ",
28551
- timestampEnd ? formatTimeOnly(timestampEnd) : "Any"
28552
- ] })
28553
- ] }),
28554
- /* @__PURE__ */ jsx(
28555
- "button",
28556
- {
28557
- onClick: () => {
28558
- setTimestampStart("");
28559
- setTimestampEnd("");
28560
- loadFirstVideoForCategory();
28561
- },
28562
- className: "text-sm text-blue-600 hover:text-blue-800 transition-colors",
28563
- children: "Clear filter"
28564
- }
28565
- )
28566
- ] }),
28567
29914
  /* Priority 1: Show loading if initial load hasn't completed yet */
28568
29915
  isLoading && !hasInitialLoad ? /* @__PURE__ */ jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading clips..." }) }) }) }) : (
28569
29916
  /* Priority 2: Show loading if category is loading BUT only if no video is available */
28570
29917
  isCategoryLoading && (!filteredVideos.length || !currentVideo) ? /* @__PURE__ */ jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading videos..." }) }) }) }) : (
28571
29918
  /* Priority 3: Show loading if navigating and current video not available */
28572
- isNavigating || currentIndex >= filteredVideos.length && currentIndex < clipCounts[activeFilter] ? /* @__PURE__ */ jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }) }) }) : (
29919
+ isNavigating || currentIndex >= filteredVideos.length && currentIndex < (mergedCounts[activeFilter] || 0) ? /* @__PURE__ */ jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }) }) }) : (
28573
29920
  /* Priority 4: Show video if we have filtered videos and current video */
28574
29921
  filteredVideos.length > 0 && currentVideo ? /* @__PURE__ */ jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx("div", { className: "relative h-full group", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900", children: [
28575
29922
  /* @__PURE__ */ jsx(
28576
- VideoPlayer,
29923
+ CroppedVideoPlayer,
28577
29924
  {
28578
29925
  ref: videoRef,
28579
29926
  src: currentVideo.src,
28580
29927
  className: "w-full h-full",
29928
+ crop: workspaceCrop?.crop || null,
28581
29929
  autoplay: true,
28582
29930
  playsInline: true,
28583
29931
  loop: false,
@@ -28631,7 +29979,7 @@ var BottlenecksContent = ({
28631
29979
  /* @__PURE__ */ 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` }),
28632
29980
  (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__ */ jsxs("span", { className: "opacity-90 font-mono bg-black/30 px-2 py-0.5 rounded", children: [
28633
29981
  "Cycle time: ",
28634
- (currentVideo.cycle_time_seconds / 20).toFixed(1),
29982
+ currentVideo.cycle_time_seconds.toFixed(1),
28635
29983
  "s"
28636
29984
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
28637
29985
  /* @__PURE__ */ jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
@@ -28668,9 +30016,8 @@ var BottlenecksContent = ({
28668
30016
  max: duration || 0,
28669
30017
  value: currentTime,
28670
30018
  onChange: (e) => {
28671
- const player = videoRef.current?.player;
28672
- if (player) {
28673
- player.currentTime(Number(e.target.value));
30019
+ if (videoRef.current) {
30020
+ videoRef.current.currentTime(Number(e.target.value));
28674
30021
  }
28675
30022
  },
28676
30023
  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",
@@ -28684,13 +30031,13 @@ var BottlenecksContent = ({
28684
30031
  ] }) })
28685
30032
  ] }) }) }) : (
28686
30033
  /* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
28687
- hasInitialLoad && Object.keys(clipCounts).length > 0 && Object.values(clipCounts).every((count) => count === 0) ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxs("div", { className: "text-center p-8", children: [
30034
+ hasInitialLoad && Object.keys(mergedCounts).length > 0 && Object.values(mergedCounts).every((count) => count === 0) ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxs("div", { className: "text-center p-8", children: [
28688
30035
  /* @__PURE__ */ 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__ */ 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" }) }),
28689
30036
  /* @__PURE__ */ jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
28690
30037
  /* @__PURE__ */ jsx("p", { className: "text-gray-500", children: "There were no video clips found for this workspace today." })
28691
30038
  ] }) }) : (
28692
30039
  /* Priority 6: Show "no matching clips" only if we have data loaded and specifically no clips for this filter */
28693
- hasInitialLoad && clipCounts[activeFilter] === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxs("div", { className: "text-center p-8", children: [
30040
+ hasInitialLoad && (mergedCounts[activeFilter] || 0) === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxs("div", { className: "text-center p-8", children: [
28694
30041
  /* @__PURE__ */ 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__ */ 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" }) }),
28695
30042
  /* @__PURE__ */ jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Matching Clips" }),
28696
30043
  /* @__PURE__ */ jsx("p", { className: "text-gray-500", children: "There are no clips matching the selected filter." })
@@ -28703,8 +30050,46 @@ var BottlenecksContent = ({
28703
30050
  )
28704
30051
  )
28705
30052
  )
28706
- ] })
28707
- ] });
30053
+ ] }) }),
30054
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 lg:flex-[1] lg:min-w-[280px] lg:max-w-[320px]", children: /* @__PURE__ */ jsx(
30055
+ FileManagerFilters,
30056
+ {
30057
+ categories: categoriesToShow.map((cat) => ({
30058
+ ...cat,
30059
+ id: cat.type
30060
+ // Map type to id for compatibility with FileManagerFilters
30061
+ })),
30062
+ videos: allVideos || [],
30063
+ activeFilter,
30064
+ currentVideoId: currentVideo?.id,
30065
+ counts: mergedCounts,
30066
+ onFilterChange: (filterId) => {
30067
+ updateActiveFilter(filterId);
30068
+ const category = categoriesToShow.find((cat) => cat.type === filterId);
30069
+ if (category) {
30070
+ trackCoreEvent(`${category.label} Filter Clicked`, {
30071
+ workspaceId,
30072
+ workspaceName,
30073
+ date,
30074
+ filterType: filterId,
30075
+ clipCount: mergedCounts[filterId] || 0
30076
+ });
30077
+ }
30078
+ },
30079
+ onVideoSelect: (videoIndex) => {
30080
+ setCurrentIndex(videoIndex);
30081
+ const selectedVideo = allVideos?.[videoIndex];
30082
+ if (selectedVideo) {
30083
+ const videoCategory = categoriesToShow.find((cat) => cat.type === selectedVideo.type);
30084
+ if (videoCategory && activeFilter !== videoCategory.type) {
30085
+ updateActiveFilter(videoCategory.type);
30086
+ }
30087
+ }
30088
+ },
30089
+ className: "h-full"
30090
+ }
30091
+ ) })
30092
+ ] }) });
28708
30093
  };
28709
30094
  var getEfficiencyColor = (efficiency) => {
28710
30095
  if (efficiency >= 80) {
@@ -30333,6 +31718,17 @@ var SideNavBar = memo(({
30333
31718
  });
30334
31719
  onMobileMenuClose?.();
30335
31720
  }, [navigate, onMobileMenuClose]);
31721
+ const handleSupervisorManagementClick = useCallback(() => {
31722
+ navigate("/supervisor-management", {
31723
+ trackingEvent: {
31724
+ name: "Supervisor Management Page Clicked",
31725
+ properties: {
31726
+ source: "side_nav"
31727
+ }
31728
+ }
31729
+ });
31730
+ onMobileMenuClose?.();
31731
+ }, [navigate, onMobileMenuClose]);
30336
31732
  const handleAIAgentClick = useCallback(() => {
30337
31733
  navigate("/ai-agent", {
30338
31734
  trackingEvent: {
@@ -30397,6 +31793,7 @@ var SideNavBar = memo(({
30397
31793
  const kpisButtonClasses = useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
30398
31794
  const targetsButtonClasses = useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
30399
31795
  const shiftsButtonClasses = useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
31796
+ const supervisorManagementButtonClasses = useMemo(() => getButtonClasses("/supervisor-management"), [getButtonClasses, pathname]);
30400
31797
  const aiAgentButtonClasses = useMemo(() => getButtonClasses("/ai-agent"), [getButtonClasses, pathname]);
30401
31798
  const profileButtonClasses = useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
30402
31799
  const helpButtonClasses = useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
@@ -30502,7 +31899,22 @@ var SideNavBar = memo(({
30502
31899
  ]
30503
31900
  }
30504
31901
  ),
30505
- skuEnabled && /* @__PURE__ */ jsxs(
31902
+ /* @__PURE__ */ jsxs(
31903
+ "button",
31904
+ {
31905
+ onClick: handleSupervisorManagementClick,
31906
+ className: supervisorManagementButtonClasses,
31907
+ "aria-label": "Supervisor Management",
31908
+ tabIndex: 0,
31909
+ role: "tab",
31910
+ "aria-selected": pathname === "/supervisor-management" || pathname.startsWith("/supervisor-management/"),
31911
+ children: [
31912
+ /* @__PURE__ */ jsx(UsersIcon, { className: "w-5 h-5 mb-1" }),
31913
+ /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight", children: "Supervisors" })
31914
+ ]
31915
+ }
31916
+ ),
31917
+ skuEnabled && true && /* @__PURE__ */ jsxs(
30506
31918
  "button",
30507
31919
  {
30508
31920
  onClick: handleSKUsClick,
@@ -30528,7 +31940,10 @@ var SideNavBar = memo(({
30528
31940
  "aria-selected": pathname === "/ai-agent" || pathname.startsWith("/ai-agent/"),
30529
31941
  children: [
30530
31942
  /* @__PURE__ */ jsx(SparklesIcon, { className: "w-5 h-5 mb-1" }),
30531
- /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight", children: "Axel" })
31943
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
31944
+ /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight", children: "Axel" }),
31945
+ /* @__PURE__ */ 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" })
31946
+ ] })
30532
31947
  ]
30533
31948
  }
30534
31949
  ),
@@ -30662,7 +32077,19 @@ var SideNavBar = memo(({
30662
32077
  ]
30663
32078
  }
30664
32079
  ),
30665
- skuEnabled && /* @__PURE__ */ jsxs(
32080
+ /* @__PURE__ */ jsxs(
32081
+ "button",
32082
+ {
32083
+ onClick: handleMobileNavClick(handleSupervisorManagementClick),
32084
+ className: getMobileButtonClass("/supervisor-management"),
32085
+ "aria-label": "Supervisor Management",
32086
+ children: [
32087
+ /* @__PURE__ */ jsx(UsersIcon, { className: getIconClass("/supervisor-management") }),
32088
+ /* @__PURE__ */ jsx("span", { className: "text-base font-medium", children: "Supervisors" })
32089
+ ]
32090
+ }
32091
+ ),
32092
+ skuEnabled && true && /* @__PURE__ */ jsxs(
30666
32093
  "button",
30667
32094
  {
30668
32095
  onClick: handleMobileNavClick(handleSKUsClick),
@@ -30682,7 +32109,10 @@ var SideNavBar = memo(({
30682
32109
  "aria-label": "AI Manufacturing Expert",
30683
32110
  children: [
30684
32111
  /* @__PURE__ */ jsx(SparklesIcon, { className: getIconClass("/ai-agent") }),
30685
- /* @__PURE__ */ jsx("span", { className: "text-base font-medium", children: "Axel AI" })
32112
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
32113
+ /* @__PURE__ */ jsx("span", { className: "text-base font-medium", children: "Axel AI" }),
32114
+ /* @__PURE__ */ 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" })
32115
+ ] })
30686
32116
  ]
30687
32117
  }
30688
32118
  ),
@@ -31201,6 +32631,1205 @@ var SingleVideoStream = ({
31201
32631
  );
31202
32632
  };
31203
32633
  var SingleVideoStream_default = SingleVideoStream;
32634
+ var SupervisorDropdown = ({
32635
+ selectedSupervisor,
32636
+ availableSupervisors,
32637
+ onSelect,
32638
+ className = "",
32639
+ disabled = false,
32640
+ placeholder = "Select Supervisor"
32641
+ }) => {
32642
+ console.log(`[SupervisorDropdown] Rendered with ${availableSupervisors.length} available supervisors:`, availableSupervisors.map((s) => ({ id: s.id, name: s.name })));
32643
+ const [isOpen, setIsOpen] = useState(false);
32644
+ const [searchTerm, setSearchTerm] = useState("");
32645
+ const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 });
32646
+ const dropdownRef = useRef(null);
32647
+ const buttonRef = useRef(null);
32648
+ const inputRef = useRef(null);
32649
+ const filteredSupervisors = availableSupervisors.filter(
32650
+ (supervisor) => supervisor.name.toLowerCase().includes(searchTerm.toLowerCase()) || supervisor.email && supervisor.email.toLowerCase().includes(searchTerm.toLowerCase())
32651
+ );
32652
+ const calculatePosition = () => {
32653
+ if (buttonRef.current) {
32654
+ const rect = buttonRef.current.getBoundingClientRect();
32655
+ setDropdownPosition({
32656
+ top: rect.bottom + window.scrollY + 4,
32657
+ left: rect.left + window.scrollX,
32658
+ width: rect.width
32659
+ });
32660
+ }
32661
+ };
32662
+ useEffect(() => {
32663
+ const handleClickOutside = (event) => {
32664
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target) && buttonRef.current && !buttonRef.current.contains(event.target)) {
32665
+ setIsOpen(false);
32666
+ setSearchTerm("");
32667
+ }
32668
+ };
32669
+ document.addEventListener("mousedown", handleClickOutside);
32670
+ return () => document.removeEventListener("mousedown", handleClickOutside);
32671
+ }, []);
32672
+ const handleSelect = (supervisor) => {
32673
+ onSelect(supervisor);
32674
+ setIsOpen(false);
32675
+ setSearchTerm("");
32676
+ };
32677
+ const handleKeyDown = (e) => {
32678
+ if (e.key === "Escape") {
32679
+ setIsOpen(false);
32680
+ setSearchTerm("");
32681
+ }
32682
+ };
32683
+ const handleToggle = () => {
32684
+ if (!disabled) {
32685
+ if (!isOpen) {
32686
+ calculatePosition();
32687
+ }
32688
+ setIsOpen(!isOpen);
32689
+ }
32690
+ };
32691
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
32692
+ /* @__PURE__ */ jsxs(
32693
+ "button",
32694
+ {
32695
+ ref: buttonRef,
32696
+ type: "button",
32697
+ onClick: handleToggle,
32698
+ className: cn(
32699
+ "w-full flex items-center justify-between px-3 py-2 text-left bg-white border rounded-md shadow-sm transition-colors",
32700
+ 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",
32701
+ isOpen && !disabled && "ring-2 ring-blue-500 border-blue-500"
32702
+ ),
32703
+ disabled,
32704
+ children: [
32705
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [
32706
+ /* @__PURE__ */ jsx(User, { className: "w-4 h-4 text-gray-400 mr-2 flex-shrink-0" }),
32707
+ /* @__PURE__ */ jsx("span", { className: cn(
32708
+ "truncate",
32709
+ selectedSupervisor ? "text-gray-900" : "text-gray-500"
32710
+ ), children: selectedSupervisor ? selectedSupervisor.name : placeholder })
32711
+ ] }),
32712
+ /* @__PURE__ */ jsx(ChevronDown, { className: cn(
32713
+ "w-4 h-4 text-gray-400 transition-transform flex-shrink-0 ml-2",
32714
+ isOpen ? "rotate-180" : ""
32715
+ ) })
32716
+ ]
32717
+ }
32718
+ ),
32719
+ isOpen && !disabled && typeof document !== "undefined" && createPortal(
32720
+ /* @__PURE__ */ jsxs(
32721
+ "div",
32722
+ {
32723
+ ref: dropdownRef,
32724
+ className: "fixed z-50 bg-white border border-gray-300 rounded-md shadow-lg",
32725
+ style: {
32726
+ top: dropdownPosition.top,
32727
+ left: dropdownPosition.left,
32728
+ width: dropdownPosition.width,
32729
+ minWidth: "200px"
32730
+ },
32731
+ children: [
32732
+ /* @__PURE__ */ jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsx(
32733
+ "input",
32734
+ {
32735
+ ref: inputRef,
32736
+ type: "text",
32737
+ placeholder: "Search supervisors...",
32738
+ value: searchTerm,
32739
+ onChange: (e) => setSearchTerm(e.target.value),
32740
+ onKeyDown: handleKeyDown,
32741
+ 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",
32742
+ autoFocus: true
32743
+ }
32744
+ ) }),
32745
+ /* @__PURE__ */ jsxs("div", { className: "max-h-60 overflow-y-auto", children: [
32746
+ selectedSupervisor && /* @__PURE__ */ jsxs(Fragment, { children: [
32747
+ /* @__PURE__ */ jsxs(
32748
+ "button",
32749
+ {
32750
+ type: "button",
32751
+ onClick: () => handleSelect(null),
32752
+ 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",
32753
+ children: [
32754
+ /* @__PURE__ */ jsx("div", { className: "w-4 h-4 mr-2" }),
32755
+ " ",
32756
+ "Clear Assignment"
32757
+ ]
32758
+ }
32759
+ ),
32760
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200" })
32761
+ ] }),
32762
+ filteredSupervisors.length > 0 ? filteredSupervisors.map((supervisor) => /* @__PURE__ */ jsxs(
32763
+ "button",
32764
+ {
32765
+ type: "button",
32766
+ onClick: () => handleSelect(supervisor),
32767
+ className: cn(
32768
+ "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",
32769
+ selectedSupervisor?.id === supervisor.id && "bg-blue-50 text-blue-700"
32770
+ ),
32771
+ children: [
32772
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [
32773
+ /* @__PURE__ */ jsx(User, { className: "w-4 h-4 text-gray-400 mr-2 flex-shrink-0" }),
32774
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
32775
+ /* @__PURE__ */ jsx("div", { className: "font-medium truncate", children: supervisor.name }),
32776
+ supervisor.email && /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500 truncate", children: supervisor.email })
32777
+ ] })
32778
+ ] }),
32779
+ selectedSupervisor?.id === supervisor.id && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
32780
+ ]
32781
+ },
32782
+ supervisor.id
32783
+ )) : /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: searchTerm ? "No supervisors found" : "No supervisors available" })
32784
+ ] })
32785
+ ]
32786
+ }
32787
+ ),
32788
+ document.body
32789
+ )
32790
+ ] });
32791
+ };
32792
+ var SupervisorDropdown_default = SupervisorDropdown;
32793
+
32794
+ // src/lib/hooks/useFirstTimeLogin.ts
32795
+ var useFirstTimeLogin = () => {
32796
+ const { user, markFirstLoginCompleted } = useAuth();
32797
+ const hasCompletedFirstLogin = user?.first_login_completed ?? false;
32798
+ const needsOnboarding = user ? !hasCompletedFirstLogin : false;
32799
+ const completeFirstLogin = async () => {
32800
+ return await markFirstLoginCompleted();
32801
+ };
32802
+ return {
32803
+ isFirstTimeLogin: !hasCompletedFirstLogin,
32804
+ // Simple boolean check
32805
+ hasCompletedFirstLogin,
32806
+ needsOnboarding,
32807
+ completeFirstLogin,
32808
+ user
32809
+ };
32810
+ };
32811
+ var SimpleOnboardingPopup = ({
32812
+ onComplete,
32813
+ isCompleting = false
32814
+ }) => {
32815
+ const [currentStep, setCurrentStep] = useState(0);
32816
+ const steps = [
32817
+ {
32818
+ title: "Welcome to Optifye Dashboard! \u{1F389}",
32819
+ content: "We're excited to have you here. This dashboard helps you monitor and optimize your factory operations in real-time.",
32820
+ icon: "\u{1F44B}"
32821
+ },
32822
+ {
32823
+ title: "Real-Time Monitoring",
32824
+ content: "Track your production lines, monitor efficiency, and get instant alerts about any issues.",
32825
+ icon: "\u{1F4CA}"
32826
+ },
32827
+ {
32828
+ title: "Data-Driven Insights",
32829
+ content: "Make informed decisions with comprehensive analytics and historical data at your fingertips.",
32830
+ icon: "\u{1F4A1}"
32831
+ }
32832
+ ];
32833
+ const handleNext = () => {
32834
+ if (currentStep < steps.length - 1) {
32835
+ setCurrentStep(currentStep + 1);
32836
+ } else {
32837
+ onComplete();
32838
+ }
32839
+ };
32840
+ const handleSkip = () => {
32841
+ onComplete();
32842
+ };
32843
+ const currentStepData = steps[currentStep];
32844
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 z-[9998] flex items-center justify-center", children: /* @__PURE__ */ 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: [
32845
+ /* @__PURE__ */ jsx(
32846
+ "button",
32847
+ {
32848
+ onClick: handleSkip,
32849
+ className: "absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors",
32850
+ disabled: isCompleting,
32851
+ children: /* @__PURE__ */ jsx(X, { size: 20 })
32852
+ }
32853
+ ),
32854
+ /* @__PURE__ */ jsxs("div", { className: "p-8", children: [
32855
+ /* @__PURE__ */ jsx("div", { className: "text-5xl mb-4 text-center", children: currentStepData.icon }),
32856
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-4 text-center", children: currentStepData.title }),
32857
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 text-center mb-8", children: currentStepData.content }),
32858
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center space-x-2 mb-6", children: steps.map((_, index) => /* @__PURE__ */ jsx(
32859
+ "div",
32860
+ {
32861
+ className: `h-2 w-2 rounded-full transition-colors ${index === currentStep ? "bg-blue-600" : "bg-gray-300"}`
32862
+ },
32863
+ index
32864
+ )) }),
32865
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
32866
+ /* @__PURE__ */ jsx(
32867
+ "button",
32868
+ {
32869
+ onClick: handleSkip,
32870
+ className: "text-gray-500 hover:text-gray-700 text-sm transition-colors",
32871
+ disabled: isCompleting,
32872
+ children: "Skip tour"
32873
+ }
32874
+ ),
32875
+ /* @__PURE__ */ jsx(
32876
+ "button",
32877
+ {
32878
+ onClick: handleNext,
32879
+ disabled: isCompleting,
32880
+ 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",
32881
+ children: isCompleting ? /* @__PURE__ */ jsxs("span", { className: "flex items-center", children: [
32882
+ /* @__PURE__ */ 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: [
32883
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
32884
+ /* @__PURE__ */ 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" })
32885
+ ] }),
32886
+ "Loading..."
32887
+ ] }) : currentStep === steps.length - 1 ? "Get Started" : "Next"
32888
+ }
32889
+ )
32890
+ ] })
32891
+ ] })
32892
+ ] }) }) });
32893
+ };
32894
+ var MinimalOnboardingPopup = ({
32895
+ onComplete,
32896
+ isCompleting = false
32897
+ }) => {
32898
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 z-[9998] flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "bg-white rounded-lg shadow-2xl max-w-sm w-full mx-4 relative z-[9999]", children: /* @__PURE__ */ jsxs("div", { className: "p-6 text-center", children: [
32899
+ /* @__PURE__ */ jsx("div", { className: "text-5xl mb-4", children: "\u{1F389}" }),
32900
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold text-gray-900 mb-3", children: "Welcome to Optifye!" }),
32901
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-6", children: "Your dashboard is ready. Let's get started!" }),
32902
+ /* @__PURE__ */ jsx(
32903
+ "button",
32904
+ {
32905
+ onClick: onComplete,
32906
+ disabled: isCompleting,
32907
+ 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",
32908
+ children: isCompleting ? "Loading..." : "Get Started"
32909
+ }
32910
+ )
32911
+ ] }) }) }) });
32912
+ };
32913
+ var onboardingSteps = [
32914
+ {
32915
+ id: "welcome",
32916
+ title: "Welcome to Optifye.ai \u{1F44B}",
32917
+ 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.",
32918
+ position: "center",
32919
+ nextTrigger: "button"
32920
+ },
32921
+ {
32922
+ id: "live-streams",
32923
+ title: "Live Manufacturing Streams",
32924
+ description: "These are real-time video feeds from your production lines. Each stream shows the current efficiency with color-coded overlays.",
32925
+ target: '.workspace-grid, [class*="workspace-grid"], [class*="grid"], [class*="workspace-container"]',
32926
+ position: "auto",
32927
+ spotlight: true,
32928
+ allowInteraction: false,
32929
+ nextTrigger: "button"
32930
+ },
32931
+ {
32932
+ id: "efficiency-legend",
32933
+ title: "Understanding Efficiency Colors",
32934
+ description: "Green (80-100%): Optimal performance\nYellow (70-79%): Needs attention\nRed (<70%): Critical issues\nFlashing red (<50%): Immediate action required",
32935
+ target: '[class*="efficiency"], [class*="legend"], [class*="indicator"]',
32936
+ position: "auto",
32937
+ spotlight: true,
32938
+ allowInteraction: false,
32939
+ nextTrigger: "button"
32940
+ },
32941
+ {
32942
+ id: "click-workspace",
32943
+ title: "Dive Into a Workspace",
32944
+ description: "Click on any video stream to explore detailed performance metrics for that production line.",
32945
+ target: '.workspace-card, [class*="workspace-item"], [class*="workspace-card"], [class*="video-card"]',
32946
+ actionTarget: '.workspace-card, [class*="workspace-item"], [class*="workspace-card"], [class*="video-card"]',
32947
+ position: "auto",
32948
+ action: "click",
32949
+ spotlight: true,
32950
+ allowInteraction: true,
32951
+ nextTrigger: "action",
32952
+ waitForPath: "/workspace"
32953
+ },
32954
+ {
32955
+ id: "workspace-metrics",
32956
+ title: "Real-Time Performance Dashboard",
32957
+ description: "Excellent! Here you can monitor OEE (Overall Equipment Effectiveness), production rates, quality metrics, and identify bottlenecks in real-time.",
32958
+ target: '[class*="metrics"], [class*="dashboard-content"], [class*="workspace-detail"], main',
32959
+ position: "auto",
32960
+ spotlight: true,
32961
+ allowInteraction: false,
32962
+ nextTrigger: "button"
32963
+ },
32964
+ {
32965
+ id: "navigate-back",
32966
+ title: "Navigate Back Home",
32967
+ description: "Click on the Home tab in the sidebar to return to the main dashboard.",
32968
+ target: '[data-nav-item="home"], [href="/"], nav a:has-text("Home"), nav button:has-text("Home"), tab:has-text("Home")',
32969
+ actionTarget: '[data-nav-item="home"], [href="/"], nav a:has-text("Home"), nav button:has-text("Home"), tab:has-text("Home")',
32970
+ position: "right",
32971
+ action: "click",
32972
+ spotlight: true,
32973
+ allowInteraction: true,
32974
+ nextTrigger: "action",
32975
+ waitForPath: "/"
32976
+ },
32977
+ {
32978
+ id: "complete",
32979
+ title: "\u{1F389} Tour Complete!",
32980
+ 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.",
32981
+ position: "center",
32982
+ nextTrigger: "button"
32983
+ }
32984
+ ];
32985
+ var InteractiveOnboardingTour = ({
32986
+ isOpen,
32987
+ onComplete,
32988
+ onSkip
32989
+ }) => {
32990
+ const [currentStep, setCurrentStep] = useState(0);
32991
+ const [isTransitioning, setIsTransitioning] = useState(false);
32992
+ const router = useRouter();
32993
+ const step = onboardingSteps[currentStep];
32994
+ const [targetElement, setTargetElement] = useState(null);
32995
+ const [tooltipPosition, setTooltipPosition] = useState({});
32996
+ const observerRef = useRef(null);
32997
+ const handleNext = useCallback(() => {
32998
+ if (isTransitioning) return;
32999
+ setIsTransitioning(true);
33000
+ setTimeout(() => {
33001
+ if (currentStep < onboardingSteps.length - 1) {
33002
+ setCurrentStep(currentStep + 1);
33003
+ } else {
33004
+ onComplete();
33005
+ }
33006
+ setIsTransitioning(false);
33007
+ }, 200);
33008
+ }, [currentStep, isTransitioning, onComplete]);
33009
+ const handlePrevious = useCallback(() => {
33010
+ if (currentStep > 0 && !isTransitioning) {
33011
+ setIsTransitioning(true);
33012
+ setTimeout(() => {
33013
+ setCurrentStep(currentStep - 1);
33014
+ setIsTransitioning(false);
33015
+ }, 200);
33016
+ }
33017
+ }, [currentStep, isTransitioning]);
33018
+ useEffect(() => {
33019
+ if (!step?.target || !isOpen) {
33020
+ setTargetElement(null);
33021
+ return;
33022
+ }
33023
+ const findElement = () => {
33024
+ const selectors = step.target.split(",").map((s) => s.trim());
33025
+ for (const selector of selectors) {
33026
+ try {
33027
+ const element2 = document.querySelector(selector);
33028
+ if (element2) {
33029
+ setTargetElement(element2);
33030
+ return element2;
33031
+ }
33032
+ } catch (e) {
33033
+ }
33034
+ }
33035
+ return null;
33036
+ };
33037
+ const element = findElement();
33038
+ if (!element && step.target) {
33039
+ observerRef.current = new MutationObserver(() => {
33040
+ const found = findElement();
33041
+ if (found) {
33042
+ observerRef.current?.disconnect();
33043
+ }
33044
+ });
33045
+ observerRef.current.observe(document.body, {
33046
+ childList: true,
33047
+ subtree: true
33048
+ });
33049
+ }
33050
+ return () => {
33051
+ observerRef.current?.disconnect();
33052
+ };
33053
+ }, [step, isOpen, router.pathname]);
33054
+ useEffect(() => {
33055
+ if (!targetElement || step?.position === "center") {
33056
+ setTooltipPosition({
33057
+ position: "fixed",
33058
+ top: "50%",
33059
+ left: "50%",
33060
+ transform: "translate(-50%, -50%)",
33061
+ zIndex: 10002
33062
+ });
33063
+ return;
33064
+ }
33065
+ const updatePosition = () => {
33066
+ const rect = targetElement.getBoundingClientRect();
33067
+ const tooltipWidth = 420;
33068
+ const tooltipHeight = 200;
33069
+ const offset = 16;
33070
+ const viewportWidth = window.innerWidth;
33071
+ const viewportHeight = window.innerHeight;
33072
+ let position = {
33073
+ position: "fixed",
33074
+ zIndex: 10002,
33075
+ maxWidth: "420px"
33076
+ };
33077
+ const spaceTop = rect.top;
33078
+ const spaceBottom = viewportHeight - rect.bottom;
33079
+ const spaceLeft = rect.left;
33080
+ const spaceRight = viewportWidth - rect.right;
33081
+ if (step.position === "auto") {
33082
+ if (spaceBottom > tooltipHeight + offset && rect.left + tooltipWidth / 2 < viewportWidth) {
33083
+ position.top = rect.bottom + offset;
33084
+ position.left = Math.max(offset, Math.min(rect.left + rect.width / 2 - tooltipWidth / 2, viewportWidth - tooltipWidth - offset));
33085
+ } else if (spaceTop > tooltipHeight + offset) {
33086
+ position.bottom = viewportHeight - rect.top + offset;
33087
+ position.left = Math.max(offset, Math.min(rect.left + rect.width / 2 - tooltipWidth / 2, viewportWidth - tooltipWidth - offset));
33088
+ } else if (spaceRight > tooltipWidth + offset) {
33089
+ position.left = rect.right + offset;
33090
+ position.top = Math.max(offset, Math.min(rect.top + rect.height / 2 - tooltipHeight / 2, viewportHeight - tooltipHeight - offset));
33091
+ } else if (spaceLeft > tooltipWidth + offset) {
33092
+ position.right = viewportWidth - rect.left + offset;
33093
+ position.top = Math.max(offset, Math.min(rect.top + rect.height / 2 - tooltipHeight / 2, viewportHeight - tooltipHeight - offset));
33094
+ } else {
33095
+ position.top = "50%";
33096
+ position.left = "50%";
33097
+ position.transform = "translate(-50%, -50%)";
33098
+ }
33099
+ } else {
33100
+ switch (step.position) {
33101
+ case "top":
33102
+ position.bottom = viewportHeight - rect.top + offset;
33103
+ position.left = rect.left + rect.width / 2 - tooltipWidth / 2;
33104
+ break;
33105
+ case "bottom":
33106
+ position.top = rect.bottom + offset;
33107
+ position.left = rect.left + rect.width / 2 - tooltipWidth / 2;
33108
+ break;
33109
+ case "left":
33110
+ position.right = viewportWidth - rect.left + offset;
33111
+ position.top = rect.top + rect.height / 2 - tooltipHeight / 2;
33112
+ break;
33113
+ case "right":
33114
+ position.left = rect.right + offset;
33115
+ position.top = rect.top + rect.height / 2 - tooltipHeight / 2;
33116
+ break;
33117
+ }
33118
+ }
33119
+ setTooltipPosition(position);
33120
+ };
33121
+ updatePosition();
33122
+ window.addEventListener("resize", updatePosition);
33123
+ window.addEventListener("scroll", updatePosition, true);
33124
+ return () => {
33125
+ window.removeEventListener("resize", updatePosition);
33126
+ window.removeEventListener("scroll", updatePosition, true);
33127
+ };
33128
+ }, [targetElement, step]);
33129
+ useEffect(() => {
33130
+ if (!step || step.nextTrigger !== "action" || !isOpen) return;
33131
+ let actionTimeout;
33132
+ const handleAction = (e) => {
33133
+ if (!step.actionTarget) return;
33134
+ const target = e.target;
33135
+ const selectors = step.actionTarget.split(",").map((s) => s.trim());
33136
+ for (const selector of selectors) {
33137
+ try {
33138
+ if (target.matches(selector) || target.closest(selector)) {
33139
+ if (step.waitForPath) {
33140
+ actionTimeout = setTimeout(() => {
33141
+ console.log("No navigation detected, progressing to next step");
33142
+ handleNext();
33143
+ }, 2e3);
33144
+ } else {
33145
+ setTimeout(() => handleNext(), 300);
33146
+ }
33147
+ return;
33148
+ }
33149
+ } catch {
33150
+ }
33151
+ }
33152
+ };
33153
+ if (step.action === "click") {
33154
+ document.addEventListener("click", handleAction, true);
33155
+ return () => {
33156
+ document.removeEventListener("click", handleAction, true);
33157
+ if (actionTimeout) clearTimeout(actionTimeout);
33158
+ };
33159
+ }
33160
+ }, [step, isOpen, handleNext]);
33161
+ useEffect(() => {
33162
+ if (!step?.waitForPath || step.nextTrigger !== "action") return;
33163
+ const checkPath = (url) => {
33164
+ const currentPath = url || router.asPath || router.pathname;
33165
+ if (step.waitForPath === "/workspace" && (currentPath.includes("/workspace") || currentPath.includes("workspace-") || router.query?.workspace)) {
33166
+ setTimeout(() => handleNext(), 800);
33167
+ return true;
33168
+ }
33169
+ if (step.waitForPath && (currentPath === step.waitForPath || currentPath.includes(step.waitForPath))) {
33170
+ setTimeout(() => handleNext(), 800);
33171
+ return true;
33172
+ }
33173
+ return false;
33174
+ };
33175
+ if (checkPath()) return;
33176
+ const handleRouteChange = (url) => {
33177
+ checkPath(url);
33178
+ };
33179
+ router.events.on("routeChangeComplete", handleRouteChange);
33180
+ router.events.on("routeChangeStart", handleRouteChange);
33181
+ return () => {
33182
+ router.events.off("routeChangeComplete", handleRouteChange);
33183
+ router.events.off("routeChangeStart", handleRouteChange);
33184
+ };
33185
+ }, [step, router, handleNext]);
33186
+ if (!isOpen || !step) return null;
33187
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
33188
+ !step.allowInteraction && /* @__PURE__ */ jsx(
33189
+ motion.div,
33190
+ {
33191
+ initial: { opacity: 0 },
33192
+ animate: { opacity: 1 },
33193
+ exit: { opacity: 0 },
33194
+ className: "fixed inset-0 bg-black/20",
33195
+ style: { zIndex: 1e4 },
33196
+ onClick: (e) => e.stopPropagation()
33197
+ }
33198
+ ),
33199
+ step.spotlight && targetElement && /* @__PURE__ */ jsx(
33200
+ Spotlight,
33201
+ {
33202
+ element: targetElement,
33203
+ allowInteraction: step.allowInteraction,
33204
+ showPulse: step.action === "click"
33205
+ }
33206
+ ),
33207
+ /* @__PURE__ */ jsx(
33208
+ motion.div,
33209
+ {
33210
+ initial: { opacity: 0, scale: 0.95, y: 10 },
33211
+ animate: { opacity: 1, scale: 1, y: 0 },
33212
+ exit: { opacity: 0, scale: 0.95, y: 10 },
33213
+ transition: { duration: 0.2, ease: "easeOut" },
33214
+ style: tooltipPosition,
33215
+ className: "pointer-events-auto",
33216
+ children: /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-900 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-800 overflow-hidden", children: [
33217
+ /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex items-center justify-between", children: [
33218
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
33219
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: [...Array(onboardingSteps.length)].map((_, i) => /* @__PURE__ */ jsx(
33220
+ motion.div,
33221
+ {
33222
+ initial: false,
33223
+ animate: {
33224
+ width: i === currentStep ? 24 : 6,
33225
+ backgroundColor: i === currentStep ? "#3B82F6" : i < currentStep ? "#93C5FD" : "#E5E7EB"
33226
+ },
33227
+ className: "h-1.5 rounded-full transition-all duration-300"
33228
+ },
33229
+ i
33230
+ )) }),
33231
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-gray-600 dark:text-gray-400", children: [
33232
+ currentStep + 1,
33233
+ " of ",
33234
+ onboardingSteps.length
33235
+ ] })
33236
+ ] }),
33237
+ /* @__PURE__ */ jsx(
33238
+ "button",
33239
+ {
33240
+ onClick: onSkip,
33241
+ 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",
33242
+ "aria-label": "Close tour",
33243
+ children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
33244
+ }
33245
+ )
33246
+ ] }) }),
33247
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-5", children: [
33248
+ /* @__PURE__ */ jsx("h3", { className: "text-xl font-semibold text-gray-900 dark:text-white mb-3", children: step.title }),
33249
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 dark:text-gray-300 leading-relaxed whitespace-pre-line", children: step.description }),
33250
+ step.action === "click" && step.nextTrigger === "action" && /* @__PURE__ */ jsx(
33251
+ motion.div,
33252
+ {
33253
+ initial: { opacity: 0, y: 5 },
33254
+ animate: { opacity: 1, y: 0 },
33255
+ transition: { delay: 0.3 },
33256
+ className: "mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl border border-blue-200 dark:border-blue-800",
33257
+ children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-blue-700 dark:text-blue-300 flex items-center gap-2", children: [
33258
+ /* @__PURE__ */ jsx(MousePointer, { className: "w-4 h-4 animate-pulse" }),
33259
+ "Click the highlighted element to continue"
33260
+ ] })
33261
+ }
33262
+ )
33263
+ ] }),
33264
+ /* @__PURE__ */ 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: [
33265
+ /* @__PURE__ */ jsx(
33266
+ "button",
33267
+ {
33268
+ onClick: onSkip,
33269
+ className: "text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
33270
+ children: "Skip tour"
33271
+ }
33272
+ ),
33273
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
33274
+ currentStep > 0 && /* @__PURE__ */ jsx(
33275
+ "button",
33276
+ {
33277
+ onClick: handlePrevious,
33278
+ disabled: isTransitioning,
33279
+ 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",
33280
+ children: "Previous"
33281
+ }
33282
+ ),
33283
+ step.nextTrigger === "button" && /* @__PURE__ */ jsx(
33284
+ "button",
33285
+ {
33286
+ onClick: handleNext,
33287
+ disabled: isTransitioning,
33288
+ 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`,
33289
+ children: currentStep === onboardingSteps.length - 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
33290
+ "Complete Tour",
33291
+ /* @__PURE__ */ jsx(Check, { className: "w-4 h-4" })
33292
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
33293
+ "Next",
33294
+ /* @__PURE__ */ jsx(ArrowRight, { className: "w-4 h-4" })
33295
+ ] })
33296
+ }
33297
+ )
33298
+ ] })
33299
+ ] })
33300
+ ] })
33301
+ },
33302
+ step.id
33303
+ )
33304
+ ] });
33305
+ if (typeof window !== "undefined") {
33306
+ return createPortal(
33307
+ /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && content }),
33308
+ document.body
33309
+ );
33310
+ }
33311
+ return null;
33312
+ };
33313
+ var Spotlight = ({ element, allowInteraction, showPulse }) => {
33314
+ const [rect, setRect] = useState(null);
33315
+ useEffect(() => {
33316
+ const updateRect = () => {
33317
+ setRect(element.getBoundingClientRect());
33318
+ };
33319
+ updateRect();
33320
+ window.addEventListener("resize", updateRect);
33321
+ window.addEventListener("scroll", updateRect, true);
33322
+ const observer = new ResizeObserver(updateRect);
33323
+ observer.observe(element);
33324
+ return () => {
33325
+ window.removeEventListener("resize", updateRect);
33326
+ window.removeEventListener("scroll", updateRect, true);
33327
+ observer.disconnect();
33328
+ };
33329
+ }, [element]);
33330
+ if (!rect) return null;
33331
+ const padding = 8;
33332
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
33333
+ !allowInteraction && /* @__PURE__ */ jsxs(
33334
+ "svg",
33335
+ {
33336
+ className: "fixed inset-0",
33337
+ style: { zIndex: 10001, pointerEvents: "auto" },
33338
+ children: [
33339
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("mask", { id: "spotlight-mask", children: [
33340
+ /* @__PURE__ */ jsx("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: "white" }),
33341
+ /* @__PURE__ */ jsx(
33342
+ "rect",
33343
+ {
33344
+ x: rect.left - padding,
33345
+ y: rect.top - padding,
33346
+ width: rect.width + padding * 2,
33347
+ height: rect.height + padding * 2,
33348
+ rx: "8",
33349
+ fill: "black"
33350
+ }
33351
+ )
33352
+ ] }) }),
33353
+ /* @__PURE__ */ jsx(
33354
+ "rect",
33355
+ {
33356
+ x: "0",
33357
+ y: "0",
33358
+ width: "100%",
33359
+ height: "100%",
33360
+ fill: "black",
33361
+ fillOpacity: "0.2",
33362
+ mask: "url(#spotlight-mask)"
33363
+ }
33364
+ )
33365
+ ]
33366
+ }
33367
+ ),
33368
+ /* @__PURE__ */ jsxs(
33369
+ motion.div,
33370
+ {
33371
+ className: "fixed pointer-events-none",
33372
+ style: {
33373
+ left: rect.left - padding,
33374
+ top: rect.top - padding,
33375
+ width: rect.width + padding * 2,
33376
+ height: rect.height + padding * 2,
33377
+ zIndex: 10001
33378
+ },
33379
+ initial: { opacity: 0, scale: 0.95 },
33380
+ animate: { opacity: 1, scale: 1 },
33381
+ exit: { opacity: 0, scale: 0.95 },
33382
+ transition: { duration: 0.3, ease: "easeOut" },
33383
+ children: [
33384
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg ring-3 ring-blue-500 shadow-lg shadow-blue-500/30" }),
33385
+ showPulse && allowInteraction && /* @__PURE__ */ jsxs(Fragment, { children: [
33386
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg ring-4 ring-blue-400 ring-opacity-75 animate-pulse" }),
33387
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg bg-blue-500/5 animate-pulse" })
33388
+ ] })
33389
+ ]
33390
+ }
33391
+ )
33392
+ ] });
33393
+ };
33394
+ var FirstTimeLoginHandler = ({
33395
+ children,
33396
+ onboardingComponent: OnboardingComponent = SimpleOnboardingPopup,
33397
+ // Default to simple popup
33398
+ enableAutoDetection = true
33399
+ }) => {
33400
+ const {
33401
+ needsOnboarding,
33402
+ completeFirstLogin,
33403
+ user
33404
+ } = useFirstTimeLogin();
33405
+ const {
33406
+ showOnboarding: showTour,
33407
+ setShowOnboarding: setShowTour,
33408
+ completeOnboarding
33409
+ } = useAuth();
33410
+ const [showOnboarding, setShowOnboarding] = useState(false);
33411
+ const [isCompleting, setIsCompleting] = useState(false);
33412
+ const [hasChecked, setHasChecked] = useState(false);
33413
+ useEffect(() => {
33414
+ if (!hasChecked && user && enableAutoDetection) {
33415
+ setHasChecked(true);
33416
+ if (needsOnboarding) {
33417
+ console.log("[FirstTimeLoginHandler] First-time login detected, showing onboarding tour");
33418
+ setShowTour(true);
33419
+ }
33420
+ }
33421
+ }, [user, needsOnboarding, enableAutoDetection, hasChecked, setShowTour]);
33422
+ const handleOnboardingComplete = async () => {
33423
+ setIsCompleting(true);
33424
+ try {
33425
+ const success = await completeFirstLogin();
33426
+ if (success) {
33427
+ console.log("[FirstTimeLoginHandler] First login marked as completed");
33428
+ setShowOnboarding(false);
33429
+ } else {
33430
+ console.error("[FirstTimeLoginHandler] Failed to mark first login as completed");
33431
+ setShowOnboarding(false);
33432
+ }
33433
+ } catch (error) {
33434
+ console.error("[FirstTimeLoginHandler] Error completing first login:", error);
33435
+ setShowOnboarding(false);
33436
+ } finally {
33437
+ setIsCompleting(false);
33438
+ }
33439
+ };
33440
+ const handleTourComplete = async () => {
33441
+ await completeOnboarding();
33442
+ };
33443
+ const handleTourSkip = () => {
33444
+ setShowTour(false);
33445
+ completeFirstLogin();
33446
+ };
33447
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
33448
+ children,
33449
+ showTour && /* @__PURE__ */ jsx(
33450
+ InteractiveOnboardingTour,
33451
+ {
33452
+ isOpen: showTour,
33453
+ onComplete: handleTourComplete,
33454
+ onSkip: handleTourSkip
33455
+ }
33456
+ ),
33457
+ showOnboarding && OnboardingComponent && /* @__PURE__ */ jsx(
33458
+ OnboardingComponent,
33459
+ {
33460
+ onComplete: handleOnboardingComplete,
33461
+ isCompleting
33462
+ }
33463
+ )
33464
+ ] });
33465
+ };
33466
+ var FirstTimeLoginDebug = () => {
33467
+ const {
33468
+ isFirstTimeLogin,
33469
+ hasCompletedFirstLogin,
33470
+ needsOnboarding,
33471
+ completeFirstLogin,
33472
+ user
33473
+ } = useFirstTimeLogin();
33474
+ if (!user) return null;
33475
+ return /* @__PURE__ */ jsxs("div", { style: {
33476
+ position: "fixed",
33477
+ top: 10,
33478
+ right: 10,
33479
+ background: "#f0f0f0",
33480
+ padding: "10px",
33481
+ border: "1px solid #ccc",
33482
+ fontSize: "12px",
33483
+ zIndex: 9999
33484
+ }, children: [
33485
+ /* @__PURE__ */ jsx("h4", { children: "First-Time Login Debug" }),
33486
+ /* @__PURE__ */ jsxs("p", { children: [
33487
+ "User ID: ",
33488
+ user.id
33489
+ ] }),
33490
+ /* @__PURE__ */ jsxs("p", { children: [
33491
+ "Email: ",
33492
+ user.email
33493
+ ] }),
33494
+ /* @__PURE__ */ jsxs("p", { children: [
33495
+ "Is First Time: ",
33496
+ isFirstTimeLogin ? "\u2705" : "\u274C"
33497
+ ] }),
33498
+ /* @__PURE__ */ jsxs("p", { children: [
33499
+ "Completed: ",
33500
+ hasCompletedFirstLogin ? "\u2705" : "\u274C"
33501
+ ] }),
33502
+ /* @__PURE__ */ jsxs("p", { children: [
33503
+ "Needs Onboarding: ",
33504
+ needsOnboarding ? "\u2705" : "\u274C"
33505
+ ] }),
33506
+ needsOnboarding && /* @__PURE__ */ jsx(
33507
+ "button",
33508
+ {
33509
+ onClick: completeFirstLogin,
33510
+ style: { marginTop: "5px", padding: "5px" },
33511
+ children: "Mark as Completed"
33512
+ }
33513
+ )
33514
+ ] });
33515
+ };
33516
+ var onboardingSteps2 = [
33517
+ {
33518
+ id: "welcome",
33519
+ title: "Welcome to Optifye.ai",
33520
+ description: "Let's take a quick tour to help you get started with our intelligent manufacturing monitoring platform.",
33521
+ position: "center"
33522
+ },
33523
+ {
33524
+ id: "live-streams",
33525
+ title: "Live Video Streams",
33526
+ description: "These are live video streams of your manufacturing lines. Monitor your production in real-time from anywhere.",
33527
+ target: ".workspace-grid",
33528
+ position: "top",
33529
+ highlightTarget: true
33530
+ },
33531
+ {
33532
+ id: "efficiency-indicator",
33533
+ title: "Efficiency Indicators",
33534
+ 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.",
33535
+ target: ".efficiency-indicator",
33536
+ position: "bottom",
33537
+ highlightTarget: true
33538
+ },
33539
+ {
33540
+ id: "click-workspace",
33541
+ title: "Explore a Workspace",
33542
+ description: "Click on any video stream to dive deeper into that workspace's performance metrics.",
33543
+ target: ".workspace-item",
33544
+ position: "center",
33545
+ highlightTarget: true,
33546
+ waitForNavigation: "/workspace/"
33547
+ },
33548
+ {
33549
+ id: "workspace-metrics",
33550
+ title: "Real-Time Efficiency Metrics",
33551
+ description: "Great! These are the real-time efficiency metrics for the workspace you just selected. Track OEE, performance, and quality in real-time.",
33552
+ target: ".metrics-dashboard",
33553
+ position: "top",
33554
+ highlightTarget: true
33555
+ },
33556
+ {
33557
+ id: "clips-navigation",
33558
+ title: "Explore Clips",
33559
+ description: 'Now, click on "Clips" in the sidebar to see how you can diagnose issues and understand root causes.',
33560
+ target: '[data-nav-item="clips"]',
33561
+ position: "right",
33562
+ highlightTarget: true,
33563
+ waitForNavigation: "/clips"
33564
+ },
33565
+ {
33566
+ id: "clips-explanation",
33567
+ title: "Clips for Root Cause Analysis",
33568
+ description: "Clips help you understand and diagnose the root cause of efficiency issues. Review recorded incidents, analyze patterns, and improve your processes.",
33569
+ target: ".clips-container",
33570
+ position: "top",
33571
+ highlightTarget: true
33572
+ },
33573
+ {
33574
+ id: "complete",
33575
+ title: "You're All Set!",
33576
+ description: "You've completed the tour! Start exploring Optifye.ai to optimize your manufacturing operations.",
33577
+ position: "center"
33578
+ }
33579
+ ];
33580
+ var OnboardingTour = ({
33581
+ isOpen,
33582
+ onComplete,
33583
+ onSkip
33584
+ }) => {
33585
+ const [currentStep, setCurrentStep] = useState(0);
33586
+ const [isWaitingForNavigation, setIsWaitingForNavigation] = useState(false);
33587
+ const router = useRouter();
33588
+ const step = onboardingSteps2[currentStep];
33589
+ useEffect(() => {
33590
+ if (!step?.waitForNavigation || !isWaitingForNavigation) return;
33591
+ const handleRouteChange = (url) => {
33592
+ if (step.waitForNavigation && url.includes(step.waitForNavigation)) {
33593
+ setIsWaitingForNavigation(false);
33594
+ handleNext();
33595
+ }
33596
+ };
33597
+ router.events.on("routeChangeComplete", handleRouteChange);
33598
+ return () => {
33599
+ router.events.off("routeChangeComplete", handleRouteChange);
33600
+ };
33601
+ }, [step, isWaitingForNavigation, router]);
33602
+ const handleNext = useCallback(() => {
33603
+ const nextStep = onboardingSteps2[currentStep + 1];
33604
+ if (step?.waitForNavigation && !isWaitingForNavigation) {
33605
+ setIsWaitingForNavigation(true);
33606
+ return;
33607
+ }
33608
+ if (currentStep < onboardingSteps2.length - 1) {
33609
+ setCurrentStep(currentStep + 1);
33610
+ if (nextStep?.target) {
33611
+ setTimeout(() => {
33612
+ const element = document.querySelector(nextStep.target);
33613
+ if (element) {
33614
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
33615
+ }
33616
+ }, 100);
33617
+ }
33618
+ } else {
33619
+ onComplete();
33620
+ }
33621
+ }, [currentStep, step, isWaitingForNavigation, onComplete]);
33622
+ const handlePrevious = useCallback(() => {
33623
+ if (currentStep > 0) {
33624
+ setCurrentStep(currentStep - 1);
33625
+ setIsWaitingForNavigation(false);
33626
+ }
33627
+ }, [currentStep]);
33628
+ const getPositionStyles = useCallback((position) => {
33629
+ const baseStyles = {
33630
+ position: "fixed",
33631
+ zIndex: 1e4
33632
+ };
33633
+ if (step?.target && position !== "center") {
33634
+ const element = document.querySelector(step.target);
33635
+ if (element) {
33636
+ const rect = element.getBoundingClientRect();
33637
+ const tooltipWidth = 400;
33638
+ const tooltipHeight = 200;
33639
+ const offset = 20;
33640
+ switch (position) {
33641
+ case "top":
33642
+ return {
33643
+ ...baseStyles,
33644
+ left: `${rect.left + rect.width / 2 - tooltipWidth / 2}px`,
33645
+ bottom: `${window.innerHeight - rect.top + offset}px`
33646
+ };
33647
+ case "bottom":
33648
+ return {
33649
+ ...baseStyles,
33650
+ left: `${rect.left + rect.width / 2 - tooltipWidth / 2}px`,
33651
+ top: `${rect.bottom + offset}px`
33652
+ };
33653
+ case "left":
33654
+ return {
33655
+ ...baseStyles,
33656
+ right: `${window.innerWidth - rect.left + offset}px`,
33657
+ top: `${rect.top + rect.height / 2 - tooltipHeight / 2}px`
33658
+ };
33659
+ case "right":
33660
+ return {
33661
+ ...baseStyles,
33662
+ left: `${rect.right + offset}px`,
33663
+ top: `${rect.top + rect.height / 2 - tooltipHeight / 2}px`
33664
+ };
33665
+ }
33666
+ }
33667
+ }
33668
+ return {
33669
+ ...baseStyles,
33670
+ top: "50%",
33671
+ left: "50%",
33672
+ transform: "translate(-50%, -50%)"
33673
+ };
33674
+ }, [step]);
33675
+ if (!isOpen || !step) return null;
33676
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
33677
+ /* @__PURE__ */ jsx(
33678
+ motion.div,
33679
+ {
33680
+ initial: { opacity: 0 },
33681
+ animate: { opacity: 1 },
33682
+ exit: { opacity: 0 },
33683
+ className: "fixed inset-0 bg-black/60 backdrop-blur-sm z-[9998]",
33684
+ onClick: (e) => e.stopPropagation()
33685
+ }
33686
+ ),
33687
+ step.highlightTarget && step.target && /* @__PURE__ */ jsx(HighlightOverlay, { target: step.target }),
33688
+ /* @__PURE__ */ jsx(
33689
+ motion.div,
33690
+ {
33691
+ initial: { opacity: 0, scale: 0.9 },
33692
+ animate: { opacity: 1, scale: 1 },
33693
+ exit: { opacity: 0, scale: 0.9 },
33694
+ transition: { duration: 0.3, ease: "easeOut" },
33695
+ style: getPositionStyles(step.position),
33696
+ className: "w-[400px] max-w-[90vw]",
33697
+ children: /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
33698
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between", children: [
33699
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
33700
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: [...Array(onboardingSteps2.length)].map((_, i) => /* @__PURE__ */ jsx(
33701
+ "div",
33702
+ {
33703
+ 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`
33704
+ },
33705
+ i
33706
+ )) }),
33707
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
33708
+ currentStep + 1,
33709
+ " / ",
33710
+ onboardingSteps2.length
33711
+ ] })
33712
+ ] }),
33713
+ /* @__PURE__ */ jsx(
33714
+ "button",
33715
+ {
33716
+ onClick: onSkip,
33717
+ className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors",
33718
+ children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
33719
+ }
33720
+ )
33721
+ ] }),
33722
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-5", children: [
33723
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-2", children: step.title }),
33724
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 dark:text-gray-300 leading-relaxed", children: step.description }),
33725
+ isWaitingForNavigation && /* @__PURE__ */ jsx("div", { className: "mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-blue-700 dark:text-blue-300 flex items-center gap-2", children: [
33726
+ /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 bg-blue-600 rounded-full animate-pulse" }),
33727
+ "Waiting for you to click..."
33728
+ ] }) })
33729
+ ] }),
33730
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 bg-gray-50 dark:bg-gray-900/50 flex items-center justify-between", children: [
33731
+ /* @__PURE__ */ jsx(
33732
+ "button",
33733
+ {
33734
+ onClick: onSkip,
33735
+ className: "text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
33736
+ children: "Skip tour"
33737
+ }
33738
+ ),
33739
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
33740
+ currentStep > 0 && /* @__PURE__ */ jsx(
33741
+ "button",
33742
+ {
33743
+ onClick: handlePrevious,
33744
+ 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",
33745
+ children: "Previous"
33746
+ }
33747
+ ),
33748
+ /* @__PURE__ */ jsx(
33749
+ "button",
33750
+ {
33751
+ onClick: handleNext,
33752
+ disabled: isWaitingForNavigation,
33753
+ 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"}`,
33754
+ children: currentStep === onboardingSteps2.length - 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
33755
+ "Complete",
33756
+ /* @__PURE__ */ jsx(Check, { className: "w-4 h-4" })
33757
+ ] }) : isWaitingForNavigation ? "Waiting..." : /* @__PURE__ */ jsxs(Fragment, { children: [
33758
+ "Next",
33759
+ /* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4" })
33760
+ ] })
33761
+ }
33762
+ )
33763
+ ] })
33764
+ ] })
33765
+ ] })
33766
+ },
33767
+ step.id
33768
+ )
33769
+ ] }) });
33770
+ };
33771
+ var HighlightOverlay = ({ target }) => {
33772
+ const [rect, setRect] = useState(null);
33773
+ useEffect(() => {
33774
+ const element = document.querySelector(target);
33775
+ if (element) {
33776
+ setRect(element.getBoundingClientRect());
33777
+ }
33778
+ }, [target]);
33779
+ if (!rect) return null;
33780
+ return /* @__PURE__ */ jsxs(
33781
+ motion.div,
33782
+ {
33783
+ initial: { opacity: 0 },
33784
+ animate: { opacity: 1 },
33785
+ exit: { opacity: 0 },
33786
+ className: "fixed z-[9999] pointer-events-none",
33787
+ style: {
33788
+ left: rect.left - 8,
33789
+ top: rect.top - 8,
33790
+ width: rect.width + 16,
33791
+ height: rect.height + 16
33792
+ },
33793
+ children: [
33794
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg ring-4 ring-blue-500/50 ring-offset-4 ring-offset-transparent animate-pulse" }),
33795
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg bg-blue-500/10" })
33796
+ ]
33797
+ }
33798
+ );
33799
+ };
33800
+ var OnboardingDemo = () => {
33801
+ const [showTour, setShowTour] = useState(false);
33802
+ const handleComplete = () => {
33803
+ setShowTour(false);
33804
+ console.log("Onboarding tour completed");
33805
+ };
33806
+ const handleSkip = () => {
33807
+ setShowTour(false);
33808
+ console.log("Onboarding tour skipped");
33809
+ };
33810
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
33811
+ /* @__PURE__ */ jsxs(
33812
+ "button",
33813
+ {
33814
+ onClick: () => setShowTour(true),
33815
+ 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",
33816
+ title: "Start Onboarding Tour",
33817
+ children: [
33818
+ /* @__PURE__ */ jsx(Info, { className: "w-4 h-4" }),
33819
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Start Tour" })
33820
+ ]
33821
+ }
33822
+ ),
33823
+ /* @__PURE__ */ jsx(
33824
+ InteractiveOnboardingTour,
33825
+ {
33826
+ isOpen: showTour,
33827
+ onComplete: handleComplete,
33828
+ onSkip: handleSkip
33829
+ }
33830
+ )
33831
+ ] });
33832
+ };
31204
33833
  var ThreadSidebar = ({
31205
33834
  activeThreadId,
31206
33835
  onSelectThread,
@@ -34407,6 +37036,15 @@ var QualityOverview = memo(({ lineInfo }) => {
34407
37036
  ] });
34408
37037
  });
34409
37038
  QualityOverview.displayName = "QualityOverview";
37039
+ var getSupervisorName = (lineId) => {
37040
+ const supervisorMapping = {
37041
+ // Add more mappings as needed
37042
+ "default": "Vivaan Baid",
37043
+ "factory": "Vivaan Baid"
37044
+ // You can add specific line IDs here when you have real data
37045
+ };
37046
+ return supervisorMapping[lineId || "default"] || "Vivaan Baid";
37047
+ };
34410
37048
  var KPIDetailView = ({
34411
37049
  lineId,
34412
37050
  date: urlDate,
@@ -34896,9 +37534,15 @@ var KPIDetailView = ({
34896
37534
  "aria-label": "Navigate back to previous page"
34897
37535
  }
34898
37536
  ) }),
34899
- /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center mt-2 sm:mt-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
34900
- /* @__PURE__ */ 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" }),
34901
- /* @__PURE__ */ 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" })
37537
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center mt-2 sm:mt-0", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
37538
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
37539
+ /* @__PURE__ */ 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" }),
37540
+ /* @__PURE__ */ 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" })
37541
+ ] }),
37542
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-600 font-medium", children: [
37543
+ "Supervisor: ",
37544
+ getSupervisorName(lineInfo?.line_id)
37545
+ ] })
34902
37546
  ] }) })
34903
37547
  ] }),
34904
37548
  (activeTab !== "monthly_history" || urlDate || urlShift) && metrics2 && /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-3 md:gap-4", children: [
@@ -35106,8 +37750,11 @@ var LineCard = ({ line, onClick }) => {
35106
37750
  onClick,
35107
37751
  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",
35108
37752
  children: [
35109
- /* @__PURE__ */ 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: [
35110
- /* @__PURE__ */ jsx("h3", { className: "text-lg sm:text-xl font-semibold text-gray-900 truncate", children: line.line_name }),
37753
+ /* @__PURE__ */ 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: [
37754
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
37755
+ /* @__PURE__ */ jsx("h3", { className: "text-lg sm:text-xl font-semibold text-gray-900 truncate", children: line.line_name }),
37756
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 mt-1", children: "Supervisor: Vivaan" })
37757
+ ] }),
35111
37758
  kpis && isOnTrack !== null && /* @__PURE__ */ 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: [
35112
37759
  /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${isOnTrack ? "bg-emerald-500" : "bg-red-500"} animate-pulse` }),
35113
37760
  /* @__PURE__ */ jsx("span", { children: isOnTrack ? "On Track" : "Behind" })
@@ -35809,9 +38456,6 @@ var ProfileView = () => {
35809
38456
  id: user.id,
35810
38457
  email: user.email,
35811
38458
  full_name: profileData.full_name,
35812
- company: profileData.company,
35813
- phone: profileData.phone,
35814
- timezone: profileData.timezone,
35815
38459
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
35816
38460
  });
35817
38461
  if (error2) throw error2;
@@ -35960,55 +38604,6 @@ var ProfileView = () => {
35960
38604
  /* @__PURE__ */ jsx("p", { className: "text-gray-900", children: profileData.email }),
35961
38605
  profileData.email_verified && /* @__PURE__ */ jsx(UserCheck, { className: "h-4 w-4 text-green-500" })
35962
38606
  ] })
35963
- ] }),
35964
- /* @__PURE__ */ jsxs("div", { children: [
35965
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Company" }),
35966
- isEditing ? /* @__PURE__ */ jsx(
35967
- "input",
35968
- {
35969
- type: "text",
35970
- value: profileData.company || "",
35971
- onChange: (e) => setProfileData((prev) => ({ ...prev, company: e.target.value })),
35972
- className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
35973
- }
35974
- ) : /* @__PURE__ */ jsx("p", { className: "text-gray-900", children: profileData.company || "Not set" })
35975
- ] }),
35976
- /* @__PURE__ */ jsxs("div", { children: [
35977
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Role" }),
35978
- /* @__PURE__ */ jsx("p", { className: "text-gray-900", children: profileData.role || "Not set" })
35979
- ] }),
35980
- /* @__PURE__ */ jsxs("div", { children: [
35981
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Phone" }),
35982
- isEditing ? /* @__PURE__ */ jsx(
35983
- "input",
35984
- {
35985
- type: "tel",
35986
- value: profileData.phone || "",
35987
- onChange: (e) => setProfileData((prev) => ({ ...prev, phone: e.target.value })),
35988
- className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
35989
- }
35990
- ) : /* @__PURE__ */ jsx("p", { className: "text-gray-900", children: profileData.phone || "Not set" })
35991
- ] }),
35992
- /* @__PURE__ */ jsxs("div", { children: [
35993
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Timezone" }),
35994
- isEditing ? /* @__PURE__ */ jsxs(
35995
- "select",
35996
- {
35997
- value: profileData.timezone || "",
35998
- onChange: (e) => setProfileData((prev) => ({ ...prev, timezone: e.target.value })),
35999
- className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent",
36000
- children: [
36001
- /* @__PURE__ */ jsx("option", { value: "", children: "Select timezone" }),
36002
- /* @__PURE__ */ jsx("option", { value: "UTC", children: "UTC" }),
36003
- /* @__PURE__ */ jsx("option", { value: "America/New_York", children: "Eastern Time" }),
36004
- /* @__PURE__ */ jsx("option", { value: "America/Chicago", children: "Central Time" }),
36005
- /* @__PURE__ */ jsx("option", { value: "America/Denver", children: "Mountain Time" }),
36006
- /* @__PURE__ */ jsx("option", { value: "America/Los_Angeles", children: "Pacific Time" }),
36007
- /* @__PURE__ */ jsx("option", { value: "Asia/Kolkata", children: "India Standard Time" }),
36008
- /* @__PURE__ */ jsx("option", { value: "Europe/London", children: "GMT" })
36009
- ]
36010
- }
36011
- ) : /* @__PURE__ */ jsx("p", { className: "text-gray-900", children: profileData.timezone || "Not set" })
36012
38607
  ] })
36013
38608
  ] }),
36014
38609
  isEditing && /* @__PURE__ */ jsx("div", { className: "mt-6 flex gap-3", children: /* @__PURE__ */ jsx(Button2, { variant: "outline", onClick: handleSaveProfile, disabled: loading, children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -38591,10 +41186,6 @@ var TargetsView = ({
38591
41186
  }, [actionIds]);
38592
41187
  const handleSaveLine = useCallback(async (lineId) => {
38593
41188
  console.log(`[handleSaveLine] Attempting to save line: ${lineId}`);
38594
- if (!canSaveTargets) {
38595
- toast.error("You do not have permission to save targets. Contact your administrator.");
38596
- return;
38597
- }
38598
41189
  const hardcodedUserId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
38599
41190
  const currentEffectiveUserId = hardcodedUserId;
38600
41191
  console.log(`[handleSaveLine] effectiveUserId: ${currentEffectiveUserId}`);
@@ -38871,6 +41462,13 @@ var WorkspaceDetailView = ({
38871
41462
  const [selectedMonth, setSelectedMonth] = useState(today.getMonth());
38872
41463
  const [selectedYear, setSelectedYear] = useState(today.getFullYear());
38873
41464
  const [selectedShift, setSelectedShift] = useState("day");
41465
+ useEffect(() => {
41466
+ if (parsedShiftId === 1) {
41467
+ setSelectedShift("night");
41468
+ } else if (parsedShiftId === 0) {
41469
+ setSelectedShift("day");
41470
+ }
41471
+ }, [parsedShiftId]);
38874
41472
  const isHistoricView = Boolean(date && parsedShiftId !== void 0);
38875
41473
  const initialTab = getInitialTab(sourceType, defaultTab, fromMonthly, date);
38876
41474
  const [activeTab, setActiveTab] = useState(initialTab);
@@ -39120,11 +41718,18 @@ var WorkspaceDetailView = ({
39120
41718
  }
39121
41719
  return;
39122
41720
  }
41721
+ if (activeTab === "monthly_history") {
41722
+ if (onNavigate) {
41723
+ onNavigate("/");
41724
+ }
41725
+ return;
41726
+ }
39123
41727
  if (date || shift) {
39124
41728
  setActiveTab("monthly_history");
39125
41729
  if (onNavigate) {
39126
41730
  const params = new URLSearchParams();
39127
41731
  params.set("fromMonthly", "true");
41732
+ params.set("shift", selectedShift === "night" ? "1" : "0");
39128
41733
  if (effectiveLineId) {
39129
41734
  params.set("lineId", effectiveLineId);
39130
41735
  }
@@ -39136,6 +41741,7 @@ var WorkspaceDetailView = ({
39136
41741
  if (onNavigate) {
39137
41742
  const params = new URLSearchParams();
39138
41743
  params.set("fromMonthly", "true");
41744
+ params.set("shift", selectedShift === "night" ? "1" : "0");
39139
41745
  if (effectiveLineId) {
39140
41746
  params.set("lineId", effectiveLineId);
39141
41747
  }
@@ -39234,7 +41840,7 @@ var WorkspaceDetailView = ({
39234
41840
  BackButtonMinimal,
39235
41841
  {
39236
41842
  onClick: handleBackNavigation,
39237
- 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",
41843
+ 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",
39238
41844
  size: "sm",
39239
41845
  "aria-label": "Navigate back to previous page"
39240
41846
  }
@@ -39260,7 +41866,7 @@ var WorkspaceDetailView = ({
39260
41866
  BackButtonMinimal,
39261
41867
  {
39262
41868
  onClick: handleBackNavigation,
39263
- 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",
41869
+ 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",
39264
41870
  size: "default",
39265
41871
  "aria-label": "Navigate back to previous page"
39266
41872
  }
@@ -39648,6 +42254,7 @@ var WorkspaceDetailView = ({
39648
42254
  const params = new URLSearchParams();
39649
42255
  params.set("date", selectedDate);
39650
42256
  params.set("shift", selectedShift === "day" ? "0" : "1");
42257
+ params.set("fromMonthly", "true");
39651
42258
  if (effectiveLineId) {
39652
42259
  params.set("lineId", effectiveLineId);
39653
42260
  }
@@ -40131,6 +42738,168 @@ var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
40131
42738
  requireAuth: true
40132
42739
  });
40133
42740
  var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
42741
+ var SupervisorManagementView = ({
42742
+ onNavigate,
42743
+ onBack,
42744
+ className = ""
42745
+ }) => {
42746
+ const supabase = useSupabaseClient();
42747
+ const entityConfig = useEntityConfig();
42748
+ const companyId = entityConfig?.companyId;
42749
+ const [data, setData] = useState({
42750
+ assignments: [],
42751
+ allSupervisors: [],
42752
+ loading: true,
42753
+ error: void 0
42754
+ });
42755
+ const [savingAssignments, setSavingAssignments] = useState(/* @__PURE__ */ new Set());
42756
+ const loadData = useCallback(async () => {
42757
+ try {
42758
+ setData((prev) => ({ ...prev, loading: true, error: void 0 }));
42759
+ if (!companyId) {
42760
+ throw new Error("Company ID is not configured");
42761
+ }
42762
+ if (!supabase) {
42763
+ throw new Error("Supabase client is not available");
42764
+ }
42765
+ console.log(`[SupervisorManagementView] Loading data for companyId: ${companyId}`);
42766
+ const supervisorService = createSupervisorService(supabase);
42767
+ const realData = await supervisorService.getSupervisorManagementData(companyId);
42768
+ console.log(`[SupervisorManagementView] Received data:`, {
42769
+ assignments: realData.assignments.length,
42770
+ allSupervisors: realData.allSupervisors.length,
42771
+ firstAssignmentAvailableSupervisors: realData.assignments[0]?.availableSupervisors?.length || 0,
42772
+ firstAssignmentAvailableSupervisorIds: realData.assignments[0]?.availableSupervisors?.map((s) => s.id) || []
42773
+ });
42774
+ setData(realData);
42775
+ } catch (error) {
42776
+ console.error("Error loading supervisor management data:", error);
42777
+ setData((prev) => ({
42778
+ ...prev,
42779
+ loading: false,
42780
+ error: error instanceof Error ? error.message : "Failed to load supervisor management data. Please try again."
42781
+ }));
42782
+ }
42783
+ }, [supabase, companyId]);
42784
+ useEffect(() => {
42785
+ loadData();
42786
+ }, [loadData]);
42787
+ const handleSupervisorChange = useCallback(async (lineId, supervisor) => {
42788
+ try {
42789
+ setSavingAssignments((prev) => /* @__PURE__ */ new Set([...prev, lineId]));
42790
+ if (!supabase) {
42791
+ throw new Error("Supabase client is not available");
42792
+ }
42793
+ const supervisorService = createSupervisorService(supabase);
42794
+ const success = await supervisorService.assignSupervisorToLine(lineId, supervisor?.id);
42795
+ if (!success) {
42796
+ throw new Error("Failed to update supervisor assignment");
42797
+ }
42798
+ setData((prev) => ({
42799
+ ...prev,
42800
+ assignments: prev.assignments.map(
42801
+ (assignment) => assignment.lineId === lineId ? { ...assignment, currentSupervisor: supervisor || void 0 } : assignment
42802
+ )
42803
+ }));
42804
+ const lineName = data.assignments.find((a) => a.lineId === lineId)?.lineName || "Line";
42805
+ const message = supervisor ? `${supervisor.name} assigned to ${lineName}` : `Supervisor removed from ${lineName}`;
42806
+ toast.success(message);
42807
+ } catch (error) {
42808
+ console.error("Error updating supervisor assignment:", error);
42809
+ toast.error("Failed to update supervisor assignment. Please try again.");
42810
+ } finally {
42811
+ setSavingAssignments((prev) => {
42812
+ const newSet = new Set(prev);
42813
+ newSet.delete(lineId);
42814
+ return newSet;
42815
+ });
42816
+ }
42817
+ }, [data.assignments, supabase]);
42818
+ const handleRefresh = useCallback(() => {
42819
+ loadData();
42820
+ }, [loadData]);
42821
+ const handleBack = useCallback(() => {
42822
+ if (onBack) {
42823
+ onBack();
42824
+ } else if (onNavigate) {
42825
+ onNavigate("/");
42826
+ }
42827
+ }, [onBack, onNavigate]);
42828
+ if (data.loading) {
42829
+ return /* @__PURE__ */ jsx("div", { className: clsx("min-h-screen bg-slate-50", className), children: /* @__PURE__ */ jsx(LoadingPage, { message: "Loading supervisor management..." }) });
42830
+ }
42831
+ if (data.error) {
42832
+ return /* @__PURE__ */ jsx("div", { className: clsx("min-h-screen bg-slate-50", className), children: /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsx(
42833
+ EmptyStateMessage,
42834
+ {
42835
+ iconType: AlertCircle,
42836
+ title: "Failed to Load Data",
42837
+ message: data.error,
42838
+ actionButton: {
42839
+ text: "Try Again",
42840
+ onClick: handleRefresh,
42841
+ className: "bg-blue-600 hover:bg-blue-700"
42842
+ }
42843
+ }
42844
+ ) }) });
42845
+ }
42846
+ return /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
42847
+ /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "px-3 sm:px-4 md:px-6 lg:px-8 py-3 sm:py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
42848
+ /* @__PURE__ */ jsx("div", { className: "sm:absolute sm:left-0", children: /* @__PURE__ */ jsx(
42849
+ BackButtonMinimal,
42850
+ {
42851
+ onClick: handleBack,
42852
+ text: "Back",
42853
+ size: "default",
42854
+ "aria-label": "Navigate back to previous page"
42855
+ }
42856
+ ) }),
42857
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center mt-2 sm:mt-0", children: [
42858
+ /* @__PURE__ */ jsx("h1", { className: "text-lg sm:text-xl md:text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "Supervisor Management" }),
42859
+ /* @__PURE__ */ 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" })
42860
+ ] }),
42861
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:block absolute right-0 w-24", children: /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
42862
+ "button",
42863
+ {
42864
+ onClick: handleRefresh,
42865
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
42866
+ "aria-label": "Refresh data",
42867
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
42868
+ }
42869
+ ) }) })
42870
+ ] }) }) }),
42871
+ /* @__PURE__ */ 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__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsx(
42872
+ EmptyStateMessage,
42873
+ {
42874
+ iconType: Building2,
42875
+ title: "No Lines Found",
42876
+ message: "There are no production lines to manage supervisor assignments for."
42877
+ }
42878
+ ) }) : /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", { className: "bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [
42879
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsxs("tr", { children: [
42880
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Production Line" }),
42881
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assigned Supervisor" })
42882
+ ] }) }),
42883
+ /* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: data.assignments.map((assignment) => /* @__PURE__ */ jsxs("tr", { className: "hover:bg-gray-50", children: [
42884
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
42885
+ /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-gray-400 mr-2" }),
42886
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-900", children: assignment.lineName })
42887
+ ] }) }),
42888
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsx("div", { className: "max-w-xs", children: /* @__PURE__ */ jsx(
42889
+ SupervisorDropdown,
42890
+ {
42891
+ selectedSupervisor: assignment.currentSupervisor,
42892
+ availableSupervisors: assignment.availableSupervisors,
42893
+ onSelect: (supervisor) => handleSupervisorChange(assignment.lineId, supervisor),
42894
+ disabled: savingAssignments.has(assignment.lineId),
42895
+ placeholder: "Select supervisor..."
42896
+ }
42897
+ ) }) })
42898
+ ] }, assignment.lineId)) })
42899
+ ] }) }) }) }) })
42900
+ ] });
42901
+ };
42902
+ var SupervisorManagementView_default = SupervisorManagementView;
40134
42903
  var S3Service = class {
40135
42904
  constructor(config) {
40136
42905
  this.s3Client = null;
@@ -40586,4 +43355,4 @@ var streamProxyConfig = {
40586
43355
  }
40587
43356
  };
40588
43357
 
40589
- export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedWorkspaceHealthView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CompactWorkspaceHealthCard, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useCanSaveTargets, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealth, useWorkspaceHealthById, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceHealthService, workspaceService };
43358
+ export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedWorkspaceHealthView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CompactWorkspaceHealthCard, CongratulationsOverlay, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, InteractiveOnboardingTour, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, MinimalOnboardingPopup, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, UserService, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createLinesService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserService, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useAccessControl, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useCanSaveTargets, useClipTypes, useClipTypesWithCounts, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealth, useWorkspaceHealthById, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, workspaceHealthService, workspaceService };