@optifye/dashboard-core 6.9.1 → 6.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.css CHANGED
@@ -2454,10 +2454,6 @@ body {
2454
2454
  --tw-bg-opacity: 1;
2455
2455
  background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
2456
2456
  }
2457
- .bg-red-400 {
2458
- --tw-bg-opacity: 1;
2459
- background-color: rgb(248 113 113 / var(--tw-bg-opacity, 1));
2460
- }
2461
2457
  .bg-red-400\/50 {
2462
2458
  background-color: rgb(248 113 113 / 0.5);
2463
2459
  }
@@ -5631,6 +5627,10 @@ input[type=range]:active::-moz-range-thumb {
5631
5627
  padding-top: 0.75rem;
5632
5628
  padding-bottom: 0.75rem;
5633
5629
  }
5630
+ .lg\:py-3\.5 {
5631
+ padding-top: 0.875rem;
5632
+ padding-bottom: 0.875rem;
5633
+ }
5634
5634
  .lg\:text-2xl {
5635
5635
  font-size: 1.5rem;
5636
5636
  line-height: 2rem;
package/dist/index.d.mts CHANGED
@@ -3607,13 +3607,13 @@ interface UseWorkspaceHealthStatusReturn {
3607
3607
  *
3608
3608
  * @description This hook subscribes to the workspace_health_status table and monitors
3609
3609
  * the last_heartbeat column for real-time updates. It automatically formats the time
3610
- * as "Xs ago", "Xm Ys ago", etc. and updates the display every second for accuracy.
3610
+ * with whole numbers (e.g., "Less than a minute ago", "4 minutes ago") and updates periodically.
3611
3611
  *
3612
3612
  * @param {string} workspaceId - The workspace UUID to monitor
3613
3613
  *
3614
3614
  * @returns {UseWorkspaceHealthStatusReturn} Object containing:
3615
3615
  * - lastHeartbeat: ISO timestamp string or null
3616
- * - timeSinceUpdate: Formatted relative time string (e.g., "30s ago")
3616
+ * - timeSinceUpdate: Formatted relative time string (e.g., "Less than a minute ago", "4 minutes ago")
3617
3617
  * - isHealthy: Boolean health status flag
3618
3618
  * - healthData: Full health status record from database
3619
3619
  * - loading: Loading state
@@ -3622,7 +3622,7 @@ interface UseWorkspaceHealthStatusReturn {
3622
3622
  *
3623
3623
  * @example
3624
3624
  * const { lastHeartbeat, timeSinceUpdate, isHealthy } = useWorkspaceHealthStatus('workspace-123');
3625
- * // timeSinceUpdate: "45s ago" or "5m 30s ago"
3625
+ * // timeSinceUpdate: "Less than a minute ago" or "4 minutes ago"
3626
3626
  */
3627
3627
  declare const useWorkspaceHealthStatus: (workspaceId: string) => UseWorkspaceHealthStatusReturn;
3628
3628
 
@@ -3708,17 +3708,18 @@ interface UseSessionKeepAliveOptions {
3708
3708
  */
3709
3709
  declare const useSessionKeepAlive: (options?: UseSessionKeepAliveOptions) => void;
3710
3710
 
3711
- type UserRole = 'owner' | 'plant_head' | 'supervisor';
3711
+ type UserRole = 'owner' | 'plant_head' | 'supervisor' | 'optifye';
3712
3712
  interface AccessControlReturn {
3713
3713
  userRole: UserRole | null;
3714
3714
  hasAccess: (path: string) => boolean;
3715
3715
  accessiblePages: string[];
3716
3716
  isPageVisible: (path: string) => boolean;
3717
3717
  canAccessPage: (path: string) => boolean;
3718
+ assignedLineIds: string[];
3719
+ assignedFactoryIds: string[];
3718
3720
  }
3719
3721
  /**
3720
3722
  * Hook to manage role-based access control
3721
- * TEMPORARILY DISABLED: All users have access to all pages
3722
3723
  *
3723
3724
  * @returns {AccessControlReturn} Access control utilities and state
3724
3725
  */
@@ -3914,6 +3915,7 @@ interface LineSupervisor {
3914
3915
  interface UseLineSupervisorReturn {
3915
3916
  supervisorName: string | null;
3916
3917
  supervisor: LineSupervisor | null;
3918
+ supervisors: LineSupervisor[];
3917
3919
  isLoading: boolean;
3918
3920
  error: Error | null;
3919
3921
  }
@@ -4919,15 +4921,15 @@ declare const getCompanyMetricsTableName: (companyId: string | undefined, prefix
4919
4921
  declare const getMetricsTablePrefix: (companyId?: string) => string;
4920
4922
 
4921
4923
  /**
4922
- * Format a timestamp as relative time with detailed precision
4923
- * Shows seconds, minutes, hours, or days ago
4924
+ * Format a timestamp as relative time with whole number precision
4925
+ * Shows minutes, hours, or days ago without real-time seconds
4924
4926
  *
4925
4927
  * @param timestamp - Date object or ISO string timestamp
4926
- * @returns Formatted relative time string (e.g., "30s ago", "5m 30s ago", "2h ago", "3d ago")
4928
+ * @returns Formatted relative time string (e.g., "Less than a minute ago", "4 minutes ago", "2 hours ago", "3 days ago")
4927
4929
  *
4928
4930
  * @example
4929
- * formatRelativeTime(new Date()) // "0s ago"
4930
- * formatRelativeTime("2024-01-01T12:00:00Z") // "5m 30s ago"
4931
+ * formatRelativeTime(new Date()) // "Less than a minute ago"
4932
+ * formatRelativeTime("2024-01-01T12:00:00Z") // "4 minutes ago"
4931
4933
  */
4932
4934
  declare function formatRelativeTime(timestamp: string | Date | null | undefined): string;
4933
4935
  /**
package/dist/index.d.ts CHANGED
@@ -3607,13 +3607,13 @@ interface UseWorkspaceHealthStatusReturn {
3607
3607
  *
3608
3608
  * @description This hook subscribes to the workspace_health_status table and monitors
3609
3609
  * the last_heartbeat column for real-time updates. It automatically formats the time
3610
- * as "Xs ago", "Xm Ys ago", etc. and updates the display every second for accuracy.
3610
+ * with whole numbers (e.g., "Less than a minute ago", "4 minutes ago") and updates periodically.
3611
3611
  *
3612
3612
  * @param {string} workspaceId - The workspace UUID to monitor
3613
3613
  *
3614
3614
  * @returns {UseWorkspaceHealthStatusReturn} Object containing:
3615
3615
  * - lastHeartbeat: ISO timestamp string or null
3616
- * - timeSinceUpdate: Formatted relative time string (e.g., "30s ago")
3616
+ * - timeSinceUpdate: Formatted relative time string (e.g., "Less than a minute ago", "4 minutes ago")
3617
3617
  * - isHealthy: Boolean health status flag
3618
3618
  * - healthData: Full health status record from database
3619
3619
  * - loading: Loading state
@@ -3622,7 +3622,7 @@ interface UseWorkspaceHealthStatusReturn {
3622
3622
  *
3623
3623
  * @example
3624
3624
  * const { lastHeartbeat, timeSinceUpdate, isHealthy } = useWorkspaceHealthStatus('workspace-123');
3625
- * // timeSinceUpdate: "45s ago" or "5m 30s ago"
3625
+ * // timeSinceUpdate: "Less than a minute ago" or "4 minutes ago"
3626
3626
  */
3627
3627
  declare const useWorkspaceHealthStatus: (workspaceId: string) => UseWorkspaceHealthStatusReturn;
3628
3628
 
@@ -3708,17 +3708,18 @@ interface UseSessionKeepAliveOptions {
3708
3708
  */
3709
3709
  declare const useSessionKeepAlive: (options?: UseSessionKeepAliveOptions) => void;
3710
3710
 
3711
- type UserRole = 'owner' | 'plant_head' | 'supervisor';
3711
+ type UserRole = 'owner' | 'plant_head' | 'supervisor' | 'optifye';
3712
3712
  interface AccessControlReturn {
3713
3713
  userRole: UserRole | null;
3714
3714
  hasAccess: (path: string) => boolean;
3715
3715
  accessiblePages: string[];
3716
3716
  isPageVisible: (path: string) => boolean;
3717
3717
  canAccessPage: (path: string) => boolean;
3718
+ assignedLineIds: string[];
3719
+ assignedFactoryIds: string[];
3718
3720
  }
3719
3721
  /**
3720
3722
  * Hook to manage role-based access control
3721
- * TEMPORARILY DISABLED: All users have access to all pages
3722
3723
  *
3723
3724
  * @returns {AccessControlReturn} Access control utilities and state
3724
3725
  */
@@ -3914,6 +3915,7 @@ interface LineSupervisor {
3914
3915
  interface UseLineSupervisorReturn {
3915
3916
  supervisorName: string | null;
3916
3917
  supervisor: LineSupervisor | null;
3918
+ supervisors: LineSupervisor[];
3917
3919
  isLoading: boolean;
3918
3920
  error: Error | null;
3919
3921
  }
@@ -4919,15 +4921,15 @@ declare const getCompanyMetricsTableName: (companyId: string | undefined, prefix
4919
4921
  declare const getMetricsTablePrefix: (companyId?: string) => string;
4920
4922
 
4921
4923
  /**
4922
- * Format a timestamp as relative time with detailed precision
4923
- * Shows seconds, minutes, hours, or days ago
4924
+ * Format a timestamp as relative time with whole number precision
4925
+ * Shows minutes, hours, or days ago without real-time seconds
4924
4926
  *
4925
4927
  * @param timestamp - Date object or ISO string timestamp
4926
- * @returns Formatted relative time string (e.g., "30s ago", "5m 30s ago", "2h ago", "3d ago")
4928
+ * @returns Formatted relative time string (e.g., "Less than a minute ago", "4 minutes ago", "2 hours ago", "3 days ago")
4927
4929
  *
4928
4930
  * @example
4929
- * formatRelativeTime(new Date()) // "0s ago"
4930
- * formatRelativeTime("2024-01-01T12:00:00Z") // "5m 30s ago"
4931
+ * formatRelativeTime(new Date()) // "Less than a minute ago"
4932
+ * formatRelativeTime("2024-01-01T12:00:00Z") // "4 minutes ago"
4931
4933
  */
4932
4934
  declare function formatRelativeTime(timestamp: string | Date | null | undefined): string;
4933
4935
  /**
package/dist/index.js CHANGED
@@ -228,7 +228,7 @@ var DEFAULT_DATE_TIME_CONFIG = {
228
228
  };
229
229
  var DEFAULT_ENDPOINTS_CONFIG = {
230
230
  whatsapp: "/api/send-whatsapp-direct",
231
- agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://optifye-agent-production.up.railway.app",
231
+ agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app",
232
232
  // Default AGNO API URL
233
233
  // Use environment variable for Slack webhook URL for privacy/security
234
234
  // Note: SLACK_WEBHOOK_URL is server-side only, NEXT_PUBLIC_SLACK_WEBHOOK_URL works client-side but is less secure
@@ -3246,7 +3246,7 @@ var SSEChatClient = class {
3246
3246
  user_id: userId,
3247
3247
  context
3248
3248
  });
3249
- const agnoApiUrl = this.baseUrl || "https://fastapi-production-111f9.up.railway.app";
3249
+ const agnoApiUrl = this.baseUrl || (process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app");
3250
3250
  const endpoint = `${agnoApiUrl}/api/v2/chat`;
3251
3251
  console.log("[SSEClient] Posting directly to AGNO:", endpoint);
3252
3252
  const response = await fetch(endpoint, {
@@ -10739,23 +10739,18 @@ function formatRelativeTime(timestamp) {
10739
10739
  const diffHours = Math.floor(diffMinutes / 60);
10740
10740
  const diffDays = Math.floor(diffHours / 24);
10741
10741
  if (diffSeconds < 60) {
10742
- return `${diffSeconds}s ago`;
10742
+ return "Less than a minute ago";
10743
10743
  }
10744
10744
  if (diffMinutes < 60) {
10745
- const remainingSeconds = diffSeconds % 60;
10746
- if (remainingSeconds === 0) {
10747
- return `${diffMinutes}m ago`;
10748
- }
10749
- return `${diffMinutes}m ${remainingSeconds}s ago`;
10745
+ const minuteLabel = diffMinutes === 1 ? "minute" : "minutes";
10746
+ return `${diffMinutes} ${minuteLabel} ago`;
10750
10747
  }
10751
10748
  if (diffHours < 24) {
10752
- const remainingMinutes = diffMinutes % 60;
10753
- if (remainingMinutes === 0) {
10754
- return `${diffHours}h ago`;
10755
- }
10756
- return `${diffHours}h ${remainingMinutes}m ago`;
10749
+ const hourLabel = diffHours === 1 ? "hour" : "hours";
10750
+ return `${diffHours} ${hourLabel} ago`;
10757
10751
  }
10758
- return `${diffDays}d ago`;
10752
+ const dayLabel = diffDays === 1 ? "day" : "days";
10753
+ return `${diffDays} ${dayLabel} ago`;
10759
10754
  } catch (error) {
10760
10755
  console.error("[formatRelativeTime] Error formatting timestamp:", error);
10761
10756
  return "Unknown";
@@ -10771,8 +10766,8 @@ function getNextUpdateInterval(timestamp) {
10771
10766
  const diffSeconds = Math.floor(diffMs / 1e3);
10772
10767
  const diffMinutes = Math.floor(diffSeconds / 60);
10773
10768
  const diffHours = Math.floor(diffMinutes / 60);
10774
- if (diffSeconds < 60) return 1e3;
10775
- if (diffMinutes < 60) return 1e3;
10769
+ if (diffSeconds < 60) return 1e4;
10770
+ if (diffMinutes < 60) return 6e4;
10776
10771
  if (diffHours < 24) return 6e4;
10777
10772
  return 36e5;
10778
10773
  } catch (error) {
@@ -10949,50 +10944,129 @@ function useAccessControl() {
10949
10944
  const userRole = React23.useMemo(() => {
10950
10945
  if (!user?.role_level) return null;
10951
10946
  const roleLevel = user.role_level;
10952
- if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor") {
10947
+ if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor" || roleLevel === "optifye") {
10953
10948
  return roleLevel;
10954
10949
  }
10955
10950
  return "supervisor";
10956
10951
  }, [user?.role_level]);
10957
- const allPages = [
10958
- "/",
10959
- "/leaderboard",
10960
- "/kpis",
10961
- "/targets",
10962
- "/shifts",
10963
- "/supervisor-management",
10964
- "/skus",
10965
- "/ai-agent",
10966
- "/help",
10967
- "/health",
10968
- "/profile",
10969
- "/workspace",
10970
- "/factory-view"
10971
- ];
10952
+ const assignedLineIds = React23.useMemo(() => {
10953
+ if (!user) return [];
10954
+ if (user.role_level === "supervisor") {
10955
+ const lines = user.properties?.line_id || user.properties?.line_ids || [];
10956
+ return Array.isArray(lines) ? lines : [];
10957
+ }
10958
+ return [];
10959
+ }, [user]);
10960
+ const assignedFactoryIds = React23.useMemo(() => {
10961
+ if (!user) return [];
10962
+ if (user.role_level === "plant_head") {
10963
+ const factories = user.properties?.factory_id || user.properties?.factory_ids || [];
10964
+ return Array.isArray(factories) ? factories : [];
10965
+ }
10966
+ return [];
10967
+ }, [user]);
10968
+ const roleAccessMap = {
10969
+ optifye: [
10970
+ "/",
10971
+ "/leaderboard",
10972
+ "/kpis",
10973
+ "/targets",
10974
+ "/shifts",
10975
+ "/supervisor-management",
10976
+ "/skus",
10977
+ "/ai-agent",
10978
+ "/help",
10979
+ "/health",
10980
+ "/profile",
10981
+ "/workspace",
10982
+ "/factory-view",
10983
+ "/team-management"
10984
+ ],
10985
+ owner: [
10986
+ "/",
10987
+ "/leaderboard",
10988
+ "/kpis",
10989
+ "/targets",
10990
+ "/shifts",
10991
+ "/supervisor-management",
10992
+ "/skus",
10993
+ "/ai-agent",
10994
+ "/help",
10995
+ "/health",
10996
+ "/profile",
10997
+ "/workspace",
10998
+ "/factory-view",
10999
+ "/team-management"
11000
+ ],
11001
+ plant_head: [
11002
+ "/",
11003
+ "/leaderboard",
11004
+ "/kpis",
11005
+ "/targets",
11006
+ "/shifts",
11007
+ "/supervisor-management",
11008
+ "/skus",
11009
+ "/ai-agent",
11010
+ "/help",
11011
+ "/health",
11012
+ "/profile",
11013
+ "/workspace",
11014
+ "/factory-view",
11015
+ "/team-management"
11016
+ ],
11017
+ supervisor: [
11018
+ "/",
11019
+ "/leaderboard",
11020
+ "/kpis",
11021
+ "/targets",
11022
+ "/shifts",
11023
+ "/skus",
11024
+ "/ai-agent",
11025
+ "/help",
11026
+ "/health",
11027
+ "/profile",
11028
+ "/workspace"
11029
+ ]
11030
+ };
10972
11031
  const accessiblePages = React23.useMemo(() => {
10973
- return allPages;
10974
- }, []);
11032
+ if (!userRole) return [];
11033
+ return roleAccessMap[userRole] || [];
11034
+ }, [userRole]);
10975
11035
  const hasAccess = React23.useMemo(() => {
10976
11036
  return (path) => {
10977
- return true;
11037
+ if (!userRole) return false;
11038
+ const basePath = path.split("?")[0].split("/").slice(0, 2).join("/");
11039
+ const hasBaseAccess = accessiblePages.includes(basePath) || accessiblePages.includes("/");
11040
+ if (userRole === "supervisor" && assignedLineIds.length > 0) {
11041
+ if (path.includes("/kpis/") || path.includes("/workspace/")) {
11042
+ const lineIdMatch = path.match(/\/kpis\/([^/?]+)/);
11043
+ if (lineIdMatch) {
11044
+ const pathLineId = lineIdMatch[1];
11045
+ return assignedLineIds.includes(pathLineId);
11046
+ }
11047
+ }
11048
+ }
11049
+ return hasBaseAccess;
10978
11050
  };
10979
- }, []);
11051
+ }, [userRole, accessiblePages, assignedLineIds]);
10980
11052
  const isPageVisible = React23.useMemo(() => {
10981
11053
  return (path) => {
10982
- return true;
11054
+ return hasAccess(path);
10983
11055
  };
10984
- }, []);
11056
+ }, [hasAccess]);
10985
11057
  const canAccessPage = React23.useMemo(() => {
10986
11058
  return (path) => {
10987
- return true;
11059
+ return hasAccess(path);
10988
11060
  };
10989
- }, []);
11061
+ }, [hasAccess]);
10990
11062
  return {
10991
11063
  userRole,
10992
11064
  hasAccess,
10993
11065
  accessiblePages,
10994
11066
  isPageVisible,
10995
- canAccessPage
11067
+ canAccessPage,
11068
+ assignedLineIds,
11069
+ assignedFactoryIds
10996
11070
  };
10997
11071
  }
10998
11072
 
@@ -11156,45 +11230,51 @@ function useHasLineAccess(lineId, configLineIds) {
11156
11230
  function useLineSupervisor(lineId) {
11157
11231
  const supabase = useSupabase();
11158
11232
  const [supervisor, setSupervisor] = React23.useState(null);
11233
+ const [supervisors, setSupervisors] = React23.useState([]);
11159
11234
  const [isLoading, setIsLoading] = React23.useState(true);
11160
11235
  const [error, setError] = React23.useState(null);
11161
- console.log("[useLineSupervisor] Hook initialized for lineId:", lineId);
11162
11236
  const fetchSupervisor = React23.useCallback(async () => {
11163
11237
  if (!lineId || !supabase) {
11164
- console.log("[useLineSupervisor] Skipping fetch - lineId or supabase missing:", { lineId, hasSupabase: !!supabase });
11165
11238
  setIsLoading(false);
11166
11239
  return;
11167
11240
  }
11168
11241
  try {
11169
11242
  setIsLoading(true);
11170
11243
  setError(null);
11171
- console.log("[useLineSupervisor] Fetching supervisor for lineId:", lineId);
11172
- const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor").filter("properties->line_id", "@>", `["${lineId}"]`).limit(1);
11173
- console.log("[useLineSupervisor] Query result:", { data, error: fetchError, lineId });
11244
+ const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor");
11174
11245
  if (fetchError) {
11175
11246
  console.error("[useLineSupervisor] Query error:", fetchError);
11176
11247
  throw fetchError;
11177
11248
  }
11178
11249
  if (data && data.length > 0) {
11179
- const supervisorData = data[0];
11180
- console.log("[useLineSupervisor] Found supervisor:", {
11181
- email: supervisorData.email,
11182
- properties: supervisorData.properties,
11183
- line_id_in_properties: supervisorData.properties?.line_id
11184
- });
11185
- const displayName = supervisorData.email.split("@")[0] || supervisorData.email;
11186
- setSupervisor({
11187
- userId: supervisorData.user_id,
11188
- email: supervisorData.email,
11189
- displayName
11250
+ const supervisorsForLine = data.filter((supervisorData) => {
11251
+ const lineIds = supervisorData.properties?.line_id || supervisorData.properties?.line_ids || [];
11252
+ const isAssigned = Array.isArray(lineIds) && lineIds.includes(lineId);
11253
+ return isAssigned;
11190
11254
  });
11255
+ if (supervisorsForLine.length > 0) {
11256
+ const allSupervisors = supervisorsForLine.map((supervisorData) => {
11257
+ const displayName = supervisorData.email.split("@")[0] || supervisorData.email;
11258
+ return {
11259
+ userId: supervisorData.user_id,
11260
+ email: supervisorData.email,
11261
+ displayName
11262
+ };
11263
+ });
11264
+ setSupervisors(allSupervisors);
11265
+ setSupervisor(allSupervisors[0]);
11266
+ } else {
11267
+ setSupervisors([]);
11268
+ setSupervisor(null);
11269
+ }
11191
11270
  } else {
11192
- console.log("[useLineSupervisor] No supervisor found for line:", lineId);
11271
+ setSupervisors([]);
11193
11272
  setSupervisor(null);
11194
11273
  }
11195
11274
  } catch (err) {
11196
- console.error("[useLineSupervisor] Error fetching supervisor:", err);
11197
- setError(err instanceof Error ? err : new Error("Failed to fetch supervisor"));
11275
+ console.error("[useLineSupervisor] Error fetching supervisors:", err);
11276
+ setError(err instanceof Error ? err : new Error("Failed to fetch supervisors"));
11277
+ setSupervisors([]);
11198
11278
  setSupervisor(null);
11199
11279
  } finally {
11200
11280
  setIsLoading(false);
@@ -11219,7 +11299,6 @@ function useLineSupervisor(lineId) {
11219
11299
  filter: `role_level=eq.supervisor`
11220
11300
  },
11221
11301
  (payload) => {
11222
- console.log("[useLineSupervisor] Real-time update received:", payload);
11223
11302
  fetchSupervisor();
11224
11303
  }
11225
11304
  ).subscribe();
@@ -11231,9 +11310,11 @@ function useLineSupervisor(lineId) {
11231
11310
  }
11232
11311
  };
11233
11312
  }, [lineId, supabase, fetchSupervisor]);
11313
+ const supervisorName = supervisors.length > 0 ? supervisors.map((s) => s.displayName).join(", ") : null;
11234
11314
  return {
11235
- supervisorName: supervisor?.displayName || null,
11315
+ supervisorName,
11236
11316
  supervisor,
11317
+ supervisors,
11237
11318
  isLoading,
11238
11319
  error
11239
11320
  };
@@ -34104,14 +34185,16 @@ var WorkspaceWhatsAppShareButton = ({
34104
34185
  };
34105
34186
  var WorkspacePdfGenerator = ({ workspace, className }) => {
34106
34187
  const [isGenerating, setIsGenerating] = React23.useState(false);
34188
+ const entityConfig = useEntityConfig();
34107
34189
  const generatePDF = async () => {
34108
34190
  setIsGenerating(true);
34109
34191
  try {
34192
+ const lineName = workspace.line_name || getLineDisplayName(entityConfig, workspace.line_id);
34110
34193
  trackCoreEvent("Workspace PDF Export Clicked", {
34111
34194
  workspace_id: workspace.workspace_id,
34112
34195
  line_id: workspace.line_id,
34113
34196
  workspace_name: workspace.workspace_name,
34114
- line_name: workspace.line_name
34197
+ line_name: lineName
34115
34198
  });
34116
34199
  const doc = new jsPDF.jsPDF();
34117
34200
  doc.setFontSize(14);
@@ -34132,7 +34215,7 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34132
34215
  doc.setFontSize(32);
34133
34216
  doc.setFont("helvetica", "bold");
34134
34217
  doc.setTextColor(0, 0, 0);
34135
- doc.text("Line 1", 20, 40);
34218
+ doc.text(lineName, 20, 40);
34136
34219
  doc.setFontSize(22);
34137
34220
  doc.setFont("helvetica", "normal");
34138
34221
  doc.setTextColor(40, 40, 40);
@@ -38832,7 +38915,7 @@ var AIAgentView = () => {
38832
38915
  const renderedContentCache = React23.useRef(/* @__PURE__ */ new Map());
38833
38916
  const { createThread, mutate: mutateThreads } = useThreads();
38834
38917
  const { messages, addMessage, setMessages } = useMessages(activeThreadId);
38835
- const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://optifye-agent-production.up.railway.app";
38918
+ const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://fastapi-production-111f9.up.railway.app";
38836
38919
  const sseClient = React23.useMemo(() => {
38837
38920
  console.log("[AIAgentView] Using AGNO API URL:", agnoApiUrl);
38838
38921
  return new SSEChatClient(agnoApiUrl);
@@ -46938,6 +47021,7 @@ var WorkspaceDetailView = ({
46938
47021
  const [showIdleTime, setShowIdleTime] = React23.useState(false);
46939
47022
  const dashboardConfig = useDashboardConfig();
46940
47023
  const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
47024
+ dashboardConfig?.supervisorConfig?.enabled || false;
46941
47025
  const {
46942
47026
  workspace: workspaceHealth,
46943
47027
  loading: healthLoading,
@@ -46946,6 +47030,36 @@ var WorkspaceDetailView = ({
46946
47030
  enableRealtime: true,
46947
47031
  refreshInterval: 3e4
46948
47032
  });
47033
+ const {
47034
+ isHealthy: isWorkspaceHealthy,
47035
+ timeSinceUpdate,
47036
+ lastHeartbeat,
47037
+ loading: healthStatusLoading,
47038
+ error: healthStatusError
47039
+ } = useWorkspaceHealthStatus(workspaceId);
47040
+ const isLive = React23.useMemo(() => {
47041
+ if (!lastHeartbeat) return false;
47042
+ const now2 = /* @__PURE__ */ new Date();
47043
+ const heartbeat = new Date(lastHeartbeat);
47044
+ const minutesSince = (now2.getTime() - heartbeat.getTime()) / 6e4;
47045
+ return minutesSince < 5;
47046
+ }, [lastHeartbeat]);
47047
+ React23.useEffect(() => {
47048
+ console.log("[WorkspaceDetailView] Workspace Health Status:", {
47049
+ workspaceId,
47050
+ // Old workspace_health table
47051
+ oldStatus: workspaceHealth?.status,
47052
+ oldLastHeartbeat: workspaceHealth?.last_heartbeat,
47053
+ oldTimeSinceLastUpdate: workspaceHealth?.timeSinceLastUpdate,
47054
+ // New workspace_health_status table
47055
+ isHealthy: isWorkspaceHealthy,
47056
+ isLive,
47057
+ lastHeartbeat,
47058
+ timeSinceUpdate,
47059
+ loading: healthLoading || healthStatusLoading,
47060
+ error: healthError || healthStatusError
47061
+ });
47062
+ }, [workspaceId, workspaceHealth, isWorkspaceHealthy, isLive, lastHeartbeat, timeSinceUpdate, healthLoading, healthStatusLoading, healthError, healthStatusError]);
46949
47063
  const {
46950
47064
  status: prefetchStatus,
46951
47065
  data: prefetchData,
@@ -46982,6 +47096,7 @@ var WorkspaceDetailView = ({
46982
47096
  const workspace = isHistoricView ? historicMetrics : liveMetrics;
46983
47097
  const loading = isHistoricView ? historicLoading : liveLoading;
46984
47098
  const error = isHistoricView ? historicError : liveError;
47099
+ const { supervisorName } = useLineSupervisor(workspace?.line_id || lineId);
46985
47100
  React23.useEffect(() => {
46986
47101
  if (onTabChange) {
46987
47102
  onTabChange(activeTab);
@@ -47204,7 +47319,7 @@ var WorkspaceDetailView = ({
47204
47319
  initial: { opacity: 1 },
47205
47320
  animate: { opacity: 1 },
47206
47321
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen w-full flex flex-col bg-slate-50", children: [
47207
- /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-2 sm:py-2.5 lg:py-3 flex flex-col shadow-sm bg-white", children: [
47322
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-3 sm:py-3 lg:py-3.5 flex flex-col shadow-sm bg-white", children: [
47208
47323
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
47209
47324
  /* @__PURE__ */ jsxRuntime.jsx(
47210
47325
  "button",
@@ -47218,15 +47333,15 @@ var WorkspaceDetailView = ({
47218
47333
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center", children: [
47219
47334
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
47220
47335
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900 truncate max-w-[220px]", children: formattedWorkspaceName }),
47221
- workspaceHealth && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
47222
- workspaceHealth.status === "healthy" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47336
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
47337
+ isLive && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47223
47338
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
47224
47339
  "relative inline-flex rounded-full h-2 w-2",
47225
- workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
47340
+ isLive ? "bg-green-500" : "bg-red-500"
47226
47341
  ) })
47227
47342
  ] }) })
47228
47343
  ] }),
47229
- activeTab !== "monthly_history" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
47344
+ activeTab !== "monthly_history" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5 mt-1", children: [
47230
47345
  workspaceHealth && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-gray-500", children: workspaceHealth.timeSinceLastUpdate }),
47231
47346
  /* @__PURE__ */ jsxRuntime.jsx(
47232
47347
  WorkspaceHealthStatusBadge,
@@ -47253,14 +47368,11 @@ var WorkspaceDetailView = ({
47253
47368
  ) }),
47254
47369
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
47255
47370
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: formattedWorkspaceName }),
47256
- workspaceHealth && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
47257
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
47258
- "animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
47259
- workspaceHealth.status === "healthy" ? "bg-green-400" : "bg-red-400"
47260
- ) }),
47371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
47372
+ isLive && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47261
47373
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
47262
47374
  "relative inline-flex rounded-full h-2.5 w-2.5",
47263
- workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
47375
+ isLive ? "bg-green-500" : "bg-red-500"
47264
47376
  ) })
47265
47377
  ] })
47266
47378
  ] }) }),
