@optifye/dashboard-core 6.10.47 → 6.10.48

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
@@ -3918,6 +3918,10 @@ var AuthService = class {
3918
3918
  email: enrichedUser.email,
3919
3919
  role: enrichedUser.role,
3920
3920
  role_level: enrichedUser.role,
3921
+ scope_mode: enrichedUser.scope_mode,
3922
+ scope_generated_at: enrichedUser.scope_generated_at,
3923
+ scope_ttl_seconds: enrichedUser.scope_ttl_seconds,
3924
+ access_scope: enrichedUser.access_scope,
3921
3925
  company_id: enrichedUser.company_id,
3922
3926
  first_login_completed: enrichedUser.profile.first_login_completed,
3923
3927
  properties: {
@@ -8124,6 +8128,8 @@ var AuthProvider = ({ children }) => {
8124
8128
  const isFetchingRef = useRef(false);
8125
8129
  const lastProcessedSessionRef = useRef(null);
8126
8130
  const hasAuthenticatedRef = useRef(false);
8131
+ const scopeFetchedAtRef = useRef(0);
8132
+ const scopeTtlSecondsRef = useRef(300);
8127
8133
  const fetchSession = useCallback(async (supabaseSession, isReauth = false, allowRetry = true) => {
8128
8134
  if (isFetchingRef.current) {
8129
8135
  console.log("[AuthContext] Already fetching, skipping duplicate request");
@@ -8136,6 +8142,9 @@ var AuthProvider = ({ children }) => {
8136
8142
  }
8137
8143
  try {
8138
8144
  const enrichedUser = await AuthService.getSession(supabaseSession.access_token);
8145
+ const parsedScopeGeneratedAt = enrichedUser.scope_generated_at ? Date.parse(enrichedUser.scope_generated_at) : NaN;
8146
+ scopeFetchedAtRef.current = Number.isFinite(parsedScopeGeneratedAt) ? parsedScopeGeneratedAt : Date.now();
8147
+ scopeTtlSecondsRef.current = enrichedUser.scope_ttl_seconds || 300;
8139
8148
  const authUser = AuthService.toAuthUser(enrichedUser);
8140
8149
  setUser(authUser);
8141
8150
  setSession(supabaseSession);
@@ -8225,6 +8234,8 @@ var AuthProvider = ({ children }) => {
8225
8234
  setError(null);
8226
8235
  setShowOnboarding(false);
8227
8236
  hasAuthenticatedRef.current = false;
8237
+ scopeFetchedAtRef.current = 0;
8238
+ scopeTtlSecondsRef.current = 300;
8228
8239
  router.push("/login");
8229
8240
  } catch (err) {
8230
8241
  console.error("[AuthContext] Sign out error:", err);
@@ -8287,6 +8298,44 @@ var AuthProvider = ({ children }) => {
8287
8298
  clearInterval(intervalId);
8288
8299
  };
8289
8300
  }, [session, supabase]);
8301
+ useEffect(() => {
8302
+ if (!session || !user) {
8303
+ return;
8304
+ }
8305
+ const refreshScopeIfExpired = async () => {
8306
+ if (!session || isFetchingRef.current) {
8307
+ return;
8308
+ }
8309
+ const ttlSeconds = user.scope_ttl_seconds || scopeTtlSecondsRef.current || 300;
8310
+ const fetchedAt = scopeFetchedAtRef.current || 0;
8311
+ const shouldRefresh = fetchedAt === 0 || Date.now() - fetchedAt >= ttlSeconds * 1e3;
8312
+ if (!shouldRefresh) {
8313
+ return;
8314
+ }
8315
+ try {
8316
+ console.log("[AuthContext] Scope TTL expired, refreshing session scope");
8317
+ await fetchSession(session, true);
8318
+ } catch (err) {
8319
+ console.error("[AuthContext] Scope refresh failed:", err);
8320
+ }
8321
+ };
8322
+ refreshScopeIfExpired();
8323
+ const intervalId = setInterval(refreshScopeIfExpired, 6e4);
8324
+ return () => clearInterval(intervalId);
8325
+ }, [session, user?.id, user?.scope_ttl_seconds, fetchSession]);
8326
+ useEffect(() => {
8327
+ if (typeof window === "undefined") {
8328
+ return;
8329
+ }
8330
+ const handleScopeRefresh = () => {
8331
+ if (!session) {
8332
+ return;
8333
+ }
8334
+ void fetchSession(session, true);
8335
+ };
8336
+ window.addEventListener("rbac:refresh-scope", handleScopeRefresh);
8337
+ return () => window.removeEventListener("rbac:refresh-scope", handleScopeRefresh);
8338
+ }, [session, fetchSession]);
8290
8339
  useEffect(() => {
8291
8340
  if (!supabase) {
8292
8341
  console.log("[AuthContext] No Supabase client, skipping auth initialization");
@@ -8355,7 +8404,8 @@ var AuthProvider = ({ children }) => {
8355
8404
  } else if (event === "TOKEN_REFRESHED") {
8356
8405
  console.log("[AuthContext] Token refreshed automatically");
8357
8406
  if (currentSession) {
8358
- setSession(currentSession);
8407
+ const isReauth = hasAuthenticatedRef.current;
8408
+ await fetchSession(currentSession, isReauth);
8359
8409
  const expiresAt = currentSession.expires_at;
8360
8410
  if (expiresAt) {
8361
8411
  const minutesUntilExpiry = Math.floor((expiresAt * 1e3 - Date.now()) / 6e4);
@@ -10503,12 +10553,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10503
10553
  const configuredLineIds = useMemo(() => {
10504
10554
  return getConfiguredLineIds(entityConfig);
10505
10555
  }, [entityConfig]);
10556
+ const targetFactoryLineIds = useMemo(() => {
10557
+ const sourceLineIds = userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
10558
+ return Array.from(new Set((sourceLineIds || []).filter(Boolean)));
10559
+ }, [userAccessibleLineIds, configuredLineIds]);
10506
10560
  const { shiftConfig: staticShiftConfig } = useDashboardConfig();
10507
10561
  const {
10508
10562
  shiftConfigMap: multiLineShiftConfigMap,
10509
10563
  isLoading: isMultiLineShiftConfigLoading
10510
10564
  } = useMultiLineShiftConfigs(
10511
- isFactoryView ? configuredLineIds : [],
10565
+ isFactoryView ? targetFactoryLineIds : [],
10512
10566
  staticShiftConfig
10513
10567
  );
10514
10568
  const {
@@ -10603,7 +10657,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10603
10657
  return;
10604
10658
  }
10605
10659
  const isFactory = currentLineIdToUse === factoryViewId;
10606
- const targetLineIds = isFactory ? userAccessibleLineIds || configuredLineIds : [currentLineIdToUse];
10660
+ const targetLineIds = isFactory ? targetFactoryLineIds : [currentLineIdToUse];
10607
10661
  const targetLineIdsKey = targetLineIds.slice().sort().join(",");
10608
10662
  const usesShiftGroups = isFactory && shiftGroups.length > 0;
10609
10663
  const singleShiftDetails = usesShiftGroups ? null : shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(defaultTimezone, staticShiftConfig);
@@ -10652,7 +10706,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10652
10706
  throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
10653
10707
  }
10654
10708
  if (targetLineIds.length === 0) {
10655
- throw new Error("No target line IDs available for fetching metrics.");
10709
+ logDebug("[useDashboardMetrics] Skipping fetch: no target line IDs after scope filtering");
10710
+ setMetrics({
10711
+ workspaceMetrics: [],
10712
+ lineMetrics: [],
10713
+ metadata: void 0,
10714
+ efficiencyLegend: DEFAULT_EFFICIENCY_LEGEND
10715
+ });
10716
+ setMetricsLineId(requestLineId);
10717
+ lastFetchKeyRef.current = inFlightFetchKeyRef.current;
10718
+ return;
10656
10719
  }
10657
10720
  let allWorkspaceMetrics = [];
10658
10721
  let allLineMetrics = [];
@@ -10670,11 +10733,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10670
10733
  lineCount: group.lineIds.length
10671
10734
  }))
10672
10735
  });
10736
+ const targetLineIdSet = new Set(targetLineIds);
10673
10737
  const metricsPromises = shiftGroups.map(async (group) => {
10674
- const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
10738
+ const groupLineIds = group.lineIds.filter((lineGroupId) => targetLineIdSet.has(lineGroupId));
10739
+ if (groupLineIds.length === 0) {
10740
+ return null;
10741
+ }
10742
+ const lineIdsParam = `line_ids=${groupLineIds.join(",")}`;
10675
10743
  const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${companyId}${forceParam}`;
10676
10744
  logDebug(`[useDashboardMetrics] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
10677
- lineIds: group.lineIds,
10745
+ lineIds: groupLineIds,
10678
10746
  date: group.date
10679
10747
  });
10680
10748
  const response = await fetch(url, {
@@ -10698,7 +10766,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10698
10766
  throw new Error(`Invalid JSON response from backend`);
10699
10767
  }
10700
10768
  });
10701
- const results = await Promise.all(metricsPromises);
10769
+ const results = (await Promise.all(metricsPromises)).filter((result) => !!result);
10702
10770
  hasFlowBuffers = results.some((result) => result?.metadata?.has_flow_buffers);
10703
10771
  results.forEach((result) => {
10704
10772
  const idleMap = result?.metadata?.idle_time_vlm_by_line;
@@ -10876,6 +10944,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10876
10944
  shiftGroups,
10877
10945
  shiftGroupsKey,
10878
10946
  configuredLineIds,
10947
+ targetFactoryLineIds,
10879
10948
  isFactoryView,
10880
10949
  multiLineShiftConfigMap,
10881
10950
  staticShiftConfig,
@@ -10968,8 +11037,14 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10968
11037
  logDebug("[useDashboardMetrics] Setting up group subscriptions:", {
10969
11038
  groupCount: currentShiftGroups.length
10970
11039
  });
11040
+ const targetFactoryLineIdsForSubscriptions = currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
11041
+ const targetFactoryLineIdSet = new Set(
11042
+ (targetFactoryLineIdsForSubscriptions || []).filter(Boolean)
11043
+ );
10971
11044
  currentShiftGroups.forEach((group, index) => {
10972
- const groupLineIds = group.lineIds.filter((id3) => id3 && id3 !== factoryViewIdentifier);
11045
+ const groupLineIds = group.lineIds.filter(
11046
+ (id3) => id3 && id3 !== factoryViewIdentifier && targetFactoryLineIdSet.has(id3)
11047
+ );
10973
11048
  if (groupLineIds.length === 0) {
10974
11049
  logDebug("[useDashboardMetrics] Skipping group subscription: no line IDs after filtering", {
10975
11050
  groupIndex: index,
@@ -16148,6 +16223,7 @@ function useAccessControl() {
16148
16223
  function useTeamManagementPermissions() {
16149
16224
  const { user } = useAuth();
16150
16225
  const currentRole = user?.role_level;
16226
+ const isSuperAdminOptifye = currentRole === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
16151
16227
  return {
16152
16228
  /**
16153
16229
  * Can the current user assign lines to this user?
@@ -16203,7 +16279,10 @@ function useTeamManagementPermissions() {
16203
16279
  availableRolesToAssign: () => {
16204
16280
  if (!currentRole) return [];
16205
16281
  if (currentRole === "optifye") {
16206
- return ["owner", "it", "plant_head", "supervisor"];
16282
+ if (isSuperAdminOptifye) {
16283
+ return ["owner", "it", "plant_head", "supervisor"];
16284
+ }
16285
+ return ["it", "plant_head", "supervisor"];
16207
16286
  }
16208
16287
  if (currentRole === "owner") {
16209
16288
  return ["it", "plant_head", "supervisor"];
@@ -16221,7 +16300,12 @@ function useTeamManagementPermissions() {
16221
16300
  */
16222
16301
  canInviteRole: (role) => {
16223
16302
  if (!currentRole) return false;
16224
- if (currentRole === "optifye") return true;
16303
+ if (currentRole === "optifye") {
16304
+ if (role === "owner") {
16305
+ return isSuperAdminOptifye;
16306
+ }
16307
+ return true;
16308
+ }
16225
16309
  if (currentRole === "owner") {
16226
16310
  return ["it", "plant_head", "supervisor"].includes(role);
16227
16311
  }
@@ -16255,40 +16339,133 @@ function useTeamManagementPermissions() {
16255
16339
  }
16256
16340
  function useUserLineAccess(configLineIds) {
16257
16341
  const { user } = useAuth();
16342
+ const { lines: dbLines } = useLines();
16258
16343
  const { accessibleLineIds, defaultLineId } = useMemo(() => {
16344
+ const dbLineIds = dbLines.map((line) => line.id);
16345
+ const dbLineIdSet = new Set(dbLineIds);
16346
+ const fallbackAllLineIds = configLineIds && configLineIds.length > 0 ? configLineIds : dbLineIds;
16347
+ const normalizeIds = (value) => {
16348
+ if (Array.isArray(value)) {
16349
+ return value.filter((id3) => typeof id3 === "string" && !!id3);
16350
+ }
16351
+ if (typeof value === "string" && value) {
16352
+ return [value];
16353
+ }
16354
+ return [];
16355
+ };
16356
+ const uniqueIds = (ids) => Array.from(new Set(ids.filter(Boolean)));
16357
+ const filterToEnabledDb = (lineIds, options = {}) => {
16358
+ const deduped = uniqueIds(lineIds);
16359
+ if (dbLineIdSet.size === 0) {
16360
+ return options.allowWhenDbUnavailable ? deduped : [];
16361
+ }
16362
+ return deduped.filter((lineId) => dbLineIdSet.has(lineId));
16363
+ };
16259
16364
  if (!user) {
16260
- return { accessibleLineIds: configLineIds || [], defaultLineId: configLineIds?.[0] };
16261
- }
16262
- if (user.role_level === "supervisor") {
16263
- const assignedLineIds = user.properties?.line_id || user.properties?.line_ids || [];
16264
- if (Array.isArray(assignedLineIds) && assignedLineIds.length > 0) {
16265
- console.log("[useUserLineAccess] Supervisor has assigned lines:", assignedLineIds);
16266
- return {
16267
- accessibleLineIds: assignedLineIds,
16268
- defaultLineId: assignedLineIds[0]
16269
- };
16365
+ const fallback = filterToEnabledDb(fallbackAllLineIds, { allowWhenDbUnavailable: true });
16366
+ return { accessibleLineIds: fallback, defaultLineId: fallback[0] };
16367
+ }
16368
+ const configSet = new Set(configLineIds || []);
16369
+ const hasScopePayload = !!user.access_scope || !!user.scope_mode;
16370
+ const role = user.role_level || user.role;
16371
+ const scopedLineIds = normalizeIds(user.access_scope?.line_ids);
16372
+ const scopedFactoryIds = normalizeIds(user.access_scope?.factory_ids);
16373
+ const properties = user.properties || {};
16374
+ const propertyLineIds = normalizeIds(properties?.line_ids || properties?.line_id);
16375
+ const propertyFactoryIds = normalizeIds(properties?.factory_ids || properties?.factory_id);
16376
+ const propertyCompanyId = typeof properties?.company_id === "string" ? properties.company_id : user.company_id;
16377
+ const scopedCompanyId = user.access_scope?.company_id || propertyCompanyId;
16378
+ const isExplicitSuperAdmin = user.scope_mode === "SUPER_ADMIN" || !!user.access_scope?.is_super_admin;
16379
+ const isLegacyOptifyeGlobal = !hasScopePayload && role === "optifye" && propertyLineIds.length === 0 && propertyFactoryIds.length === 0 && !propertyCompanyId;
16380
+ const lineFactoryMap = /* @__PURE__ */ new Map();
16381
+ dbLines.forEach((line) => {
16382
+ lineFactoryMap.set(line.id, line.factory_id);
16383
+ });
16384
+ const applyConfigFilter = (lineIds) => {
16385
+ const dedupedLineIds = uniqueIds(lineIds);
16386
+ if (!configLineIds || configLineIds.length === 0) {
16387
+ return dedupedLineIds;
16270
16388
  }
16271
- console.warn("[useUserLineAccess] Supervisor has NO assigned lines!", {
16272
- properties: user.properties,
16273
- hasLineId: !!user.properties?.line_id,
16274
- hasLineIds: !!user.properties?.line_ids
16389
+ const filtered = dedupedLineIds.filter((id3) => configSet.has(id3));
16390
+ return filtered.length > 0 ? filtered : dedupedLineIds;
16391
+ };
16392
+ const resolveFactoryScopedLines = (factoryIds) => {
16393
+ const factorySet = new Set(factoryIds);
16394
+ const baseLineIds = configLineIds && configLineIds.length > 0 ? configLineIds : dbLineIds;
16395
+ const filtered = baseLineIds.filter((lineId) => {
16396
+ const factoryId = lineFactoryMap.get(lineId);
16397
+ return !!factoryId && factorySet.has(factoryId);
16275
16398
  });
16399
+ if (filtered.length > 0) {
16400
+ return uniqueIds(filtered);
16401
+ }
16402
+ const fromDb = dbLines.filter((line) => !!line.factory_id && factorySet.has(line.factory_id)).map((line) => line.id);
16403
+ if (fromDb.length > 0) {
16404
+ return uniqueIds(fromDb);
16405
+ }
16406
+ return [];
16407
+ };
16408
+ if (isExplicitSuperAdmin || isLegacyOptifyeGlobal) {
16409
+ const all = filterToEnabledDb(fallbackAllLineIds, { allowWhenDbUnavailable: true });
16276
16410
  return {
16277
- accessibleLineIds: [],
16278
- defaultLineId: void 0
16411
+ accessibleLineIds: all,
16412
+ defaultLineId: all[0]
16279
16413
  };
16280
16414
  }
16281
- if (user.role_level === "plant_head") {
16415
+ if (scopedLineIds.length > 0) {
16416
+ const filtered = applyConfigFilter(scopedLineIds);
16417
+ const scoped = filterToEnabledDb(filtered.length > 0 ? filtered : uniqueIds(scopedLineIds));
16282
16418
  return {
16283
- accessibleLineIds: configLineIds || [],
16284
- defaultLineId: configLineIds?.[0]
16419
+ accessibleLineIds: scoped,
16420
+ defaultLineId: scoped[0]
16285
16421
  };
16286
16422
  }
16287
- return {
16288
- accessibleLineIds: configLineIds || [],
16289
- defaultLineId: configLineIds?.[0]
16290
- };
16291
- }, [user, configLineIds]);
16423
+ if (scopedFactoryIds.length > 0) {
16424
+ const scoped = filterToEnabledDb(resolveFactoryScopedLines(scopedFactoryIds));
16425
+ return {
16426
+ accessibleLineIds: scoped,
16427
+ defaultLineId: scoped[0]
16428
+ };
16429
+ }
16430
+ if (scopedCompanyId) {
16431
+ const dbCompanyLineIds = dbLines.filter((line) => !line.company_id || line.company_id === scopedCompanyId).map((line) => line.id);
16432
+ const companyScopedLines = configLineIds && configLineIds.length > 0 ? (() => {
16433
+ const dbCompanySet = new Set(dbCompanyLineIds);
16434
+ const filtered = configLineIds.filter((lineId) => dbCompanySet.has(lineId));
16435
+ if (filtered.length > 0) return filtered;
16436
+ return dbCompanyLineIds;
16437
+ })() : dbCompanyLineIds;
16438
+ const scoped = filterToEnabledDb(companyScopedLines);
16439
+ return {
16440
+ accessibleLineIds: scoped,
16441
+ defaultLineId: scoped[0]
16442
+ };
16443
+ }
16444
+ if (propertyLineIds.length > 0) {
16445
+ const filtered = applyConfigFilter(propertyLineIds);
16446
+ const scoped = filterToEnabledDb(filtered);
16447
+ return {
16448
+ accessibleLineIds: scoped,
16449
+ defaultLineId: scoped[0]
16450
+ };
16451
+ }
16452
+ if (propertyFactoryIds.length > 0) {
16453
+ const scoped = filterToEnabledDb(resolveFactoryScopedLines(propertyFactoryIds));
16454
+ return {
16455
+ accessibleLineIds: scoped,
16456
+ defaultLineId: scoped[0]
16457
+ };
16458
+ }
16459
+ if (role === "owner" || role === "it" || role === "optifye") {
16460
+ const companyScopedLines = scopedCompanyId ? dbLines.filter((line) => !line.company_id || line.company_id === scopedCompanyId).map((line) => line.id) : [];
16461
+ const scoped = filterToEnabledDb(companyScopedLines);
16462
+ return {
16463
+ accessibleLineIds: scoped,
16464
+ defaultLineId: scoped[0]
16465
+ };
16466
+ }
16467
+ return { accessibleLineIds: [], defaultLineId: void 0 };
16468
+ }, [user, configLineIds, dbLines]);
16292
16469
  return { accessibleLineIds, defaultLineId, user };
16293
16470
  }
16294
16471
  function useActiveLineId(queryLineId, configLineIds) {
@@ -16480,12 +16657,7 @@ var useSupervisorsByLineIds = (lineIds, options) => {
16480
16657
  const nextSupervisorsByLineId = /* @__PURE__ */ new Map();
16481
16658
  const nextAllSupervisorsMap = /* @__PURE__ */ new Map();
16482
16659
  if (useBackend && resolvedCompanyId) {
16483
- const searchParams = new URLSearchParams({
16484
- company_id: resolvedCompanyId
16485
- });
16486
- if (lineIdsKey) {
16487
- searchParams.set("line_ids", lineIdsKey);
16488
- }
16660
+ const searchParams = new URLSearchParams({ company_id: resolvedCompanyId });
16489
16661
  const data = await fetchBackendJson(supabase, `/api/dashboard/line-supervisors?${searchParams.toString()}`);
16490
16662
  (data?.supervisors || []).forEach((row) => {
16491
16663
  const assignedLineIds = Array.isArray(row.line_ids) ? row.line_ids : [];
@@ -28305,8 +28477,52 @@ function withAccessControl(WrappedComponent2, options = {}) {
28305
28477
  const WithAccessControlComponent = (props) => {
28306
28478
  const router = useRouter();
28307
28479
  const { user, loading: authLoading } = useAuth();
28308
- const { canAccessPage, userRole } = useAccessControl();
28309
- requiredPath || router.pathname;
28480
+ const role = user?.role_level;
28481
+ const scope = user?.access_scope;
28482
+ const isSuperAdmin = !!scope?.is_super_admin || user?.scope_mode === "SUPER_ADMIN";
28483
+ const getBasePath = (path) => {
28484
+ const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
28485
+ return firstSegment ? `/${firstSegment}` : "/";
28486
+ };
28487
+ const getIdFromPath = (path, key) => {
28488
+ const queryValue = router.query[key];
28489
+ if (typeof queryValue === "string") return queryValue;
28490
+ if (Array.isArray(queryValue) && queryValue.length > 0) return queryValue[0];
28491
+ const segments = path.split("?")[0].split("/").filter(Boolean);
28492
+ const keyIndex = segments.findIndex((segment) => segment.toLowerCase() === key.toLowerCase());
28493
+ if (keyIndex >= 0 && keyIndex < segments.length - 1) return segments[keyIndex + 1];
28494
+ return null;
28495
+ };
28496
+ const roleAccessMap = {
28497
+ optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28498
+ owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28499
+ it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28500
+ plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28501
+ supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/improvement-center", "/tickets"]
28502
+ };
28503
+ const canAccessPath = (path) => {
28504
+ if (!user || !role) return false;
28505
+ if (isSuperAdmin) return true;
28506
+ const basePath = getBasePath(path);
28507
+ const allowed = roleAccessMap[role] || [];
28508
+ if (!allowed.includes(basePath)) {
28509
+ return false;
28510
+ }
28511
+ const workspaceId = getIdFromPath(path, "workspace");
28512
+ if (workspaceId && scope?.workspace_ids?.length) {
28513
+ return scope.workspace_ids.includes(workspaceId);
28514
+ }
28515
+ const lineId = getIdFromPath(path, "kpis") || getIdFromPath(path, "line");
28516
+ if (lineId && scope?.line_ids?.length) {
28517
+ return scope.line_ids.includes(lineId);
28518
+ }
28519
+ const factoryId = getIdFromPath(path, "factory");
28520
+ if (factoryId && scope?.factory_ids?.length) {
28521
+ return scope.factory_ids.includes(factoryId);
28522
+ }
28523
+ return true;
28524
+ };
28525
+ const pathToCheck = requiredPath || router.asPath || router.pathname;
28310
28526
  if (authLoading) {
28311
28527
  return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
28312
28528
  /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }),
@@ -28316,6 +28532,24 @@ function withAccessControl(WrappedComponent2, options = {}) {
28316
28532
  if (!user) {
28317
28533
  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" }) }) });
28318
28534
  }
28535
+ const hasAccess = canAccessPath(pathToCheck);
28536
+ if (!hasAccess) {
28537
+ if (UnauthorizedComponent) {
28538
+ return /* @__PURE__ */ jsx(UnauthorizedComponent, {});
28539
+ }
28540
+ if (typeof window !== "undefined") {
28541
+ router.replace(redirectTo);
28542
+ }
28543
+ return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
28544
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Access Denied" }),
28545
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-4", children: "You don't have permission to access this page." }),
28546
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
28547
+ "Your role: ",
28548
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: role || "Unknown" })
28549
+ ] }),
28550
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Redirecting to home page..." })
28551
+ ] }) });
28552
+ }
28319
28553
  return /* @__PURE__ */ jsx(WrappedComponent2, { ...props });
28320
28554
  };
28321
28555
  WithAccessControlComponent.displayName = `withAccessControl(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
@@ -48629,9 +48863,29 @@ var SideNavBar = memo$1(({
48629
48863
  }) => {
48630
48864
  const router = useRouter();
48631
48865
  const { navigate } = useNavigation();
48632
- const { signOut } = useAuth();
48866
+ const { signOut, user } = useAuth();
48633
48867
  const entityConfig = useEntityConfig();
48634
48868
  const dashboardConfig = useDashboardConfig();
48869
+ const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
48870
+ const role = user?.role_level;
48871
+ const roleAccessMap = {
48872
+ optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48873
+ owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48874
+ it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48875
+ plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48876
+ supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/tickets", "/improvement-center"]
48877
+ };
48878
+ const getBasePath = useCallback((path) => {
48879
+ const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
48880
+ return firstSegment ? `/${firstSegment}` : "/";
48881
+ }, []);
48882
+ const canAccessPath = useCallback((path) => {
48883
+ if (!role) return false;
48884
+ if (isSuperAdmin) return true;
48885
+ const basePath = getBasePath(path);
48886
+ const allowedPaths = roleAccessMap[role] || [];
48887
+ return allowedPaths.includes(basePath);
48888
+ }, [role, isSuperAdmin, getBasePath]);
48635
48889
  const lineId = entityConfig.defaultLineId || LINE_1_UUID;
48636
48890
  const skuEnabled = dashboardConfig?.skuConfig?.enabled || false;
48637
48891
  dashboardConfig?.supervisorConfig?.enabled || false;
@@ -48797,7 +49051,7 @@ var SideNavBar = memo$1(({
48797
49051
  const settingsTriggerRef = useRef(null);
48798
49052
  const settingsItems = useMemo(() => {
48799
49053
  const items = [
48800
- {
49054
+ ...canAccessPath("/targets") ? [{
48801
49055
  key: "targets",
48802
49056
  label: "Targets",
48803
49057
  icon: AdjustmentsHorizontalIcon,
@@ -48806,8 +49060,8 @@ var SideNavBar = memo$1(({
48806
49060
  setIsSettingsOpen(false);
48807
49061
  },
48808
49062
  isActive: pathname === "/targets" || pathname.startsWith("/targets/")
48809
- },
48810
- {
49063
+ }] : [],
49064
+ ...canAccessPath("/shifts") ? [{
48811
49065
  key: "shifts",
48812
49066
  label: "Shifts",
48813
49067
  icon: ClockIcon,
@@ -48816,8 +49070,8 @@ var SideNavBar = memo$1(({
48816
49070
  setIsSettingsOpen(false);
48817
49071
  },
48818
49072
  isActive: pathname === "/shifts" || pathname.startsWith("/shifts/")
48819
- },
48820
- {
49073
+ }] : [],
49074
+ ...canAccessPath("/team-management") ? [{
48821
49075
  key: "teams",
48822
49076
  label: "Teams",
48823
49077
  icon: UsersIcon,
@@ -48826,8 +49080,8 @@ var SideNavBar = memo$1(({
48826
49080
  setIsSettingsOpen(false);
48827
49081
  },
48828
49082
  isActive: pathname === "/team-management" || pathname.startsWith("/team-management/")
48829
- },
48830
- {
49083
+ }] : [],
49084
+ ...canAccessPath("/profile") ? [{
48831
49085
  key: "profile",
48832
49086
  label: "Profile",
48833
49087
  icon: UserCircleIcon,
@@ -48836,9 +49090,9 @@ var SideNavBar = memo$1(({
48836
49090
  setIsSettingsOpen(false);
48837
49091
  },
48838
49092
  isActive: pathname === "/profile" || pathname.startsWith("/profile/")
48839
- }
49093
+ }] : []
48840
49094
  ];
48841
- if (ticketsEnabled) {
49095
+ if (ticketsEnabled && canAccessPath("/tickets")) {
48842
49096
  items.push({
48843
49097
  key: "tickets",
48844
49098
  label: "Tickets",
@@ -48850,18 +49104,20 @@ var SideNavBar = memo$1(({
48850
49104
  isActive: pathname === "/tickets" || pathname.startsWith("/tickets/")
48851
49105
  });
48852
49106
  }
48853
- items.push({
48854
- key: "help",
48855
- label: "Help",
48856
- icon: QuestionMarkCircleIcon,
48857
- onClick: () => {
48858
- handleHelpClick();
48859
- setIsSettingsOpen(false);
48860
- },
48861
- isActive: pathname === "/help" || pathname.startsWith("/help/")
48862
- });
49107
+ if (canAccessPath("/help")) {
49108
+ items.push({
49109
+ key: "help",
49110
+ label: "Help",
49111
+ icon: QuestionMarkCircleIcon,
49112
+ onClick: () => {
49113
+ handleHelpClick();
49114
+ setIsSettingsOpen(false);
49115
+ },
49116
+ isActive: pathname === "/help" || pathname.startsWith("/help/")
49117
+ });
49118
+ }
48863
49119
  return items;
48864
- }, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled]);
49120
+ }, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled, canAccessPath]);
48865
49121
  const handleLogout = useCallback(async () => {
48866
49122
  setIsSettingsOpen(false);
48867
49123
  try {
@@ -48899,7 +49155,7 @@ var SideNavBar = memo$1(({
48899
49155
  }
48900
49156
  ) }),
48901
49157
  /* @__PURE__ */ jsxs("div", { className: "flex-1 w-full py-6 px-4 overflow-y-auto", children: [
48902
- /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsxs(
49158
+ /* @__PURE__ */ jsx("div", { className: "mb-6", children: canAccessPath("/") && /* @__PURE__ */ jsxs(
48903
49159
  "button",
48904
49160
  {
48905
49161
  onClick: handleHomeClick,
@@ -48915,7 +49171,7 @@ var SideNavBar = memo$1(({
48915
49171
  }
48916
49172
  ) }),
48917
49173
  /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
48918
- /* @__PURE__ */ jsxs(
49174
+ canAccessPath("/leaderboard") && /* @__PURE__ */ jsxs(
48919
49175
  "button",
48920
49176
  {
48921
49177
  onClick: handleLeaderboardClick,
@@ -48930,7 +49186,7 @@ var SideNavBar = memo$1(({
48930
49186
  ]
48931
49187
  }
48932
49188
  ),
48933
- /* @__PURE__ */ jsxs(
49189
+ canAccessPath("/kpis") && /* @__PURE__ */ jsxs(
48934
49190
  "button",
48935
49191
  {
48936
49192
  onClick: handleKPIsClick,
@@ -48945,7 +49201,7 @@ var SideNavBar = memo$1(({
48945
49201
  ]
48946
49202
  }
48947
49203
  ),
48948
- /* @__PURE__ */ jsxs(
49204
+ canAccessPath("/improvement-center") && /* @__PURE__ */ jsxs(
48949
49205
  "button",
48950
49206
  {
48951
49207
  onClick: handleImprovementClick,
@@ -48961,7 +49217,7 @@ var SideNavBar = memo$1(({
48961
49217
  }
48962
49218
  ),
48963
49219
  showSupervisorManagement,
48964
- skuEnabled && true && /* @__PURE__ */ jsxs(
49220
+ skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxs(
48965
49221
  "button",
48966
49222
  {
48967
49223
  onClick: handleSKUsClick,
@@ -48976,7 +49232,7 @@ var SideNavBar = memo$1(({
48976
49232
  ]
48977
49233
  }
48978
49234
  ),
48979
- /* @__PURE__ */ jsxs(
49235
+ canAccessPath("/health") && /* @__PURE__ */ jsxs(
48980
49236
  "button",
48981
49237
  {
48982
49238
  onClick: handleHealthClick,
@@ -48993,7 +49249,7 @@ var SideNavBar = memo$1(({
48993
49249
  )
48994
49250
  ] })
48995
49251
  ] }),
48996
- /* @__PURE__ */ jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxs(
49252
+ settingsItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxs(
48997
49253
  "button",
48998
49254
  {
48999
49255
  ref: settingsTriggerRef,
@@ -49031,7 +49287,7 @@ var SideNavBar = memo$1(({
49031
49287
  };
49032
49288
  };
49033
49289
  return /* @__PURE__ */ jsxs("nav", { className: "px-5 py-6", children: [
49034
- /* @__PURE__ */ jsxs(
49290
+ canAccessPath("/") && /* @__PURE__ */ jsxs(
49035
49291
  "button",
49036
49292
  {
49037
49293
  onClick: handleMobileNavClick(handleHomeClick),
@@ -49044,7 +49300,7 @@ var SideNavBar = memo$1(({
49044
49300
  }
49045
49301
  ),
49046
49302
  /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
49047
- /* @__PURE__ */ jsxs(
49303
+ canAccessPath("/leaderboard") && /* @__PURE__ */ jsxs(
49048
49304
  "button",
49049
49305
  {
49050
49306
  onClick: handleMobileNavClick(handleLeaderboardClick),
@@ -49056,7 +49312,7 @@ var SideNavBar = memo$1(({
49056
49312
  ]
49057
49313
  }
49058
49314
  ),
49059
- /* @__PURE__ */ jsxs(
49315
+ canAccessPath("/kpis") && /* @__PURE__ */ jsxs(
49060
49316
  "button",
49061
49317
  {
49062
49318
  onClick: handleMobileNavClick(handleKPIsClick),
@@ -49068,7 +49324,7 @@ var SideNavBar = memo$1(({
49068
49324
  ]
49069
49325
  }
49070
49326
  ),
49071
- /* @__PURE__ */ jsxs(
49327
+ canAccessPath("/improvement-center") && /* @__PURE__ */ jsxs(
49072
49328
  "button",
49073
49329
  {
49074
49330
  onClick: handleMobileNavClick(handleImprovementClick),
@@ -49081,7 +49337,7 @@ var SideNavBar = memo$1(({
49081
49337
  }
49082
49338
  ),
49083
49339
  showSupervisorManagement,
49084
- skuEnabled && true && /* @__PURE__ */ jsxs(
49340
+ skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxs(
49085
49341
  "button",
49086
49342
  {
49087
49343
  onClick: handleMobileNavClick(handleSKUsClick),
@@ -49093,7 +49349,7 @@ var SideNavBar = memo$1(({
49093
49349
  ]
49094
49350
  }
49095
49351
  ),
49096
- /* @__PURE__ */ jsxs(
49352
+ canAccessPath("/health") && /* @__PURE__ */ jsxs(
49097
49353
  "button",
49098
49354
  {
49099
49355
  onClick: handleMobileNavClick(handleHealthClick),
@@ -49106,10 +49362,10 @@ var SideNavBar = memo$1(({
49106
49362
  }
49107
49363
  )
49108
49364
  ] }),
49109
- /* @__PURE__ */ jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
49365
+ settingsItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
49110
49366
  /* @__PURE__ */ jsx("h3", { className: "px-5 mb-3 text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Settings & Support" }),
49111
49367
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
49112
- /* @__PURE__ */ jsxs(
49368
+ canAccessPath("/targets") && /* @__PURE__ */ jsxs(
49113
49369
  "button",
49114
49370
  {
49115
49371
  onClick: handleMobileNavClick(handleTargetsClick),
@@ -49121,7 +49377,7 @@ var SideNavBar = memo$1(({
49121
49377
  ]
49122
49378
  }
49123
49379
  ),
49124
- /* @__PURE__ */ jsxs(
49380
+ canAccessPath("/shifts") && /* @__PURE__ */ jsxs(
49125
49381
  "button",
49126
49382
  {
49127
49383
  onClick: handleMobileNavClick(handleShiftsClick),
@@ -49133,7 +49389,7 @@ var SideNavBar = memo$1(({
49133
49389
  ]
49134
49390
  }
49135
49391
  ),
49136
- /* @__PURE__ */ jsxs(
49392
+ canAccessPath("/team-management") && /* @__PURE__ */ jsxs(
49137
49393
  "button",
49138
49394
  {
49139
49395
  onClick: handleMobileNavClick(handleTeamManagementClick),
@@ -49145,7 +49401,7 @@ var SideNavBar = memo$1(({
49145
49401
  ]
49146
49402
  }
49147
49403
  ),
49148
- /* @__PURE__ */ jsxs(
49404
+ canAccessPath("/profile") && /* @__PURE__ */ jsxs(
49149
49405
  "button",
49150
49406
  {
49151
49407
  onClick: handleMobileNavClick(handleProfileClick),
@@ -49157,7 +49413,7 @@ var SideNavBar = memo$1(({
49157
49413
  ]
49158
49414
  }
49159
49415
  ),
49160
- ticketsEnabled && /* @__PURE__ */ jsxs(
49416
+ ticketsEnabled && canAccessPath("/tickets") && /* @__PURE__ */ jsxs(
49161
49417
  "button",
49162
49418
  {
49163
49419
  onClick: handleMobileNavClick(handleTicketsClick),
@@ -49169,7 +49425,7 @@ var SideNavBar = memo$1(({
49169
49425
  ]
49170
49426
  }
49171
49427
  ),
49172
- /* @__PURE__ */ jsxs(
49428
+ canAccessPath("/help") && /* @__PURE__ */ jsxs(
49173
49429
  "button",
49174
49430
  {
49175
49431
  onClick: handleMobileNavClick(handleHelpClick),
@@ -51429,9 +51685,19 @@ var InviteUserDialog = ({
51429
51685
  const [factorySearch, setFactorySearch] = useState("");
51430
51686
  const [isSubmitting, setIsSubmitting] = useState(false);
51431
51687
  const [error, setError] = useState(null);
51688
+ const isSuperAdminOptifye = user?.role_level === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
51689
+ const canInviteOwner = isSuperAdminOptifye;
51432
51690
  const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
51433
51691
  const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
51434
51692
  const canInviteSupervisor = ["owner", "it", "plant_head", "optifye"].includes(user?.role_level || "");
51693
+ const invitableRoles = useMemo(() => {
51694
+ const roles = [];
51695
+ if (canInviteOwner) roles.push("owner");
51696
+ if (canInviteIT) roles.push("it");
51697
+ if (canInvitePlantHead) roles.push("plant_head");
51698
+ if (canInviteSupervisor) roles.push("supervisor");
51699
+ return roles;
51700
+ }, [canInviteOwner, canInviteIT, canInvitePlantHead, canInviteSupervisor]);
51435
51701
  const filteredLines = useMemo(() => {
51436
51702
  const search = lineSearch.trim().toLowerCase();
51437
51703
  if (!search) return availableLines;
@@ -51463,6 +51729,14 @@ var InviteUserDialog = ({
51463
51729
  setError(null);
51464
51730
  }
51465
51731
  }, [isOpen]);
51732
+ useEffect(() => {
51733
+ if (!isOpen || invitableRoles.length === 0) {
51734
+ return;
51735
+ }
51736
+ if (!invitableRoles.includes(selectedRole)) {
51737
+ setSelectedRole(invitableRoles[0]);
51738
+ }
51739
+ }, [isOpen, invitableRoles, selectedRole]);
51466
51740
  const validateEmail = (email2) => {
51467
51741
  const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$/;
51468
51742
  return emailRegex.test(email2);
@@ -51492,6 +51766,14 @@ var InviteUserDialog = ({
51492
51766
  setError("Please enter a valid email address");
51493
51767
  return;
51494
51768
  }
51769
+ if (!invitableRoles.includes(selectedRole)) {
51770
+ setError("You do not have permission to assign this role.");
51771
+ return;
51772
+ }
51773
+ if (selectedRole === "owner" && !canInviteOwner) {
51774
+ setError("Only super-admin Optifye users can create owner users.");
51775
+ return;
51776
+ }
51495
51777
  const companyId = entityConfig?.companyId || user?.properties?.company_id;
51496
51778
  if (!companyId) {
51497
51779
  setError("Company ID not found. Please refresh and try again.");
@@ -51672,6 +51954,33 @@ var InviteUserDialog = ({
51672
51954
  /* @__PURE__ */ jsx("span", { className: "text-red-500", children: "*" })
51673
51955
  ] }),
51674
51956
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
51957
+ canInviteOwner && /* @__PURE__ */ jsxs(
51958
+ "label",
51959
+ {
51960
+ className: cn(
51961
+ "flex items-start gap-3 p-4 border-2 rounded-lg cursor-pointer transition-all duration-200",
51962
+ selectedRole === "owner" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"
51963
+ ),
51964
+ children: [
51965
+ /* @__PURE__ */ jsx(
51966
+ "input",
51967
+ {
51968
+ type: "radio",
51969
+ name: "role",
51970
+ value: "owner",
51971
+ checked: selectedRole === "owner",
51972
+ onChange: (e) => setSelectedRole(e.target.value),
51973
+ className: "mt-1",
51974
+ disabled: isSubmitting
51975
+ }
51976
+ ),
51977
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
51978
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 mb-1", children: /* @__PURE__ */ jsx(RoleBadge_default, { role: "owner", size: "sm" }) }),
51979
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "Company owner access. Full management permissions for the selected company dashboard." })
51980
+ ] })
51981
+ ]
51982
+ }
51983
+ ),
51675
51984
  canInviteIT && /* @__PURE__ */ jsxs(
51676
51985
  "label",
51677
51986
  {
@@ -53543,11 +53852,32 @@ var UserManagementTable = ({
53543
53852
  setSortDirection("asc");
53544
53853
  }
53545
53854
  };
53855
+ const getFactoryIdsFromUser = (user) => {
53856
+ const properties = user.properties || {};
53857
+ const rawFactoryIds = properties.factory_ids ?? properties.factory_id;
53858
+ if (Array.isArray(rawFactoryIds)) {
53859
+ return rawFactoryIds.filter((factoryId) => typeof factoryId === "string" && factoryId.length > 0);
53860
+ }
53861
+ if (typeof rawFactoryIds === "string" && rawFactoryIds.length > 0) {
53862
+ return [rawFactoryIds];
53863
+ }
53864
+ return [];
53865
+ };
53546
53866
  const formatAssignments = (user) => {
53547
53867
  if (user.role_level === "owner" || user.role_level === "it") return "Company-wide";
53548
53868
  if (user.role_level === "optifye") return "All companies";
53549
- if (user.role_level === "plant_head" && user.assigned_factories) {
53550
- return user.assigned_factories.join(", ") || "No factories assigned";
53869
+ if (user.role_level === "plant_head") {
53870
+ if (user.assigned_factories && user.assigned_factories.length > 0) {
53871
+ return user.assigned_factories.join(", ");
53872
+ }
53873
+ const assignedFactoryIds = getFactoryIdsFromUser(user);
53874
+ if (assignedFactoryIds.length === 1) {
53875
+ return "1 factory assigned";
53876
+ }
53877
+ if (assignedFactoryIds.length > 1) {
53878
+ return `${assignedFactoryIds.length} factories assigned`;
53879
+ }
53880
+ return "No factories assigned";
53551
53881
  }
53552
53882
  if (user.role_level === "supervisor" && user.assigned_lines) {
53553
53883
  return user.assigned_lines.join(", ") || "No lines assigned";
@@ -53739,22 +54069,28 @@ var UserManagementTable = ({
53739
54069
  onUpdate: onLineAssignmentUpdate || (async () => {
53740
54070
  })
53741
54071
  }
53742
- ) : user.role_level === "plant_head" ? /* @__PURE__ */ jsx(
53743
- FactoryAssignmentDropdown,
53744
- {
53745
- userId: user.user_id,
53746
- currentFactoryIds: user.properties?.factory_ids || [],
53747
- availableFactories: (
53748
- // Filter factories to only show those from the target user's company
53749
- user.properties?.company_id ? availableFactories.filter(
53750
- (factory) => factory.company_id === user.properties?.company_id
53751
- ) : availableFactories
53752
- ),
53753
- canEdit: permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate,
53754
- onUpdate: onFactoryAssignmentUpdate || (async () => {
53755
- })
54072
+ ) : user.role_level === "plant_head" ? (() => {
54073
+ const canEditFactoryAssignments = permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate;
54074
+ if (!canEditFactoryAssignments) {
54075
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) });
53756
54076
  }
53757
- ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
54077
+ return /* @__PURE__ */ jsx(
54078
+ FactoryAssignmentDropdown,
54079
+ {
54080
+ userId: user.user_id,
54081
+ currentFactoryIds: getFactoryIdsFromUser(user),
54082
+ availableFactories: (
54083
+ // Filter factories to only show those from the target user's company
54084
+ user.properties?.company_id ? availableFactories.filter(
54085
+ (factory) => factory.company_id === user.properties?.company_id
54086
+ ) : availableFactories
54087
+ ),
54088
+ canEdit: canEditFactoryAssignments,
54089
+ onUpdate: onFactoryAssignmentUpdate || (async () => {
54090
+ })
54091
+ }
54092
+ );
54093
+ })() : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
53758
54094
  showUsageStats && /* @__PURE__ */ jsx("td", { className: "px-6 py-4", children: user.role_level === "plant_head" || user.role_level === "supervisor" ? /* @__PURE__ */ jsx(
53759
54095
  "button",
53760
54096
  {
@@ -54919,52 +55255,18 @@ function HomeView({
54919
55255
  });
54920
55256
  return merged;
54921
55257
  }, [lineNames, dbLines]);
54922
- const isOwner = user?.role_level === "owner";
54923
- const isPlantHead = user?.role_level === "plant_head";
55258
+ const enabledLineIdSet = useMemo(
55259
+ () => new Set(dbLines.filter((line) => line.enable).map((line) => line.id)),
55260
+ [dbLines]
55261
+ );
54924
55262
  const isSupervisor = user?.role_level === "supervisor";
54925
- const assignedLineIds = useMemo(() => {
54926
- const rawLineIds = user?.properties?.line_id || user?.properties?.line_ids || [];
54927
- return Array.isArray(rawLineIds) ? rawLineIds : [];
54928
- }, [user]);
54929
- const assignedLineIdsInConfig = useMemo(() => {
54930
- if (assignedLineIds.length === 0) {
54931
- return [];
54932
- }
54933
- return assignedLineIds.filter((id3) => allLineIds.includes(id3));
54934
- }, [assignedLineIds, allLineIds]);
54935
- const assignedFactoryIds = useMemo(() => {
54936
- const rawFactoryIds = user?.properties?.factory_id || user?.properties?.factory_ids || [];
54937
- return Array.isArray(rawFactoryIds) ? rawFactoryIds : [];
54938
- }, [user]);
54939
- const lineFactoryMap = useMemo(() => {
54940
- const map = /* @__PURE__ */ new Map();
54941
- dbLines.forEach((line) => {
54942
- map.set(line.id, line.factory_id);
54943
- });
54944
- return map;
54945
- }, [dbLines]);
54946
- const plantHeadLineIds = useMemo(() => {
54947
- if (!isPlantHead || assignedFactoryIds.length === 0) {
54948
- return [];
54949
- }
54950
- const assignedFactoryIdSet = new Set(assignedFactoryIds);
54951
- return allLineIds.filter((lineId) => assignedFactoryIdSet.has(lineFactoryMap.get(lineId) || ""));
54952
- }, [isPlantHead, assignedFactoryIds, allLineIds, lineFactoryMap]);
54953
55263
  const visibleLineIds = useMemo(() => {
54954
- if (isOwner) {
54955
- return allLineIds;
55264
+ const scoped = Array.from(new Set(allLineIds.filter(Boolean)));
55265
+ if (enabledLineIdSet.size === 0) {
55266
+ return scoped;
54956
55267
  }
54957
- if (isPlantHead) {
54958
- const combinedLineIds = /* @__PURE__ */ new Set();
54959
- plantHeadLineIds.forEach((lineId) => combinedLineIds.add(lineId));
54960
- assignedLineIdsInConfig.forEach((lineId) => combinedLineIds.add(lineId));
54961
- return Array.from(combinedLineIds);
54962
- }
54963
- if (isSupervisor) {
54964
- return assignedLineIdsInConfig;
54965
- }
54966
- return allLineIds;
54967
- }, [isOwner, isPlantHead, isSupervisor, allLineIds, plantHeadLineIds, assignedLineIdsInConfig]);
55268
+ return scoped.filter((lineId) => enabledLineIdSet.has(lineId));
55269
+ }, [allLineIds, enabledLineIdSet]);
54968
55270
  const fallbackLineId = visibleLineIds[0] || defaultLineId;
54969
55271
  const defaultHomeLineId = fallbackLineId;
54970
55272
  const availableLineIds = useMemo(() => {
@@ -54991,7 +55293,7 @@ function HomeView({
54991
55293
  return defaultHomeLineId;
54992
55294
  });
54993
55295
  useEffect(() => {
54994
- if (!user || !isSupervisor && !isPlantHead || availableLineIds.length === 0) {
55296
+ if (!user || availableLineIds.length === 0) {
54995
55297
  return;
54996
55298
  }
54997
55299
  try {
@@ -55005,13 +55307,14 @@ function HomeView({
55005
55307
  } catch (error) {
55006
55308
  console.warn("Failed to read line filter from sessionStorage:", error);
55007
55309
  }
55310
+ if (availableLineIds.includes(selectedLineId)) {
55311
+ return;
55312
+ }
55008
55313
  if (defaultHomeLineId !== selectedLineId) {
55009
55314
  setSelectedLineId(defaultHomeLineId);
55010
55315
  }
55011
55316
  }, [
55012
55317
  user,
55013
- isSupervisor,
55014
- isPlantHead,
55015
55318
  availableLineIds,
55016
55319
  defaultHomeLineId,
55017
55320
  selectedLineId,
@@ -55030,14 +55333,14 @@ function HomeView({
55030
55333
  const [diagnoses, setDiagnoses] = useState([]);
55031
55334
  const timezone = useAppTimezone();
55032
55335
  const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
55033
- allLineIds,
55336
+ visibleLineIds,
55034
55337
  dashboardConfig?.shiftConfig
55035
55338
  );
55036
55339
  useEffect(() => {
55037
55340
  const initDisplayNames = async () => {
55038
55341
  try {
55039
55342
  if (selectedLineId === factoryViewId) {
55040
- for (const lineId of allLineIds) {
55343
+ for (const lineId of visibleLineIds) {
55041
55344
  await preInitializeWorkspaceDisplayNames(lineId);
55042
55345
  }
55043
55346
  } else {
@@ -55050,7 +55353,7 @@ function HomeView({
55050
55353
  }
55051
55354
  };
55052
55355
  initDisplayNames();
55053
- }, [selectedLineId, factoryViewId, allLineIds]);
55356
+ }, [selectedLineId, factoryViewId, visibleLineIds]);
55054
55357
  const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
55055
55358
  const {
55056
55359
  displayNames: workspaceDisplayNames,
@@ -58801,8 +59104,32 @@ var KPIsOverviewView = ({
58801
59104
  const dbTimezone = useAppTimezone();
58802
59105
  const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
58803
59106
  const { startDate: monthStartDate, endDate: monthEndDateKey, monthEndDate } = getMonthDateInfo(configuredTimezone);
58804
- const isSupervisor = user?.role_level === "supervisor";
58805
- const assignedLineIdsForLeaderboard = isSupervisor ? lineIds : void 0;
59107
+ const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
59108
+ const scopedLineIds = React26__default.useMemo(
59109
+ () => Array.isArray(user?.access_scope?.line_ids) ? user.access_scope.line_ids.filter((lineId) => typeof lineId === "string" && lineId.length > 0) : [],
59110
+ [user?.access_scope?.line_ids]
59111
+ );
59112
+ const hasCanonicalScope = !!user?.scope_mode || !!user?.access_scope;
59113
+ const scopeRole = (user?.role_level || user?.role || "").toLowerCase();
59114
+ const isStrictLineScopedRole = scopeRole === "supervisor" || scopeRole === "plant_head";
59115
+ const resolvedAssignedLineIds = React26__default.useMemo(() => {
59116
+ if (isSuperAdmin) return [];
59117
+ if (scopedLineIds.length > 0) return scopedLineIds;
59118
+ if (lineIds && lineIds.length > 0) return lineIds;
59119
+ if (isStrictLineScopedRole && hasCanonicalScope) return [];
59120
+ return [];
59121
+ }, [isSuperAdmin, scopedLineIds, lineIds, isStrictLineScopedRole, hasCanonicalScope]);
59122
+ const assignedLineIdSet = React26__default.useMemo(
59123
+ () => new Set(resolvedAssignedLineIds),
59124
+ [resolvedAssignedLineIds]
59125
+ );
59126
+ const metricsLineIds = React26__default.useMemo(() => {
59127
+ if (isSuperAdmin) {
59128
+ return lineIds ?? [];
59129
+ }
59130
+ return resolvedAssignedLineIds;
59131
+ }, [isSuperAdmin, lineIds, resolvedAssignedLineIds]);
59132
+ const assignedLineIdsForLeaderboard = isSuperAdmin ? void 0 : resolvedAssignedLineIds;
58806
59133
  const leaderboardLinesForView = React26__default.useMemo(() => {
58807
59134
  const targetMode = viewType === "machine" ? "uptime" : "output";
58808
59135
  return leaderboardLines.filter((line) => (line.monitoring_mode ?? "output") === targetMode);
@@ -58854,7 +59181,7 @@ var KPIsOverviewView = ({
58854
59181
  error: metricsError
58855
59182
  } = useDashboardMetrics({
58856
59183
  lineId: factoryViewId,
58857
- userAccessibleLineIds: lineIds
59184
+ userAccessibleLineIds: metricsLineIds
58858
59185
  });
58859
59186
  const defaultKPIs = React26__default.useMemo(() => createDefaultKPIs(), []);
58860
59187
  const kpisByLineId = React26__default.useMemo(() => {
@@ -58908,16 +59235,16 @@ var KPIsOverviewView = ({
58908
59235
  console.log("[KPIsOverviewView] Fetching lines with lineIds filter:", lineIds);
58909
59236
  const allLines = await dashboardService.getAllLines();
58910
59237
  let filteredLines = allLines;
58911
- if (lineIds && lineIds.length > 0) {
58912
- filteredLines = allLines.filter((line) => lineIds.includes(line.id));
58913
- console.log("[KPIsOverviewView] Filtered lines:", {
59238
+ if (!isSuperAdmin) {
59239
+ filteredLines = allLines.filter((line) => assignedLineIdSet.has(line.id));
59240
+ console.log("[KPIsOverviewView] Applied scoped line filter:", {
58914
59241
  total: allLines.length,
58915
59242
  filtered: filteredLines.length,
58916
- lineIds,
59243
+ allowedLineIds: resolvedAssignedLineIds,
58917
59244
  filteredLineIds: filteredLines.map((l) => l.id)
58918
59245
  });
58919
59246
  } else {
58920
- console.log("[KPIsOverviewView] No lineIds filter, showing all lines:", allLines.length);
59247
+ console.log("[KPIsOverviewView] Super admin view, showing all lines:", allLines.length);
58921
59248
  }
58922
59249
  setLines(filteredLines);
58923
59250
  } catch (err) {
@@ -58928,34 +59255,36 @@ var KPIsOverviewView = ({
58928
59255
  }
58929
59256
  };
58930
59257
  fetchLines();
58931
- }, [supabase, dashboardConfig, lineIds]);
59258
+ }, [supabase, dashboardConfig, lineIds, isSuperAdmin, assignedLineIdSet, resolvedAssignedLineIds]);
58932
59259
  useEffect(() => {
58933
59260
  let isMounted = true;
58934
59261
  const fetchLeaderboardLines = async () => {
58935
59262
  if (!supabase || !resolvedCompanyId) {
58936
- setLeaderboardLines(lines);
59263
+ setLeaderboardLines(lines.filter((line) => line.enable !== false));
58937
59264
  return;
58938
59265
  }
58939
59266
  setLeaderboardLinesLoading(true);
58940
59267
  try {
58941
- const linesService2 = new LinesService(supabase);
58942
- const companyLines = await linesService2.getLinesByCompanyId(resolvedCompanyId);
59268
+ const data = await fetchBackendJson(
59269
+ supabase,
59270
+ `/api/dashboard/leaderboard-lines?company_id=${encodeURIComponent(resolvedCompanyId)}`
59271
+ );
58943
59272
  if (!isMounted) return;
58944
- const transformed = companyLines.map((line) => ({
59273
+ const transformed = (data.lines || []).filter((line) => line.enable !== false).map((line) => ({
58945
59274
  id: line.id,
58946
- line_name: line.name,
58947
- factory_id: line.factoryId || "",
59275
+ line_name: line.line_name,
59276
+ factory_id: line.factory_id || "",
58948
59277
  factory_name: "N/A",
58949
- company_id: line.companyId,
59278
+ company_id: line.company_id,
58950
59279
  company_name: "",
58951
- enable: line.isActive ?? true,
58952
- monitoring_mode: line.monitoringMode ?? "output"
59280
+ enable: line.enable ?? true,
59281
+ monitoring_mode: line.monitoring_mode ?? "output"
58953
59282
  }));
58954
59283
  setLeaderboardLines(transformed);
58955
59284
  } catch (err) {
58956
59285
  console.error("[KPIsOverviewView] Failed to load leaderboard lines:", err);
58957
59286
  if (!isMounted) return;
58958
- setLeaderboardLines(lines);
59287
+ setLeaderboardLines(lines.filter((line) => line.enable !== false));
58959
59288
  } finally {
58960
59289
  if (isMounted) setLeaderboardLinesLoading(false);
58961
59290
  }
@@ -59036,11 +59365,10 @@ var KPIsOverviewView = ({
59036
59365
  lineMode: viewType === "machine" ? "uptime" : "output"
59037
59366
  });
59038
59367
  const nextMap = /* @__PURE__ */ new Map();
59368
+ targetLineIds.forEach((lineId) => nextMap.set(lineId, 0));
59039
59369
  entries.forEach((entry) => {
59040
59370
  const value = Number(entry.avg_efficiency);
59041
- if (Number.isFinite(value)) {
59042
- nextMap.set(entry.line_id, value);
59043
- }
59371
+ nextMap.set(entry.line_id, Number.isFinite(value) ? value : 0);
59044
59372
  });
59045
59373
  setTodayEfficiencyByLineId(nextMap);
59046
59374
  } catch (err) {
@@ -59123,6 +59451,9 @@ var KPIsOverviewView = ({
59123
59451
  };
59124
59452
  };
59125
59453
  const handleLineClick = (line, kpis) => {
59454
+ if (!isSuperAdmin && !assignedLineIdSet.has(line.id)) {
59455
+ return;
59456
+ }
59126
59457
  const trackProps = {
59127
59458
  line_id: line.id,
59128
59459
  line_name: line.line_name,
@@ -59635,6 +59966,7 @@ var MobileWorkspaceCard = memo$1(({
59635
59966
  workspace,
59636
59967
  rank,
59637
59968
  cardClass,
59969
+ isClickable,
59638
59970
  onWorkspaceClick,
59639
59971
  getMedalIcon,
59640
59972
  efficiencyLabel
@@ -59647,8 +59979,8 @@ var MobileWorkspaceCard = memo$1(({
59647
59979
  transition: {
59648
59980
  layout: { duration: 0.3, ease: "easeInOut" }
59649
59981
  },
59650
- onClick: () => onWorkspaceClick(workspace, rank),
59651
- className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] cursor-pointer`,
59982
+ onClick: isClickable ? () => onWorkspaceClick(workspace, rank) : void 0,
59983
+ className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] ${isClickable ? "cursor-pointer" : "cursor-not-allowed opacity-75"}`,
59652
59984
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
59653
59985
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
59654
59986
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -59670,13 +60002,14 @@ var MobileWorkspaceCard = memo$1(({
59670
60002
  ] })
59671
60003
  }
59672
60004
  ), (prevProps, nextProps) => {
59673
- return prevProps.efficiencyLabel === nextProps.efficiencyLabel && prevProps.rank === nextProps.rank && prevProps.cardClass === nextProps.cardClass && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.action_count === nextProps.workspace.action_count && prevProps.workspace.action_threshold === nextProps.workspace.action_threshold && prevProps.workspace.avg_cycle_time === nextProps.workspace.avg_cycle_time && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
60005
+ return prevProps.efficiencyLabel === nextProps.efficiencyLabel && prevProps.rank === nextProps.rank && prevProps.cardClass === nextProps.cardClass && prevProps.isClickable === nextProps.isClickable && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.action_count === nextProps.workspace.action_count && prevProps.workspace.action_threshold === nextProps.workspace.action_threshold && prevProps.workspace.avg_cycle_time === nextProps.workspace.avg_cycle_time && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
59674
60006
  });
59675
60007
  MobileWorkspaceCard.displayName = "MobileWorkspaceCard";
59676
60008
  var DesktopWorkspaceRow = memo$1(({
59677
60009
  workspace,
59678
60010
  index,
59679
60011
  rowClass,
60012
+ isClickable,
59680
60013
  onWorkspaceClick,
59681
60014
  getMedalIcon
59682
60015
  }) => /* @__PURE__ */ jsxs(
@@ -59686,8 +60019,8 @@ var DesktopWorkspaceRow = memo$1(({
59686
60019
  layoutId: `row-${workspace.workspace_uuid}`,
59687
60020
  initial: false,
59688
60021
  transition: { layout: { duration: 0.3, ease: "easeInOut" } },
59689
- onClick: () => onWorkspaceClick(workspace, index + 1),
59690
- className: `${rowClass} hover:bg-gray-50/90 transition-colors duration-150 cursor-pointer group`,
60022
+ onClick: isClickable ? () => onWorkspaceClick(workspace, index + 1) : void 0,
60023
+ className: `${rowClass} transition-colors duration-150 ${isClickable ? "hover:bg-gray-50/90 cursor-pointer group" : "cursor-not-allowed opacity-75"}`,
59691
60024
  children: [
59692
60025
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap group-hover:font-medium", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
59693
60026
  /* @__PURE__ */ jsx("span", { children: index + 1 }),
@@ -59699,7 +60032,7 @@ var DesktopWorkspaceRow = memo$1(({
59699
60032
  ]
59700
60033
  }
59701
60034
  ), (prevProps, nextProps) => {
59702
- return prevProps.index === nextProps.index && prevProps.rowClass === nextProps.rowClass && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
60035
+ return prevProps.index === nextProps.index && prevProps.rowClass === nextProps.rowClass && prevProps.isClickable === nextProps.isClickable && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
59703
60036
  });
59704
60037
  DesktopWorkspaceRow.displayName = "DesktopWorkspaceRow";
59705
60038
  var LeaderboardDetailView = memo$1(({
@@ -59859,6 +60192,16 @@ var LeaderboardDetailView = memo$1(({
59859
60192
  }
59860
60193
  return allLineIds;
59861
60194
  }, [entityConfig, userAccessibleLineIds]);
60195
+ const accessibleLineIdSet = useMemo(
60196
+ () => new Set((userAccessibleLineIds || []).filter(Boolean)),
60197
+ [userAccessibleLineIds]
60198
+ );
60199
+ const canOpenWorkspace = useCallback((workspaceLineId) => {
60200
+ if (!workspaceLineId) return false;
60201
+ if (!userAccessibleLineIds) return true;
60202
+ if (accessibleLineIdSet.size === 0) return false;
60203
+ return accessibleLineIdSet.has(workspaceLineId);
60204
+ }, [accessibleLineIdSet, userAccessibleLineIds]);
59862
60205
  const { hasUptime: lineModeHasUptime, hasOutput: lineModeHasOutput } = useMemo(() => {
59863
60206
  if (!lines || lines.length === 0) {
59864
60207
  return { hasUptime: false, hasOutput: false };
@@ -60256,6 +60599,9 @@ var LeaderboardDetailView = memo$1(({
60256
60599
  return `${startDate.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${endDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
60257
60600
  }, [normalizedRange]);
60258
60601
  const handleWorkspaceClick = useCallback((workspace, rank) => {
60602
+ if (!canOpenWorkspace(workspace.line_id)) {
60603
+ return;
60604
+ }
60259
60605
  trackCoreEvent("Workspace from Leaderboard Clicked", {
60260
60606
  workspace_name: workspace.workspace_name,
60261
60607
  workspace_id: workspace.workspace_uuid,
@@ -60288,7 +60634,7 @@ var LeaderboardDetailView = memo$1(({
60288
60634
  const combinedParams = navParams ? `${navParams}&${contextParamString}` : `?${contextParamString}`;
60289
60635
  navigation.navigate(`/workspace/${workspace.workspace_uuid}${combinedParams}`);
60290
60636
  }
60291
- }, [onWorkspaceClick, navigation, date, shiftId]);
60637
+ }, [canOpenWorkspace, onWorkspaceClick, navigation, date, shiftId]);
60292
60638
  useEffect(() => {
60293
60639
  workspacesLengthRef.current = activeEntries.length || 0;
60294
60640
  }, [activeEntries.length]);
@@ -60579,6 +60925,7 @@ var LeaderboardDetailView = memo$1(({
60579
60925
  workspace: ws,
60580
60926
  rank,
60581
60927
  cardClass,
60928
+ isClickable: canOpenWorkspace(ws.line_id),
60582
60929
  onWorkspaceClick: stableHandleWorkspaceClick,
60583
60930
  getMedalIcon: stableGetMedalIcon,
60584
60931
  efficiencyLabel
@@ -60603,6 +60950,7 @@ var LeaderboardDetailView = memo$1(({
60603
60950
  workspace: ws,
60604
60951
  index,
60605
60952
  rowClass,
60953
+ isClickable: canOpenWorkspace(ws.line_id),
60606
60954
  onWorkspaceClick: stableHandleWorkspaceClick,
60607
60955
  getMedalIcon: stableGetMedalIcon
60608
60956
  },
@@ -60613,7 +60961,7 @@ var LeaderboardDetailView = memo$1(({
60613
60961
  ) })
60614
60962
  ] });
60615
60963
  }, (prevProps, nextProps) => {
60616
- return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && JSON.stringify(prevProps.lineNames) === JSON.stringify(nextProps.lineNames) && prevProps.className === nextProps.className;
60964
+ return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && JSON.stringify(prevProps.lineNames) === JSON.stringify(nextProps.lineNames) && JSON.stringify(prevProps.userAccessibleLineIds) === JSON.stringify(nextProps.userAccessibleLineIds) && prevProps.className === nextProps.className;
60617
60965
  });
60618
60966
  LeaderboardDetailView.displayName = "LeaderboardDetailView";
60619
60967
  var LeaderboardDetailViewWithDisplayNames = withAllWorkspaceDisplayNames(LeaderboardDetailView);
@@ -66374,9 +66722,38 @@ var TeamManagementView = ({
66374
66722
  optifye: 0
66375
66723
  });
66376
66724
  const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
66725
+ const normalizeIds = useCallback((value) => {
66726
+ if (Array.isArray(value)) {
66727
+ return value.filter((id3) => typeof id3 === "string" && id3.length > 0);
66728
+ }
66729
+ if (typeof value === "string" && value.length > 0) {
66730
+ return [value];
66731
+ }
66732
+ return [];
66733
+ }, []);
66734
+ const plantHeadFactoryIds = useMemo(() => {
66735
+ if (user?.role_level !== "plant_head") return [];
66736
+ const scopedFactoryIds = normalizeIds(user?.access_scope?.factory_ids);
66737
+ if (scopedFactoryIds.length > 0) {
66738
+ return Array.from(new Set(scopedFactoryIds));
66739
+ }
66740
+ const propertyFactoryIds = normalizeIds(
66741
+ user?.properties?.factory_ids ?? user?.properties?.factory_id
66742
+ );
66743
+ if (propertyFactoryIds.length > 0) {
66744
+ return Array.from(new Set(propertyFactoryIds));
66745
+ }
66746
+ return entityConfig?.factoryId ? [entityConfig.factoryId] : [];
66747
+ }, [user, entityConfig?.factoryId, normalizeIds]);
66748
+ const notifyScopeRefresh = useCallback(() => {
66749
+ if (typeof window !== "undefined") {
66750
+ window.dispatchEvent(new Event("rbac:refresh-scope"));
66751
+ }
66752
+ }, []);
66377
66753
  const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "plant_head" || user?.role_level === "optifye";
66378
66754
  const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
66379
- const companyIdForUsage = entityConfig?.companyId || user?.properties?.company_id;
66755
+ const pageCompanyId = entityConfig?.companyId || user?.properties?.company_id;
66756
+ const companyIdForUsage = pageCompanyId;
66380
66757
  const usageDateRange = useMemo(() => {
66381
66758
  const today = /* @__PURE__ */ new Date();
66382
66759
  const dayOfWeek = today.getDay();
@@ -66407,8 +66784,8 @@ var TeamManagementView = ({
66407
66784
  return acc;
66408
66785
  }, {});
66409
66786
  }, [usageData, usageDateRange.daysElapsed]);
66410
- const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
66411
- const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" || user?.role_level === "it" ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
66787
+ const pageTitle = "Team Management";
66788
+ const pageDescription = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye" ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
66412
66789
  const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "it", "plant_head"].includes(user.role_level) : false;
66413
66790
  const loadData = useCallback(async () => {
66414
66791
  if (!supabase) {
@@ -66416,20 +66793,16 @@ var TeamManagementView = ({
66416
66793
  setIsLoading(false);
66417
66794
  return;
66418
66795
  }
66419
- const isOptifyeUser = user?.role_level === "optifye";
66420
- if (!isOptifyeUser) {
66421
- const companyId2 = entityConfig?.companyId || user?.properties?.company_id;
66422
- if (!companyId2) {
66423
- setError("Company not found. Please contact your administrator.");
66424
- setIsLoading(false);
66425
- return;
66426
- }
66796
+ const companyId = pageCompanyId;
66797
+ if (!companyId) {
66798
+ setError("Company not found. Please contact your administrator.");
66799
+ setIsLoading(false);
66800
+ return;
66427
66801
  }
66428
- const companyId = entityConfig?.companyId || user?.properties?.company_id;
66429
66802
  console.log("[TeamManagementView] Loading data:", {
66430
66803
  role: user?.role_level,
66431
- isOptifye: isOptifyeUser,
66432
- companyId: isOptifyeUser ? "ALL" : companyId
66804
+ isOptifye: user?.role_level === "optifye",
66805
+ companyId
66433
66806
  });
66434
66807
  setIsLoading(true);
66435
66808
  setError(void 0);
@@ -66444,23 +66817,7 @@ var TeamManagementView = ({
66444
66817
  if (!backendUrl) {
66445
66818
  throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
66446
66819
  }
66447
- if (isOptifyeUser) {
66448
- const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").order("factory_name");
66449
- const linesResponse = await fetch(`${backendUrl}/api/lines`, {
66450
- headers: {
66451
- "Authorization": `Bearer ${token}`,
66452
- "Content-Type": "application/json"
66453
- }
66454
- });
66455
- if (!linesResponse.ok) {
66456
- throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
66457
- }
66458
- const linesData = await linesResponse.json();
66459
- const lines = linesData.lines || [];
66460
- setAvailableFactories(factories || []);
66461
- setAvailableLines(lines);
66462
- console.log("[TeamManagementView] Optifye - Loaded factories:", factories?.length, "lines:", lines?.length, "Sample lines:", lines?.slice(0, 3));
66463
- } else if ((user?.role_level === "owner" || user?.role_level === "it") && companyId) {
66820
+ if ((user?.role_level === "optifye" || user?.role_level === "owner" || user?.role_level === "it") && companyId) {
66464
66821
  const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").eq("company_id", companyId).order("factory_name");
66465
66822
  const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
66466
66823
  headers: {
@@ -66475,9 +66832,8 @@ var TeamManagementView = ({
66475
66832
  const lines = linesData.lines || [];
66476
66833
  setAvailableFactories(factories || []);
66477
66834
  setAvailableLines(lines);
66478
- console.log("[TeamManagementView] Owner/IT - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
66835
+ console.log("[TeamManagementView] Company-scoped team view - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
66479
66836
  } else if (user?.role_level === "plant_head") {
66480
- const plantHeadFactoryIds = user?.properties?.factory_ids || [];
66481
66837
  if (plantHeadFactoryIds.length > 0) {
66482
66838
  if (companyId) {
66483
66839
  const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
@@ -66530,35 +66886,46 @@ var TeamManagementView = ({
66530
66886
  setAvailableFactories([]);
66531
66887
  console.log("[TeamManagementView] Fallback - Company:", companyId, "Loaded lines:", lines?.length);
66532
66888
  }
66533
- if (isOptifyeUser) {
66534
- const [usersData, userStats] = await Promise.all([
66535
- userManagementService.getAllUsers(),
66536
- userManagementService.getAllUserStats()
66537
- ]);
66538
- setUsers(usersData);
66539
- setStats({
66540
- totalUsers: userStats.total,
66541
- owners: userStats.owners,
66542
- it: userStats.it,
66543
- plantHeads: userStats.plant_heads,
66544
- supervisors: userStats.supervisors,
66545
- optifye: userStats.optifye
66546
- });
66547
- } else {
66548
- const [usersData, userStats] = await Promise.all([
66549
- user?.role_level === "plant_head" && entityConfig?.factoryId ? userManagementService.getFactoryUsers(entityConfig.factoryId) : userManagementService.getCompanyUsers(companyId),
66550
- userManagementService.getUserStats(companyId)
66551
- ]);
66552
- setUsers(usersData);
66553
- setStats({
66554
- totalUsers: userStats.total,
66555
- owners: userStats.owners,
66556
- it: userStats.it,
66557
- plantHeads: userStats.plant_heads,
66558
- supervisors: userStats.supervisors,
66559
- optifye: 0
66560
- });
66561
- }
66889
+ const usersPromise = user?.role_level === "plant_head" ? (async () => {
66890
+ if (plantHeadFactoryIds.length === 0) {
66891
+ return [];
66892
+ }
66893
+ const results = await Promise.allSettled(
66894
+ plantHeadFactoryIds.map((factoryId) => userManagementService.getFactoryUsers(factoryId))
66895
+ );
66896
+ const successful = results.filter(
66897
+ (result) => result.status === "fulfilled"
66898
+ ).flatMap((result) => result.value || []);
66899
+ if (successful.length > 0) {
66900
+ const byUserId = /* @__PURE__ */ new Map();
66901
+ successful.forEach((u) => {
66902
+ if (u?.user_id) {
66903
+ byUserId.set(u.user_id, u);
66904
+ }
66905
+ });
66906
+ return Array.from(byUserId.values());
66907
+ }
66908
+ const firstRejected = results.find(
66909
+ (result) => result.status === "rejected"
66910
+ );
66911
+ if (firstRejected) {
66912
+ throw firstRejected.reason;
66913
+ }
66914
+ return [];
66915
+ })() : userManagementService.getCompanyUsers(companyId);
66916
+ const [usersData, userStats] = await Promise.all([
66917
+ usersPromise,
66918
+ userManagementService.getUserStats(companyId)
66919
+ ]);
66920
+ setUsers(usersData);
66921
+ setStats({
66922
+ totalUsers: userStats.total,
66923
+ owners: userStats.owners,
66924
+ it: userStats.it,
66925
+ plantHeads: userStats.plant_heads,
66926
+ supervisors: userStats.supervisors,
66927
+ optifye: 0
66928
+ });
66562
66929
  } catch (err) {
66563
66930
  console.error("Error loading team management data:", err);
66564
66931
  setError(err instanceof Error ? err.message : "Failed to load data");
@@ -66566,16 +66933,16 @@ var TeamManagementView = ({
66566
66933
  } finally {
66567
66934
  setIsLoading(false);
66568
66935
  }
66569
- }, [supabase, user, entityConfig]);
66936
+ }, [supabase, user, pageCompanyId, entityConfig?.factoryId, plantHeadFactoryIds]);
66570
66937
  useEffect(() => {
66571
- const companyId = entityConfig?.companyId || user?.properties?.company_id;
66572
- const canLoad = hasAccess && user && (user.role_level === "optifye" || !!companyId);
66938
+ const companyId = pageCompanyId;
66939
+ const canLoad = hasAccess && user && !!companyId;
66573
66940
  if (canLoad) {
66574
66941
  loadData();
66575
66942
  } else if (!user) {
66576
66943
  setIsLoading(true);
66577
66944
  }
66578
- }, [hasAccess, loadData, user, entityConfig?.companyId]);
66945
+ }, [hasAccess, loadData, user, pageCompanyId]);
66579
66946
  const handleUserAdded = useCallback(() => {
66580
66947
  loadData();
66581
66948
  }, [loadData]);
@@ -66589,12 +66956,13 @@ var TeamManagementView = ({
66589
66956
  updated_by: user.id
66590
66957
  });
66591
66958
  toast.success("User role updated successfully");
66959
+ notifyScopeRefresh();
66592
66960
  loadData();
66593
66961
  } catch (err) {
66594
66962
  console.error("Error updating user role:", err);
66595
66963
  toast.error("Failed to update user role");
66596
66964
  }
66597
- }, [supabase, user, loadData]);
66965
+ }, [supabase, user, loadData, notifyScopeRefresh]);
66598
66966
  const handleRemoveUser = useCallback(async (userId) => {
66599
66967
  if (!supabase || !user) return;
66600
66968
  try {
@@ -66617,12 +66985,13 @@ var TeamManagementView = ({
66617
66985
  assigned_by: user.id
66618
66986
  });
66619
66987
  toast.success("Line assignments updated successfully");
66988
+ notifyScopeRefresh();
66620
66989
  loadData();
66621
66990
  } catch (err) {
66622
66991
  console.error("Error updating line assignments:", err);
66623
66992
  toast.error("Failed to update line assignments");
66624
66993
  }
66625
- }, [supabase, user, loadData]);
66994
+ }, [supabase, user, loadData, notifyScopeRefresh]);
66626
66995
  const handleFactoryAssignmentUpdate = useCallback(async (userId, factoryIds) => {
66627
66996
  if (!supabase || !user) return;
66628
66997
  try {
@@ -66633,12 +67002,13 @@ var TeamManagementView = ({
66633
67002
  assigned_by: user.id
66634
67003
  });
66635
67004
  toast.success("Factory assignments updated successfully");
67005
+ notifyScopeRefresh();
66636
67006
  loadData();
66637
67007
  } catch (err) {
66638
67008
  console.error("Error updating factory assignments:", err);
66639
67009
  toast.error("Failed to update factory assignments");
66640
67010
  }
66641
- }, [supabase, user, loadData]);
67011
+ }, [supabase, user, loadData, notifyScopeRefresh]);
66642
67012
  const handleProfileUpdate = useCallback(async (userId, firstName, lastName, profilePhotoUrl) => {
66643
67013
  if (!supabase || !user) return;
66644
67014
  try {