@@ -49648,6 +49760,34 @@ var InviteUserDialog = ({
49648
49760
  throw new Error(data.error);
49649
49761
  }
49650
49762
  sonner.toast.success(data?.message || "User added successfully!");
49763
+ try {
49764
+ const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
49765
+ if (dashboardUrl) {
49766
+ console.log("Sending welcome email to:", email.trim());
49767
+ const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
49768
+ body: {
49769
+ action: "send-welcome-email",
49770
+ email: email.trim(),
49771
+ company_id: companyId,
49772
+ dashboard_url: dashboardUrl
49773
+ }
49774
+ });
49775
+ if (emailError) {
49776
+ console.error("Failed to send welcome email:", emailError);
49777
+ sonner.toast.warning("User added successfully, but welcome email could not be sent");
49778
+ } else if (emailData?.success) {
49779
+ console.log("Welcome email sent successfully:", emailData);
49780
+ sonner.toast.success("User added and welcome email sent!");
49781
+ } else {
49782
+ console.log("Welcome email response:", emailData);
49783
+ }
49784
+ } else {
49785
+ console.warn("Dashboard URL not available, skipping welcome email");
49786
+ }
49787
+ } catch (emailErr) {
49788
+ console.error("Error sending welcome email:", emailErr);
49789
+ sonner.toast.info("User added successfully (email service unavailable)");
49790
+ }
49651
49791
  onInviteSent?.();
49652
49792
  onClose();
49653
49793
  } catch (err) {
@@ -51440,7 +51580,7 @@ var S3Service = class {
51440
51580
  };
51441
51581
 
51442
51582
  // src/lib/api/optifye-agent.ts
51443
- var OPTIFYE_API_URL = "https://optifye-agent-production.up.railway.app";
51583
+ var OPTIFYE_API_URL = process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app";
51444
51584
  var OptifyeAgentClient = class {
51445
51585
  constructor(apiUrl = OPTIFYE_API_URL) {
51446
51586
  this.apiUrl = apiUrl;
package/dist/index.mjs CHANGED
@@ -198,7 +198,7 @@ var DEFAULT_DATE_TIME_CONFIG = {
198
198
  };
199
199
  var DEFAULT_ENDPOINTS_CONFIG = {
200
200
  whatsapp: "/api/send-whatsapp-direct",
201
- agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://optifye-agent-production.up.railway.app",
201
+ agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app",
202
202
  // Default AGNO API URL
203
203
  // Use environment variable for Slack webhook URL for privacy/security
204
204
  // Note: SLACK_WEBHOOK_URL is server-side only, NEXT_PUBLIC_SLACK_WEBHOOK_URL works client-side but is less secure
@@ -3216,7 +3216,7 @@ var SSEChatClient = class {
3216
3216
  user_id: userId,
3217
3217
  context
3218
3218
  });
3219
- const agnoApiUrl = this.baseUrl || "https://fastapi-production-111f9.up.railway.app";
3219
+ const agnoApiUrl = this.baseUrl || (process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app");
3220
3220
  const endpoint = `${agnoApiUrl}/api/v2/chat`;
3221
3221
  console.log("[SSEClient] Posting directly to AGNO:", endpoint);
3222
3222
  const response = await fetch(endpoint, {
@@ -10709,23 +10709,18 @@ function formatRelativeTime(timestamp) {
10709
10709
  const diffHours = Math.floor(diffMinutes / 60);
10710
10710
  const diffDays = Math.floor(diffHours / 24);
10711
10711
  if (diffSeconds < 60) {
10712
- return `${diffSeconds}s ago`;
10712
+ return "Less than a minute ago";
10713
10713
  }
10714
10714
  if (diffMinutes < 60) {
10715
- const remainingSeconds = diffSeconds % 60;
10716
- if (remainingSeconds === 0) {
10717
- return `${diffMinutes}m ago`;
10718
- }
10719
- return `${diffMinutes}m ${remainingSeconds}s ago`;
10715
+ const minuteLabel = diffMinutes === 1 ? "minute" : "minutes";
10716
+ return `${diffMinutes} ${minuteLabel} ago`;
10720
10717
  }
10721
10718
  if (diffHours < 24) {
10722
- const remainingMinutes = diffMinutes % 60;
10723
- if (remainingMinutes === 0) {
10724
- return `${diffHours}h ago`;
10725
- }
10726
- return `${diffHours}h ${remainingMinutes}m ago`;
10719
+ const hourLabel = diffHours === 1 ? "hour" : "hours";
10720
+ return `${diffHours} ${hourLabel} ago`;
10727
10721
  }
10728
- return `${diffDays}d ago`;
10722
+ const dayLabel = diffDays === 1 ? "day" : "days";
10723
+ return `${diffDays} ${dayLabel} ago`;
10729
10724
  } catch (error) {
10730
10725
  console.error("[formatRelativeTime] Error formatting timestamp:", error);
10731
10726
  return "Unknown";
@@ -10741,8 +10736,8 @@ function getNextUpdateInterval(timestamp) {
10741
10736
  const diffSeconds = Math.floor(diffMs / 1e3);
10742
10737
  const diffMinutes = Math.floor(diffSeconds / 60);
10743
10738
  const diffHours = Math.floor(diffMinutes / 60);
10744
- if (diffSeconds < 60) return 1e3;
10745
- if (diffMinutes < 60) return 1e3;
10739
+ if (diffSeconds < 60) return 1e4;
10740
+ if (diffMinutes < 60) return 6e4;
10746
10741
  if (diffHours < 24) return 6e4;
10747
10742
  return 36e5;
10748
10743
  } catch (error) {
@@ -10919,50 +10914,129 @@ function useAccessControl() {
10919
10914
  const userRole = useMemo(() => {
10920
10915
  if (!user?.role_level) return null;
10921
10916
  const roleLevel = user.role_level;
10922
- if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor") {
10917
+ if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor" || roleLevel === "optifye") {
10923
10918
  return roleLevel;
10924
10919
  }
10925
10920
  return "supervisor";
10926
10921
  }, [user?.role_level]);
10927
- const allPages = [
10928
- "/",
10929
- "/leaderboard",
10930
- "/kpis",
10931
- "/targets",
10932
- "/shifts",
10933
- "/supervisor-management",
10934
- "/skus",
10935
- "/ai-agent",
10936
- "/help",
10937
- "/health",
10938
- "/profile",
10939
- "/workspace",
10940
- "/factory-view"
10941
- ];
10922
+ const assignedLineIds = useMemo(() => {
10923
+ if (!user) return [];
10924
+ if (user.role_level === "supervisor") {
10925
+ const lines = user.properties?.line_id || user.properties?.line_ids || [];
10926
+ return Array.isArray(lines) ? lines : [];
10927
+ }
10928
+ return [];
10929
+ }, [user]);
10930
+ const assignedFactoryIds = useMemo(() => {
10931
+ if (!user) return [];
10932
+ if (user.role_level === "plant_head") {
10933
+ const factories = user.properties?.factory_id || user.properties?.factory_ids || [];
10934
+ return Array.isArray(factories) ? factories : [];
10935
+ }
10936
+ return [];
10937
+ }, [user]);
10938
+ const roleAccessMap = {
10939
+ optifye: [
10940
+ "/",
10941
+ "/leaderboard",
10942
+ "/kpis",
10943
+ "/targets",
10944
+ "/shifts",
10945
+ "/supervisor-management",
10946
+ "/skus",
10947
+ "/ai-agent",
10948
+ "/help",
10949
+ "/health",
10950
+ "/profile",
10951
+ "/workspace",
10952
+ "/factory-view",
10953
+ "/team-management"
10954
+ ],
10955
+ owner: [
10956
+ "/",
10957
+ "/leaderboard",
10958
+ "/kpis",
10959
+ "/targets",
10960
+ "/shifts",
10961
+ "/supervisor-management",
10962
+ "/skus",
10963
+ "/ai-agent",
10964
+ "/help",
10965
+ "/health",
10966
+ "/profile",
10967
+ "/workspace",
10968
+ "/factory-view",
10969
+ "/team-management"
10970
+ ],
10971
+ plant_head: [
10972
+ "/",
10973
+ "/leaderboard",
10974
+ "/kpis",
10975
+ "/targets",
10976
+ "/shifts",
10977
+ "/supervisor-management",
10978
+ "/skus",
10979
+ "/ai-agent",
10980
+ "/help",
10981
+ "/health",
10982
+ "/profile",
10983
+ "/workspace",
10984
+ "/factory-view",
10985
+ "/team-management"
10986
+ ],
10987
+ supervisor: [
10988
+ "/",
10989
+ "/leaderboard",
10990
+ "/kpis",
10991
+ "/targets",
10992
+ "/shifts",
10993
+ "/skus",
10994
+ "/ai-agent",
10995
+ "/help",
10996
+ "/health",
10997
+ "/profile",
10998
+ "/workspace"
10999
+ ]
11000
+ };
10942
11001
  const accessiblePages = useMemo(() => {
10943
- return allPages;
10944
- }, []);
11002
+ if (!userRole) return [];
11003
+ return roleAccessMap[userRole] || [];
11004
+ }, [userRole]);
10945
11005
  const hasAccess = useMemo(() => {
10946
11006
  return (path) => {
10947
- return true;
11007
+ if (!userRole) return false;
11008
+ const basePath = path.split("?")[0].split("/").slice(0, 2).join("/");
11009
+ const hasBaseAccess = accessiblePages.includes(basePath) || accessiblePages.includes("/");
11010
+ if (userRole === "supervisor" && assignedLineIds.length > 0) {
11011
+ if (path.includes("/kpis/") || path.includes("/workspace/")) {
11012
+ const lineIdMatch = path.match(/\/kpis\/([^/?]+)/);
11013
+ if (lineIdMatch) {
11014
+ const pathLineId = lineIdMatch[1];
11015
+ return assignedLineIds.includes(pathLineId);
11016
+ }
11017
+ }
11018
+ }
11019
+ return hasBaseAccess;
10948
11020
  };
10949
- }, []);
11021
+ }, [userRole, accessiblePages, assignedLineIds]);
10950
11022
  const isPageVisible = useMemo(() => {
10951
11023
  return (path) => {
10952
- return true;
11024
+ return hasAccess(path);
10953
11025
  };
10954
- }, []);
11026
+ }, [hasAccess]);
10955
11027
  const canAccessPage = useMemo(() => {
10956
11028
  return (path) => {
10957
- return true;
11029
+ return hasAccess(path);
10958
11030
  };
10959
- }, []);
11031
+ }, [hasAccess]);
10960
11032
  return {
10961
11033
  userRole,
10962
11034
  hasAccess,
10963
11035
  accessiblePages,
10964
11036
  isPageVisible,
10965
- canAccessPage
11037
+ canAccessPage,
11038
+ assignedLineIds,
11039
+ assignedFactoryIds
10966
11040
  };
10967
11041
  }
10968
11042
 
@@ -11126,45 +11200,51 @@ function useHasLineAccess(lineId, configLineIds) {
11126
11200
  function useLineSupervisor(lineId) {
11127
11201
  const supabase = useSupabase();
11128
11202
  const [supervisor, setSupervisor] = useState(null);
11203
+ const [supervisors, setSupervisors] = useState([]);
11129
11204
  const [isLoading, setIsLoading] = useState(true);
11130
11205
  const [error, setError] = useState(null);
11131
- console.log("[useLineSupervisor] Hook initialized for lineId:", lineId);
11132
11206
  const fetchSupervisor = useCallback(async () => {
11133
11207
  if (!lineId || !supabase) {
11134
- console.log("[useLineSupervisor] Skipping fetch - lineId or supabase missing:", { lineId, hasSupabase: !!supabase });
11135
11208
  setIsLoading(false);
11136
11209
  return;
11137
11210
  }
11138
11211
  try {
11139
11212
  setIsLoading(true);
11140
11213
  setError(null);
11141
- console.log("[useLineSupervisor] Fetching supervisor for lineId:", lineId);
11142
- const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor").filter("properties->line_id", "@>", `["${lineId}"]`).limit(1);
11143
- console.log("[useLineSupervisor] Query result:", { data, error: fetchError, lineId });
11214
+ const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor");
11144
11215
  if (fetchError) {
11145
11216
  console.error("[useLineSupervisor] Query error:", fetchError);
11146
11217
  throw fetchError;
11147
11218
  }
11148
11219
  if (data && data.length > 0) {
11149
- const supervisorData = data[0];
11150
- console.log("[useLineSupervisor] Found supervisor:", {
11151
- email: supervisorData.email,
11152
- properties: supervisorData.properties,
11153
- line_id_in_properties: supervisorData.properties?.line_id
11154
- });
11155
- const displayName = supervisorData.email.split("@")[0] || supervisorData.email;
11156
- setSupervisor({
11157
- userId: supervisorData.user_id,
11158
- email: supervisorData.email,
11159
- displayName
11220
+ const supervisorsForLine = data.filter((supervisorData) => {
11221
+ const lineIds = supervisorData.properties?.line_id || supervisorData.properties?.line_ids || [];
11222
+ const isAssigned = Array.isArray(lineIds) && lineIds.includes(lineId);
11223
+ return isAssigned;
11160
11224
  });
11225
+ if (supervisorsForLine.length > 0) {
11226
+ const allSupervisors = supervisorsForLine.map((supervisorData) => {
11227
+ const displayName = supervisorData.email.split("@")[0] || supervisorData.email;
11228
+ return {
11229
+ userId: supervisorData.user_id,
11230
+ email: supervisorData.email,
11231
+ displayName
11232
+ };
11233
+ });
11234
+ setSupervisors(allSupervisors);
11235
+ setSupervisor(allSupervisors[0]);
11236
+ } else {
11237
+ setSupervisors([]);
11238
+ setSupervisor(null);
11239
+ }
11161
11240
  } else {
11162
- console.log("[useLineSupervisor] No supervisor found for line:", lineId);
11241
+ setSupervisors([]);
11163
11242
  setSupervisor(null);
11164
11243
  }
11165
11244
  } catch (err) {
11166
- console.error("[useLineSupervisor] Error fetching supervisor:", err);
11167
- setError(err instanceof Error ? err : new Error("Failed to fetch supervisor"));
11245
+ console.error("[useLineSupervisor] Error fetching supervisors:", err);
11246
+ setError(err instanceof Error ? err : new Error("Failed to fetch supervisors"));
11247
+ setSupervisors([]);
11168
11248
  setSupervisor(null);
11169
11249
  } finally {
11170
11250
  setIsLoading(false);
@@ -11189,7 +11269,6 @@ function useLineSupervisor(lineId) {
11189
11269
  filter: `role_level=eq.supervisor`
11190
11270
  },
11191
11271
  (payload) => {
11192
- console.log("[useLineSupervisor] Real-time update received:", payload);
11193
11272
  fetchSupervisor();
11194
11273
  }
11195
11274
  ).subscribe();
@@ -11201,9 +11280,11 @@ function useLineSupervisor(lineId) {
11201
11280
  }
11202
11281
  };
11203
11282
  }, [lineId, supabase, fetchSupervisor]);
11283
+ const supervisorName = supervisors.length > 0 ? supervisors.map((s) => s.displayName).join(", ") : null;
11204
11284
  return {
11205
- supervisorName: supervisor?.displayName || null,
11285
+ supervisorName,
11206
11286
  supervisor,
11287
+ supervisors,
11207
11288
  isLoading,
11208
11289
  error
11209
11290
  };
@@ -34074,14 +34155,16 @@ var WorkspaceWhatsAppShareButton = ({
34074
34155
  };
34075
34156
  var WorkspacePdfGenerator = ({ workspace, className }) => {
34076
34157
  const [isGenerating, setIsGenerating] = useState(false);
34158
+ const entityConfig = useEntityConfig();
34077
34159
  const generatePDF = async () => {
34078
34160
  setIsGenerating(true);
34079
34161
  try {
34162
+ const lineName = workspace.line_name || getLineDisplayName(entityConfig, workspace.line_id);
34080
34163
  trackCoreEvent("Workspace PDF Export Clicked", {
34081
34164
  workspace_id: workspace.workspace_id,
34082
34165
  line_id: workspace.line_id,
34083
34166
  workspace_name: workspace.workspace_name,
34084
- line_name: workspace.line_name
34167
+ line_name: lineName
34085
34168
  });
34086
34169
  const doc = new jsPDF$1();
34087
34170
  doc.setFontSize(14);
@@ -34102,7 +34185,7 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34102
34185
  doc.setFontSize(32);
34103
34186
  doc.setFont("helvetica", "bold");
34104
34187
  doc.setTextColor(0, 0, 0);
34105
- doc.text("Line 1", 20, 40);
34188
+ doc.text(lineName, 20, 40);
34106
34189
  doc.setFontSize(22);
34107
34190
  doc.setFont("helvetica", "normal");
34108
34191
  doc.setTextColor(40, 40, 40);
@@ -38802,7 +38885,7 @@ var AIAgentView = () => {
38802
38885
  const renderedContentCache = useRef(/* @__PURE__ */ new Map());
38803
38886
  const { createThread, mutate: mutateThreads } = useThreads();
38804
38887
  const { messages, addMessage, setMessages } = useMessages(activeThreadId);
38805
- const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://optifye-agent-production.up.railway.app";
38888
+ const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://fastapi-production-111f9.up.railway.app";
38806
38889
  const sseClient = useMemo(() => {
38807
38890
  console.log("[AIAgentView] Using AGNO API URL:", agnoApiUrl);
38808
38891
  return new SSEChatClient(agnoApiUrl);
@@ -46908,6 +46991,7 @@ var WorkspaceDetailView = ({
46908
46991
  const [showIdleTime, setShowIdleTime] = useState(false);
46909
46992
  const dashboardConfig = useDashboardConfig();
46910
46993
  const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
46994
+ dashboardConfig?.supervisorConfig?.enabled || false;
46911
46995
  const {
46912
46996
  workspace: workspaceHealth,
46913
46997
  loading: healthLoading,
@@ -46916,6 +47000,36 @@ var WorkspaceDetailView = ({
46916
47000
  enableRealtime: true,
46917
47001
  refreshInterval: 3e4
46918
47002
  });
47003
+ const {
47004
+ isHealthy: isWorkspaceHealthy,
47005
+ timeSinceUpdate,
47006
+ lastHeartbeat,
47007
+ loading: healthStatusLoading,
47008
+ error: healthStatusError
47009
+ } = useWorkspaceHealthStatus(workspaceId);
47010
+ const isLive = useMemo(() => {
47011
+ if (!lastHeartbeat) return false;
47012
+ const now2 = /* @__PURE__ */ new Date();
47013
+ const heartbeat = new Date(lastHeartbeat);
47014
+ const minutesSince = (now2.getTime() - heartbeat.getTime()) / 6e4;
47015
+ return minutesSince < 5;
47016
+ }, [lastHeartbeat]);
47017
+ useEffect(() => {
47018
+ console.log("[WorkspaceDetailView] Workspace Health Status:", {
47019
+ workspaceId,
47020
+ // Old workspace_health table
47021
+ oldStatus: workspaceHealth?.status,
47022
+ oldLastHeartbeat: workspaceHealth?.last_heartbeat,
47023
+ oldTimeSinceLastUpdate: workspaceHealth?.timeSinceLastUpdate,
47024
+ // New workspace_health_status table
47025
+ isHealthy: isWorkspaceHealthy,
47026
+ isLive,
47027
+ lastHeartbeat,
47028
+ timeSinceUpdate,
47029
+ loading: healthLoading || healthStatusLoading,
47030
+ error: healthError || healthStatusError
47031
+ });
47032
+ }, [workspaceId, workspaceHealth, isWorkspaceHealthy, isLive, lastHeartbeat, timeSinceUpdate, healthLoading, healthStatusLoading, healthError, healthStatusError]);
46919
47033
  const {
46920
47034
  status: prefetchStatus,
46921
47035
  data: prefetchData,
@@ -46952,6 +47066,7 @@ var WorkspaceDetailView = ({
46952
47066
  const workspace = isHistoricView ? historicMetrics : liveMetrics;
46953
47067
  const loading = isHistoricView ? historicLoading : liveLoading;
46954
47068
  const error = isHistoricView ? historicError : liveError;
47069
+ const { supervisorName } = useLineSupervisor(workspace?.line_id || lineId);
46955
47070
  useEffect(() => {
46956
47071
  if (onTabChange) {
46957
47072
  onTabChange(activeTab);
@@ -47174,7 +47289,7 @@ var WorkspaceDetailView = ({
47174
47289
  initial: { opacity: 1 },
47175
47290
  animate: { opacity: 1 },
47176
47291
  children: /* @__PURE__ */ jsxs("div", { className: "min-h-screen w-full flex flex-col bg-slate-50", children: [
47177
- /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-2 sm:py-2.5 lg:py-3 flex flex-col shadow-sm bg-white", children: [
47292
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-3 sm:py-3 lg:py-3.5 flex flex-col shadow-sm bg-white", children: [
47178
47293
  /* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
47179
47294
  /* @__PURE__ */ jsx(
47180
47295
  "button",
@@ -47188,15 +47303,15 @@ var WorkspaceDetailView = ({
47188
47303
  /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center", children: [
47189
47304
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
47190
47305
  /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900 truncate max-w-[220px]", children: formattedWorkspaceName }),
47191
- workspaceHealth && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
47192
- workspaceHealth.status === "healthy" && /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47306
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
47307
+ isLive && /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47193
47308
  /* @__PURE__ */ jsx("span", { className: clsx(
47194
47309
  "relative inline-flex rounded-full h-2 w-2",
47195
- workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
47310
+ isLive ? "bg-green-500" : "bg-red-500"
47196
47311
  ) })
47197
47312
  ] }) })
47198
47313
  ] }),
47199
- activeTab !== "monthly_history" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
47314
+ activeTab !== "monthly_history" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5 mt-1", children: [
47200
47315
  workspaceHealth && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-500", children: workspaceHealth.timeSinceLastUpdate }),
47201
47316
  /* @__PURE__ */ jsx(
47202
47317
  WorkspaceHealthStatusBadge,
@@ -47223,14 +47338,11 @@ var WorkspaceDetailView = ({
47223
47338
  ) }),
47224
47339
  /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
47225
47340
  /* @__PURE__ */ jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: formattedWorkspaceName }),
47226
- workspaceHealth && /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
47227
- /* @__PURE__ */ jsx("span", { className: clsx(
47228
- "animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
47229
- workspaceHealth.status === "healthy" ? "bg-green-400" : "bg-red-400"
47230
- ) }),
47341
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
47342
+ isLive && /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47231
47343
  /* @__PURE__ */ jsx("span", { className: clsx(
47232
47344
  "relative inline-flex rounded-full h-2.5 w-2.5",
47233
- workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
47345
+ isLive ? "bg-green-500" : "bg-red-500"
47234
47346
  ) })
47235
47347
  ] })
47236
47348
  ] }) }),
@@ -49618,6 +49730,34 @@ var InviteUserDialog = ({
49618
49730
  throw new Error(data.error);
49619
49731
  }
49620
49732
  toast.success(data?.message || "User added successfully!");
49733
+ try {
49734
+ const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
49735
+ if (dashboardUrl) {
49736
+ console.log("Sending welcome email to:", email.trim());
49737
+ const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
49738
+ body: {
49739
+ action: "send-welcome-email",
49740
+ email: email.trim(),
49741
+ company_id: companyId,
49742
+ dashboard_url: dashboardUrl
49743
+ }
49744
+ });
49745
+ if (emailError) {
49746
+ console.error("Failed to send welcome email:", emailError);
49747
+ toast.warning("User added successfully, but welcome email could not be sent");
49748
+ } else if (emailData?.success) {
49749
+ console.log("Welcome email sent successfully:", emailData);
49750
+ toast.success("User added and welcome email sent!");
49751
+ } else {
49752
+ console.log("Welcome email response:", emailData);
49753
+ }
49754
+ } else {
49755
+ console.warn("Dashboard URL not available, skipping welcome email");
49756
+ }
49757
+ } catch (emailErr) {
49758
+ console.error("Error sending welcome email:", emailErr);
49759
+ toast.info("User added successfully (email service unavailable)");
49760
+ }
49621
49761
  onInviteSent?.();
49622
49762
  onClose();
49623
49763
  } catch (err) {
@@ -51410,7 +51550,7 @@ var S3Service = class {
51410
51550
  };
51411
51551
 
51412
51552
  // src/lib/api/optifye-agent.ts
51413
- var OPTIFYE_API_URL = "https://optifye-agent-production.up.railway.app";
51553
+ var OPTIFYE_API_URL = process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app";
51414
51554
  var OptifyeAgentClient = class {
51415
51555
  constructor(apiUrl = OPTIFYE_API_URL) {
51416
51556
  this.apiUrl = apiUrl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.9.1",
3
+ "version": "6.9.2",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",