@optifye/dashboard-core 6.10.46 → 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.js CHANGED
@@ -3947,6 +3947,10 @@ var AuthService = class {
3947
3947
  email: enrichedUser.email,
3948
3948
  role: enrichedUser.role,
3949
3949
  role_level: enrichedUser.role,
3950
+ scope_mode: enrichedUser.scope_mode,
3951
+ scope_generated_at: enrichedUser.scope_generated_at,
3952
+ scope_ttl_seconds: enrichedUser.scope_ttl_seconds,
3953
+ access_scope: enrichedUser.access_scope,
3950
3954
  company_id: enrichedUser.company_id,
3951
3955
  first_login_completed: enrichedUser.profile.first_login_completed,
3952
3956
  properties: {
@@ -8153,6 +8157,8 @@ var AuthProvider = ({ children }) => {
8153
8157
  const isFetchingRef = React26.useRef(false);
8154
8158
  const lastProcessedSessionRef = React26.useRef(null);
8155
8159
  const hasAuthenticatedRef = React26.useRef(false);
8160
+ const scopeFetchedAtRef = React26.useRef(0);
8161
+ const scopeTtlSecondsRef = React26.useRef(300);
8156
8162
  const fetchSession = React26.useCallback(async (supabaseSession, isReauth = false, allowRetry = true) => {
8157
8163
  if (isFetchingRef.current) {
8158
8164
  console.log("[AuthContext] Already fetching, skipping duplicate request");
@@ -8165,6 +8171,9 @@ var AuthProvider = ({ children }) => {
8165
8171
  }
8166
8172
  try {
8167
8173
  const enrichedUser = await AuthService.getSession(supabaseSession.access_token);
8174
+ const parsedScopeGeneratedAt = enrichedUser.scope_generated_at ? Date.parse(enrichedUser.scope_generated_at) : NaN;
8175
+ scopeFetchedAtRef.current = Number.isFinite(parsedScopeGeneratedAt) ? parsedScopeGeneratedAt : Date.now();
8176
+ scopeTtlSecondsRef.current = enrichedUser.scope_ttl_seconds || 300;
8168
8177
  const authUser = AuthService.toAuthUser(enrichedUser);
8169
8178
  setUser(authUser);
8170
8179
  setSession(supabaseSession);
@@ -8254,6 +8263,8 @@ var AuthProvider = ({ children }) => {
8254
8263
  setError(null);
8255
8264
  setShowOnboarding(false);
8256
8265
  hasAuthenticatedRef.current = false;
8266
+ scopeFetchedAtRef.current = 0;
8267
+ scopeTtlSecondsRef.current = 300;
8257
8268
  router$1.push("/login");
8258
8269
  } catch (err) {
8259
8270
  console.error("[AuthContext] Sign out error:", err);
@@ -8316,6 +8327,44 @@ var AuthProvider = ({ children }) => {
8316
8327
  clearInterval(intervalId);
8317
8328
  };
8318
8329
  }, [session, supabase]);
8330
+ React26.useEffect(() => {
8331
+ if (!session || !user) {
8332
+ return;
8333
+ }
8334
+ const refreshScopeIfExpired = async () => {
8335
+ if (!session || isFetchingRef.current) {
8336
+ return;
8337
+ }
8338
+ const ttlSeconds = user.scope_ttl_seconds || scopeTtlSecondsRef.current || 300;
8339
+ const fetchedAt = scopeFetchedAtRef.current || 0;
8340
+ const shouldRefresh = fetchedAt === 0 || Date.now() - fetchedAt >= ttlSeconds * 1e3;
8341
+ if (!shouldRefresh) {
8342
+ return;
8343
+ }
8344
+ try {
8345
+ console.log("[AuthContext] Scope TTL expired, refreshing session scope");
8346
+ await fetchSession(session, true);
8347
+ } catch (err) {
8348
+ console.error("[AuthContext] Scope refresh failed:", err);
8349
+ }
8350
+ };
8351
+ refreshScopeIfExpired();
8352
+ const intervalId = setInterval(refreshScopeIfExpired, 6e4);
8353
+ return () => clearInterval(intervalId);
8354
+ }, [session, user?.id, user?.scope_ttl_seconds, fetchSession]);
8355
+ React26.useEffect(() => {
8356
+ if (typeof window === "undefined") {
8357
+ return;
8358
+ }
8359
+ const handleScopeRefresh = () => {
8360
+ if (!session) {
8361
+ return;
8362
+ }
8363
+ void fetchSession(session, true);
8364
+ };
8365
+ window.addEventListener("rbac:refresh-scope", handleScopeRefresh);
8366
+ return () => window.removeEventListener("rbac:refresh-scope", handleScopeRefresh);
8367
+ }, [session, fetchSession]);
8319
8368
  React26.useEffect(() => {
8320
8369
  if (!supabase) {
8321
8370
  console.log("[AuthContext] No Supabase client, skipping auth initialization");
@@ -8384,7 +8433,8 @@ var AuthProvider = ({ children }) => {
8384
8433
  } else if (event === "TOKEN_REFRESHED") {
8385
8434
  console.log("[AuthContext] Token refreshed automatically");
8386
8435
  if (currentSession) {
8387
- setSession(currentSession);
8436
+ const isReauth = hasAuthenticatedRef.current;
8437
+ await fetchSession(currentSession, isReauth);
8388
8438
  const expiresAt = currentSession.expires_at;
8389
8439
  if (expiresAt) {
8390
8440
  const minutesUntilExpiry = Math.floor((expiresAt * 1e3 - Date.now()) / 6e4);
@@ -10532,12 +10582,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10532
10582
  const configuredLineIds = React26.useMemo(() => {
10533
10583
  return getConfiguredLineIds(entityConfig);
10534
10584
  }, [entityConfig]);
10585
+ const targetFactoryLineIds = React26.useMemo(() => {
10586
+ const sourceLineIds = userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
10587
+ return Array.from(new Set((sourceLineIds || []).filter(Boolean)));
10588
+ }, [userAccessibleLineIds, configuredLineIds]);
10535
10589
  const { shiftConfig: staticShiftConfig } = useDashboardConfig();
10536
10590
  const {
10537
10591
  shiftConfigMap: multiLineShiftConfigMap,
10538
10592
  isLoading: isMultiLineShiftConfigLoading
10539
10593
  } = useMultiLineShiftConfigs(
10540
- isFactoryView ? configuredLineIds : [],
10594
+ isFactoryView ? targetFactoryLineIds : [],
10541
10595
  staticShiftConfig
10542
10596
  );
10543
10597
  const {
@@ -10632,7 +10686,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10632
10686
  return;
10633
10687
  }
10634
10688
  const isFactory = currentLineIdToUse === factoryViewId;
10635
- const targetLineIds = isFactory ? userAccessibleLineIds || configuredLineIds : [currentLineIdToUse];
10689
+ const targetLineIds = isFactory ? targetFactoryLineIds : [currentLineIdToUse];
10636
10690
  const targetLineIdsKey = targetLineIds.slice().sort().join(",");
10637
10691
  const usesShiftGroups = isFactory && shiftGroups.length > 0;
10638
10692
  const singleShiftDetails = usesShiftGroups ? null : shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(defaultTimezone, staticShiftConfig);
@@ -10681,7 +10735,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10681
10735
  throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
10682
10736
  }
10683
10737
  if (targetLineIds.length === 0) {
10684
- throw new Error("No target line IDs available for fetching metrics.");
10738
+ logDebug("[useDashboardMetrics] Skipping fetch: no target line IDs after scope filtering");
10739
+ setMetrics({
10740
+ workspaceMetrics: [],
10741
+ lineMetrics: [],
10742
+ metadata: void 0,
10743
+ efficiencyLegend: DEFAULT_EFFICIENCY_LEGEND
10744
+ });
10745
+ setMetricsLineId(requestLineId);
10746
+ lastFetchKeyRef.current = inFlightFetchKeyRef.current;
10747
+ return;
10685
10748
  }
10686
10749
  let allWorkspaceMetrics = [];
10687
10750
  let allLineMetrics = [];
@@ -10699,11 +10762,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10699
10762
  lineCount: group.lineIds.length
10700
10763
  }))
10701
10764
  });
10765
+ const targetLineIdSet = new Set(targetLineIds);
10702
10766
  const metricsPromises = shiftGroups.map(async (group) => {
10703
- const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
10767
+ const groupLineIds = group.lineIds.filter((lineGroupId) => targetLineIdSet.has(lineGroupId));
10768
+ if (groupLineIds.length === 0) {
10769
+ return null;
10770
+ }
10771
+ const lineIdsParam = `line_ids=${groupLineIds.join(",")}`;
10704
10772
  const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${companyId}${forceParam}`;
10705
10773
  logDebug(`[useDashboardMetrics] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
10706
- lineIds: group.lineIds,
10774
+ lineIds: groupLineIds,
10707
10775
  date: group.date
10708
10776
  });
10709
10777
  const response = await fetch(url, {
@@ -10727,7 +10795,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10727
10795
  throw new Error(`Invalid JSON response from backend`);
10728
10796
  }
10729
10797
  });
10730
- const results = await Promise.all(metricsPromises);
10798
+ const results = (await Promise.all(metricsPromises)).filter((result) => !!result);
10731
10799
  hasFlowBuffers = results.some((result) => result?.metadata?.has_flow_buffers);
10732
10800
  results.forEach((result) => {
10733
10801
  const idleMap = result?.metadata?.idle_time_vlm_by_line;
@@ -10905,6 +10973,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10905
10973
  shiftGroups,
10906
10974
  shiftGroupsKey,
10907
10975
  configuredLineIds,
10976
+ targetFactoryLineIds,
10908
10977
  isFactoryView,
10909
10978
  multiLineShiftConfigMap,
10910
10979
  staticShiftConfig,
@@ -10997,8 +11066,14 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
10997
11066
  logDebug("[useDashboardMetrics] Setting up group subscriptions:", {
10998
11067
  groupCount: currentShiftGroups.length
10999
11068
  });
11069
+ const targetFactoryLineIdsForSubscriptions = currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
11070
+ const targetFactoryLineIdSet = new Set(
11071
+ (targetFactoryLineIdsForSubscriptions || []).filter(Boolean)
11072
+ );
11000
11073
  currentShiftGroups.forEach((group, index) => {
11001
- const groupLineIds = group.lineIds.filter((id3) => id3 && id3 !== factoryViewIdentifier);
11074
+ const groupLineIds = group.lineIds.filter(
11075
+ (id3) => id3 && id3 !== factoryViewIdentifier && targetFactoryLineIdSet.has(id3)
11076
+ );
11002
11077
  if (groupLineIds.length === 0) {
11003
11078
  logDebug("[useDashboardMetrics] Skipping group subscription: no line IDs after filtering", {
11004
11079
  groupIndex: index,
@@ -16177,6 +16252,7 @@ function useAccessControl() {
16177
16252
  function useTeamManagementPermissions() {
16178
16253
  const { user } = useAuth();
16179
16254
  const currentRole = user?.role_level;
16255
+ const isSuperAdminOptifye = currentRole === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
16180
16256
  return {
16181
16257
  /**
16182
16258
  * Can the current user assign lines to this user?
@@ -16232,7 +16308,10 @@ function useTeamManagementPermissions() {
16232
16308
  availableRolesToAssign: () => {
16233
16309
  if (!currentRole) return [];
16234
16310
  if (currentRole === "optifye") {
16235
- return ["owner", "it", "plant_head", "supervisor"];
16311
+ if (isSuperAdminOptifye) {
16312
+ return ["owner", "it", "plant_head", "supervisor"];
16313
+ }
16314
+ return ["it", "plant_head", "supervisor"];
16236
16315
  }
16237
16316
  if (currentRole === "owner") {
16238
16317
  return ["it", "plant_head", "supervisor"];
@@ -16250,7 +16329,12 @@ function useTeamManagementPermissions() {
16250
16329
  */
16251
16330
  canInviteRole: (role) => {
16252
16331
  if (!currentRole) return false;
16253
- if (currentRole === "optifye") return true;
16332
+ if (currentRole === "optifye") {
16333
+ if (role === "owner") {
16334
+ return isSuperAdminOptifye;
16335
+ }
16336
+ return true;
16337
+ }
16254
16338
  if (currentRole === "owner") {
16255
16339
  return ["it", "plant_head", "supervisor"].includes(role);
16256
16340
  }
@@ -16284,40 +16368,133 @@ function useTeamManagementPermissions() {
16284
16368
  }
16285
16369
  function useUserLineAccess(configLineIds) {
16286
16370
  const { user } = useAuth();
16371
+ const { lines: dbLines } = useLines();
16287
16372
  const { accessibleLineIds, defaultLineId } = React26.useMemo(() => {
16373
+ const dbLineIds = dbLines.map((line) => line.id);
16374
+ const dbLineIdSet = new Set(dbLineIds);
16375
+ const fallbackAllLineIds = configLineIds && configLineIds.length > 0 ? configLineIds : dbLineIds;
16376
+ const normalizeIds = (value) => {
16377
+ if (Array.isArray(value)) {
16378
+ return value.filter((id3) => typeof id3 === "string" && !!id3);
16379
+ }
16380
+ if (typeof value === "string" && value) {
16381
+ return [value];
16382
+ }
16383
+ return [];
16384
+ };
16385
+ const uniqueIds = (ids) => Array.from(new Set(ids.filter(Boolean)));
16386
+ const filterToEnabledDb = (lineIds, options = {}) => {
16387
+ const deduped = uniqueIds(lineIds);
16388
+ if (dbLineIdSet.size === 0) {
16389
+ return options.allowWhenDbUnavailable ? deduped : [];
16390
+ }
16391
+ return deduped.filter((lineId) => dbLineIdSet.has(lineId));
16392
+ };
16288
16393
  if (!user) {
16289
- return { accessibleLineIds: configLineIds || [], defaultLineId: configLineIds?.[0] };
16290
- }
16291
- if (user.role_level === "supervisor") {
16292
- const assignedLineIds = user.properties?.line_id || user.properties?.line_ids || [];
16293
- if (Array.isArray(assignedLineIds) && assignedLineIds.length > 0) {
16294
- console.log("[useUserLineAccess] Supervisor has assigned lines:", assignedLineIds);
16295
- return {
16296
- accessibleLineIds: assignedLineIds,
16297
- defaultLineId: assignedLineIds[0]
16298
- };
16394
+ const fallback = filterToEnabledDb(fallbackAllLineIds, { allowWhenDbUnavailable: true });
16395
+ return { accessibleLineIds: fallback, defaultLineId: fallback[0] };
16396
+ }
16397
+ const configSet = new Set(configLineIds || []);
16398
+ const hasScopePayload = !!user.access_scope || !!user.scope_mode;
16399
+ const role = user.role_level || user.role;
16400
+ const scopedLineIds = normalizeIds(user.access_scope?.line_ids);
16401
+ const scopedFactoryIds = normalizeIds(user.access_scope?.factory_ids);
16402
+ const properties = user.properties || {};
16403
+ const propertyLineIds = normalizeIds(properties?.line_ids || properties?.line_id);
16404
+ const propertyFactoryIds = normalizeIds(properties?.factory_ids || properties?.factory_id);
16405
+ const propertyCompanyId = typeof properties?.company_id === "string" ? properties.company_id : user.company_id;
16406
+ const scopedCompanyId = user.access_scope?.company_id || propertyCompanyId;
16407
+ const isExplicitSuperAdmin = user.scope_mode === "SUPER_ADMIN" || !!user.access_scope?.is_super_admin;
16408
+ const isLegacyOptifyeGlobal = !hasScopePayload && role === "optifye" && propertyLineIds.length === 0 && propertyFactoryIds.length === 0 && !propertyCompanyId;
16409
+ const lineFactoryMap = /* @__PURE__ */ new Map();
16410
+ dbLines.forEach((line) => {
16411
+ lineFactoryMap.set(line.id, line.factory_id);
16412
+ });
16413
+ const applyConfigFilter = (lineIds) => {
16414
+ const dedupedLineIds = uniqueIds(lineIds);
16415
+ if (!configLineIds || configLineIds.length === 0) {
16416
+ return dedupedLineIds;
16299
16417
  }
16300
- console.warn("[useUserLineAccess] Supervisor has NO assigned lines!", {
16301
- properties: user.properties,
16302
- hasLineId: !!user.properties?.line_id,
16303
- hasLineIds: !!user.properties?.line_ids
16418
+ const filtered = dedupedLineIds.filter((id3) => configSet.has(id3));
16419
+ return filtered.length > 0 ? filtered : dedupedLineIds;
16420
+ };
16421
+ const resolveFactoryScopedLines = (factoryIds) => {
16422
+ const factorySet = new Set(factoryIds);
16423
+ const baseLineIds = configLineIds && configLineIds.length > 0 ? configLineIds : dbLineIds;
16424
+ const filtered = baseLineIds.filter((lineId) => {
16425
+ const factoryId = lineFactoryMap.get(lineId);
16426
+ return !!factoryId && factorySet.has(factoryId);
16304
16427
  });
16428
+ if (filtered.length > 0) {
16429
+ return uniqueIds(filtered);
16430
+ }
16431
+ const fromDb = dbLines.filter((line) => !!line.factory_id && factorySet.has(line.factory_id)).map((line) => line.id);
16432
+ if (fromDb.length > 0) {
16433
+ return uniqueIds(fromDb);
16434
+ }
16435
+ return [];
16436
+ };
16437
+ if (isExplicitSuperAdmin || isLegacyOptifyeGlobal) {
16438
+ const all = filterToEnabledDb(fallbackAllLineIds, { allowWhenDbUnavailable: true });
16305
16439
  return {
16306
- accessibleLineIds: [],
16307
- defaultLineId: void 0
16440
+ accessibleLineIds: all,
16441
+ defaultLineId: all[0]
16308
16442
  };
16309
16443
  }
16310
- if (user.role_level === "plant_head") {
16444
+ if (scopedLineIds.length > 0) {
16445
+ const filtered = applyConfigFilter(scopedLineIds);
16446
+ const scoped = filterToEnabledDb(filtered.length > 0 ? filtered : uniqueIds(scopedLineIds));
16311
16447
  return {
16312
- accessibleLineIds: configLineIds || [],
16313
- defaultLineId: configLineIds?.[0]
16448
+ accessibleLineIds: scoped,
16449
+ defaultLineId: scoped[0]
16314
16450
  };
16315
16451
  }
16316
- return {
16317
- accessibleLineIds: configLineIds || [],
16318
- defaultLineId: configLineIds?.[0]
16319
- };
16320
- }, [user, configLineIds]);
16452
+ if (scopedFactoryIds.length > 0) {
16453
+ const scoped = filterToEnabledDb(resolveFactoryScopedLines(scopedFactoryIds));
16454
+ return {
16455
+ accessibleLineIds: scoped,
16456
+ defaultLineId: scoped[0]
16457
+ };
16458
+ }
16459
+ if (scopedCompanyId) {
16460
+ const dbCompanyLineIds = dbLines.filter((line) => !line.company_id || line.company_id === scopedCompanyId).map((line) => line.id);
16461
+ const companyScopedLines = configLineIds && configLineIds.length > 0 ? (() => {
16462
+ const dbCompanySet = new Set(dbCompanyLineIds);
16463
+ const filtered = configLineIds.filter((lineId) => dbCompanySet.has(lineId));
16464
+ if (filtered.length > 0) return filtered;
16465
+ return dbCompanyLineIds;
16466
+ })() : dbCompanyLineIds;
16467
+ const scoped = filterToEnabledDb(companyScopedLines);
16468
+ return {
16469
+ accessibleLineIds: scoped,
16470
+ defaultLineId: scoped[0]
16471
+ };
16472
+ }
16473
+ if (propertyLineIds.length > 0) {
16474
+ const filtered = applyConfigFilter(propertyLineIds);
16475
+ const scoped = filterToEnabledDb(filtered);
16476
+ return {
16477
+ accessibleLineIds: scoped,
16478
+ defaultLineId: scoped[0]
16479
+ };
16480
+ }
16481
+ if (propertyFactoryIds.length > 0) {
16482
+ const scoped = filterToEnabledDb(resolveFactoryScopedLines(propertyFactoryIds));
16483
+ return {
16484
+ accessibleLineIds: scoped,
16485
+ defaultLineId: scoped[0]
16486
+ };
16487
+ }
16488
+ if (role === "owner" || role === "it" || role === "optifye") {
16489
+ const companyScopedLines = scopedCompanyId ? dbLines.filter((line) => !line.company_id || line.company_id === scopedCompanyId).map((line) => line.id) : [];
16490
+ const scoped = filterToEnabledDb(companyScopedLines);
16491
+ return {
16492
+ accessibleLineIds: scoped,
16493
+ defaultLineId: scoped[0]
16494
+ };
16495
+ }
16496
+ return { accessibleLineIds: [], defaultLineId: void 0 };
16497
+ }, [user, configLineIds, dbLines]);
16321
16498
  return { accessibleLineIds, defaultLineId, user };
16322
16499
  }
16323
16500
  function useActiveLineId(queryLineId, configLineIds) {
@@ -16509,12 +16686,7 @@ var useSupervisorsByLineIds = (lineIds, options) => {
16509
16686
  const nextSupervisorsByLineId = /* @__PURE__ */ new Map();
16510
16687
  const nextAllSupervisorsMap = /* @__PURE__ */ new Map();
16511
16688
  if (useBackend && resolvedCompanyId) {
16512
- const searchParams = new URLSearchParams({
16513
- company_id: resolvedCompanyId
16514
- });
16515
- if (lineIdsKey) {
16516
- searchParams.set("line_ids", lineIdsKey);
16517
- }
16689
+ const searchParams = new URLSearchParams({ company_id: resolvedCompanyId });
16518
16690
  const data = await fetchBackendJson(supabase, `/api/dashboard/line-supervisors?${searchParams.toString()}`);
16519
16691
  (data?.supervisors || []).forEach((row) => {
16520
16692
  const assignedLineIds = Array.isArray(row.line_ids) ? row.line_ids : [];
@@ -28334,8 +28506,52 @@ function withAccessControl(WrappedComponent2, options = {}) {
28334
28506
  const WithAccessControlComponent = (props) => {
28335
28507
  const router$1 = router.useRouter();
28336
28508
  const { user, loading: authLoading } = useAuth();
28337
- const { canAccessPage, userRole } = useAccessControl();
28338
- requiredPath || router$1.pathname;
28509
+ const role = user?.role_level;
28510
+ const scope = user?.access_scope;
28511
+ const isSuperAdmin = !!scope?.is_super_admin || user?.scope_mode === "SUPER_ADMIN";
28512
+ const getBasePath = (path) => {
28513
+ const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
28514
+ return firstSegment ? `/${firstSegment}` : "/";
28515
+ };
28516
+ const getIdFromPath = (path, key) => {
28517
+ const queryValue = router$1.query[key];
28518
+ if (typeof queryValue === "string") return queryValue;
28519
+ if (Array.isArray(queryValue) && queryValue.length > 0) return queryValue[0];
28520
+ const segments = path.split("?")[0].split("/").filter(Boolean);
28521
+ const keyIndex = segments.findIndex((segment) => segment.toLowerCase() === key.toLowerCase());
28522
+ if (keyIndex >= 0 && keyIndex < segments.length - 1) return segments[keyIndex + 1];
28523
+ return null;
28524
+ };
28525
+ const roleAccessMap = {
28526
+ optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28527
+ owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28528
+ it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28529
+ plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
28530
+ supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/improvement-center", "/tickets"]
28531
+ };
28532
+ const canAccessPath = (path) => {
28533
+ if (!user || !role) return false;
28534
+ if (isSuperAdmin) return true;
28535
+ const basePath = getBasePath(path);
28536
+ const allowed = roleAccessMap[role] || [];
28537
+ if (!allowed.includes(basePath)) {
28538
+ return false;
28539
+ }
28540
+ const workspaceId = getIdFromPath(path, "workspace");
28541
+ if (workspaceId && scope?.workspace_ids?.length) {
28542
+ return scope.workspace_ids.includes(workspaceId);
28543
+ }
28544
+ const lineId = getIdFromPath(path, "kpis") || getIdFromPath(path, "line");
28545
+ if (lineId && scope?.line_ids?.length) {
28546
+ return scope.line_ids.includes(lineId);
28547
+ }
28548
+ const factoryId = getIdFromPath(path, "factory");
28549
+ if (factoryId && scope?.factory_ids?.length) {
28550
+ return scope.factory_ids.includes(factoryId);
28551
+ }
28552
+ return true;
28553
+ };
28554
+ const pathToCheck = requiredPath || router$1.asPath || router$1.pathname;
28339
28555
  if (authLoading) {
28340
28556
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
28341
28557
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }),
@@ -28345,6 +28561,24 @@ function withAccessControl(WrappedComponent2, options = {}) {
28345
28561
  if (!user) {
28346
28562
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-4", children: "Please log in to continue" }) }) });
28347
28563
  }
28564
+ const hasAccess = canAccessPath(pathToCheck);
28565
+ if (!hasAccess) {
28566
+ if (UnauthorizedComponent) {
28567
+ return /* @__PURE__ */ jsxRuntime.jsx(UnauthorizedComponent, {});
28568
+ }
28569
+ if (typeof window !== "undefined") {
28570
+ router$1.replace(redirectTo);
28571
+ }
28572
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
28573
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Access Denied" }),
28574
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-4", children: "You don't have permission to access this page." }),
28575
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500", children: [
28576
+ "Your role: ",
28577
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: role || "Unknown" })
28578
+ ] }),
28579
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Redirecting to home page..." })
28580
+ ] }) });
28581
+ }
28348
28582
  return /* @__PURE__ */ jsxRuntime.jsx(WrappedComponent2, { ...props });
28349
28583
  };
28350
28584
  WithAccessControlComponent.displayName = `withAccessControl(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
@@ -48658,9 +48892,29 @@ var SideNavBar = React26.memo(({
48658
48892
  }) => {
48659
48893
  const router$1 = router.useRouter();
48660
48894
  const { navigate } = useNavigation();
48661
- const { signOut } = useAuth();
48895
+ const { signOut, user } = useAuth();
48662
48896
  const entityConfig = useEntityConfig();
48663
48897
  const dashboardConfig = useDashboardConfig();
48898
+ const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
48899
+ const role = user?.role_level;
48900
+ const roleAccessMap = {
48901
+ optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48902
+ owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48903
+ it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48904
+ plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
48905
+ supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/tickets", "/improvement-center"]
48906
+ };
48907
+ const getBasePath = React26.useCallback((path) => {
48908
+ const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
48909
+ return firstSegment ? `/${firstSegment}` : "/";
48910
+ }, []);
48911
+ const canAccessPath = React26.useCallback((path) => {
48912
+ if (!role) return false;
48913
+ if (isSuperAdmin) return true;
48914
+ const basePath = getBasePath(path);
48915
+ const allowedPaths = roleAccessMap[role] || [];
48916
+ return allowedPaths.includes(basePath);
48917
+ }, [role, isSuperAdmin, getBasePath]);
48664
48918
  const lineId = entityConfig.defaultLineId || LINE_1_UUID;
48665
48919
  const skuEnabled = dashboardConfig?.skuConfig?.enabled || false;
48666
48920
  dashboardConfig?.supervisorConfig?.enabled || false;
@@ -48826,7 +49080,7 @@ var SideNavBar = React26.memo(({
48826
49080
  const settingsTriggerRef = React26.useRef(null);
48827
49081
  const settingsItems = React26.useMemo(() => {
48828
49082
  const items = [
48829
- {
49083
+ ...canAccessPath("/targets") ? [{
48830
49084
  key: "targets",
48831
49085
  label: "Targets",
48832
49086
  icon: outline.AdjustmentsHorizontalIcon,
@@ -48835,8 +49089,8 @@ var SideNavBar = React26.memo(({
48835
49089
  setIsSettingsOpen(false);
48836
49090
  },
48837
49091
  isActive: pathname === "/targets" || pathname.startsWith("/targets/")
48838
- },
48839
- {
49092
+ }] : [],
49093
+ ...canAccessPath("/shifts") ? [{
48840
49094
  key: "shifts",
48841
49095
  label: "Shifts",
48842
49096
  icon: outline.ClockIcon,
@@ -48845,8 +49099,8 @@ var SideNavBar = React26.memo(({
48845
49099
  setIsSettingsOpen(false);
48846
49100
  },
48847
49101
  isActive: pathname === "/shifts" || pathname.startsWith("/shifts/")
48848
- },
48849
- {
49102
+ }] : [],
49103
+ ...canAccessPath("/team-management") ? [{
48850
49104
  key: "teams",
48851
49105
  label: "Teams",
48852
49106
  icon: outline.UsersIcon,
@@ -48855,8 +49109,8 @@ var SideNavBar = React26.memo(({
48855
49109
  setIsSettingsOpen(false);
48856
49110
  },
48857
49111
  isActive: pathname === "/team-management" || pathname.startsWith("/team-management/")
48858
- },
48859
- {
49112
+ }] : [],
49113
+ ...canAccessPath("/profile") ? [{
48860
49114
  key: "profile",
48861
49115
  label: "Profile",
48862
49116
  icon: outline.UserCircleIcon,
@@ -48865,9 +49119,9 @@ var SideNavBar = React26.memo(({
48865
49119
  setIsSettingsOpen(false);
48866
49120
  },
48867
49121
  isActive: pathname === "/profile" || pathname.startsWith("/profile/")
48868
- }
49122
+ }] : []
48869
49123
  ];
48870
- if (ticketsEnabled) {
49124
+ if (ticketsEnabled && canAccessPath("/tickets")) {
48871
49125
  items.push({
48872
49126
  key: "tickets",
48873
49127
  label: "Tickets",
@@ -48879,18 +49133,20 @@ var SideNavBar = React26.memo(({
48879
49133
  isActive: pathname === "/tickets" || pathname.startsWith("/tickets/")
48880
49134
  });
48881
49135
  }
48882
- items.push({
48883
- key: "help",
48884
- label: "Help",
48885
- icon: outline.QuestionMarkCircleIcon,
48886
- onClick: () => {
48887
- handleHelpClick();
48888
- setIsSettingsOpen(false);
48889
- },
48890
- isActive: pathname === "/help" || pathname.startsWith("/help/")
48891
- });
49136
+ if (canAccessPath("/help")) {
49137
+ items.push({
49138
+ key: "help",
49139
+ label: "Help",
49140
+ icon: outline.QuestionMarkCircleIcon,
49141
+ onClick: () => {
49142
+ handleHelpClick();
49143
+ setIsSettingsOpen(false);
49144
+ },
49145
+ isActive: pathname === "/help" || pathname.startsWith("/help/")
49146
+ });
49147
+ }
48892
49148
  return items;
48893
- }, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled]);
49149
+ }, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled, canAccessPath]);
48894
49150
  const handleLogout = React26.useCallback(async () => {
48895
49151
  setIsSettingsOpen(false);
48896
49152
  try {
@@ -48928,7 +49184,7 @@ var SideNavBar = React26.memo(({
48928
49184
  }
48929
49185
  ) }),
48930
49186
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 w-full py-6 px-4 overflow-y-auto", children: [
48931
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsxRuntime.jsxs(
49187
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6", children: canAccessPath("/") && /* @__PURE__ */ jsxRuntime.jsxs(
48932
49188
  "button",
48933
49189
  {
48934
49190
  onClick: handleHomeClick,
@@ -48944,7 +49200,7 @@ var SideNavBar = React26.memo(({
48944
49200
  }
48945
49201
  ) }),
48946
49202
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
48947
- /* @__PURE__ */ jsxRuntime.jsxs(
49203
+ canAccessPath("/leaderboard") && /* @__PURE__ */ jsxRuntime.jsxs(
48948
49204
  "button",
48949
49205
  {
48950
49206
  onClick: handleLeaderboardClick,
@@ -48959,7 +49215,7 @@ var SideNavBar = React26.memo(({
48959
49215
  ]
48960
49216
  }
48961
49217
  ),
48962
- /* @__PURE__ */ jsxRuntime.jsxs(
49218
+ canAccessPath("/kpis") && /* @__PURE__ */ jsxRuntime.jsxs(
48963
49219
  "button",
48964
49220
  {
48965
49221
  onClick: handleKPIsClick,
@@ -48974,7 +49230,7 @@ var SideNavBar = React26.memo(({
48974
49230
  ]
48975
49231
  }
48976
49232
  ),
48977
- /* @__PURE__ */ jsxRuntime.jsxs(
49233
+ canAccessPath("/improvement-center") && /* @__PURE__ */ jsxRuntime.jsxs(
48978
49234
  "button",
48979
49235
  {
48980
49236
  onClick: handleImprovementClick,
@@ -48990,7 +49246,7 @@ var SideNavBar = React26.memo(({
48990
49246
  }
48991
49247
  ),
48992
49248
  showSupervisorManagement,
48993
- skuEnabled && true && /* @__PURE__ */ jsxRuntime.jsxs(
49249
+ skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxRuntime.jsxs(
48994
49250
  "button",
48995
49251
  {
48996
49252
  onClick: handleSKUsClick,
@@ -49005,7 +49261,7 @@ var SideNavBar = React26.memo(({
49005
49261
  ]
49006
49262
  }
49007
49263
  ),
49008
- /* @__PURE__ */ jsxRuntime.jsxs(
49264
+ canAccessPath("/health") && /* @__PURE__ */ jsxRuntime.jsxs(
49009
49265
  "button",
49010
49266
  {
49011
49267
  onClick: handleHealthClick,
@@ -49022,7 +49278,7 @@ var SideNavBar = React26.memo(({
49022
49278
  )
49023
49279
  ] })
49024
49280
  ] }),
49025
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
49281
+ settingsItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
49026
49282
  "button",
49027
49283
  {
49028
49284
  ref: settingsTriggerRef,
@@ -49060,7 +49316,7 @@ var SideNavBar = React26.memo(({
49060
49316
  };
49061
49317
  };
49062
49318
  return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "px-5 py-6", children: [
49063
- /* @__PURE__ */ jsxRuntime.jsxs(
49319
+ canAccessPath("/") && /* @__PURE__ */ jsxRuntime.jsxs(
49064
49320
  "button",
49065
49321
  {
49066
49322
  onClick: handleMobileNavClick(handleHomeClick),
@@ -49073,7 +49329,7 @@ var SideNavBar = React26.memo(({
49073
49329
  }
49074
49330
  ),
49075
49331
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 space-y-2", children: [
49076
- /* @__PURE__ */ jsxRuntime.jsxs(
49332
+ canAccessPath("/leaderboard") && /* @__PURE__ */ jsxRuntime.jsxs(
49077
49333
  "button",
49078
49334
  {
49079
49335
  onClick: handleMobileNavClick(handleLeaderboardClick),
@@ -49085,7 +49341,7 @@ var SideNavBar = React26.memo(({
49085
49341
  ]
49086
49342
  }
49087
49343
  ),
49088
- /* @__PURE__ */ jsxRuntime.jsxs(
49344
+ canAccessPath("/kpis") && /* @__PURE__ */ jsxRuntime.jsxs(
49089
49345
  "button",
49090
49346
  {
49091
49347
  onClick: handleMobileNavClick(handleKPIsClick),
@@ -49097,7 +49353,7 @@ var SideNavBar = React26.memo(({
49097
49353
  ]
49098
49354
  }
49099
49355
  ),
49100
- /* @__PURE__ */ jsxRuntime.jsxs(
49356
+ canAccessPath("/improvement-center") && /* @__PURE__ */ jsxRuntime.jsxs(
49101
49357
  "button",
49102
49358
  {
49103
49359
  onClick: handleMobileNavClick(handleImprovementClick),
@@ -49110,7 +49366,7 @@ var SideNavBar = React26.memo(({
49110
49366
  }
49111
49367
  ),
49112
49368
  showSupervisorManagement,
49113
- skuEnabled && true && /* @__PURE__ */ jsxRuntime.jsxs(
49369
+ skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxRuntime.jsxs(
49114
49370
  "button",
49115
49371
  {
49116
49372
  onClick: handleMobileNavClick(handleSKUsClick),
@@ -49122,7 +49378,7 @@ var SideNavBar = React26.memo(({
49122
49378
  ]
49123
49379
  }
49124
49380
  ),
49125
- /* @__PURE__ */ jsxRuntime.jsxs(
49381
+ canAccessPath("/health") && /* @__PURE__ */ jsxRuntime.jsxs(
49126
49382
  "button",
49127
49383
  {
49128
49384
  onClick: handleMobileNavClick(handleHealthClick),
@@ -49135,10 +49391,10 @@ var SideNavBar = React26.memo(({
49135
49391
  }
49136
49392
  )
49137
49393
  ] }),
49138
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
49394
+ settingsItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
49139
49395
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "px-5 mb-3 text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Settings & Support" }),
49140
49396
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
49141
- /* @__PURE__ */ jsxRuntime.jsxs(
49397
+ canAccessPath("/targets") && /* @__PURE__ */ jsxRuntime.jsxs(
49142
49398
  "button",
49143
49399
  {
49144
49400
  onClick: handleMobileNavClick(handleTargetsClick),
@@ -49150,7 +49406,7 @@ var SideNavBar = React26.memo(({
49150
49406
  ]
49151
49407
  }
49152
49408
  ),
49153
- /* @__PURE__ */ jsxRuntime.jsxs(
49409
+ canAccessPath("/shifts") && /* @__PURE__ */ jsxRuntime.jsxs(
49154
49410
  "button",
49155
49411
  {
49156
49412
  onClick: handleMobileNavClick(handleShiftsClick),
@@ -49162,7 +49418,7 @@ var SideNavBar = React26.memo(({
49162
49418
  ]
49163
49419
  }
49164
49420
  ),
49165
- /* @__PURE__ */ jsxRuntime.jsxs(
49421
+ canAccessPath("/team-management") && /* @__PURE__ */ jsxRuntime.jsxs(
49166
49422
  "button",
49167
49423
  {
49168
49424
  onClick: handleMobileNavClick(handleTeamManagementClick),
@@ -49174,7 +49430,7 @@ var SideNavBar = React26.memo(({
49174
49430
  ]
49175
49431
  }
49176
49432
  ),
49177
- /* @__PURE__ */ jsxRuntime.jsxs(
49433
+ canAccessPath("/profile") && /* @__PURE__ */ jsxRuntime.jsxs(
49178
49434
  "button",
49179
49435
  {
49180
49436
  onClick: handleMobileNavClick(handleProfileClick),
@@ -49186,7 +49442,7 @@ var SideNavBar = React26.memo(({
49186
49442
  ]
49187
49443
  }
49188
49444
  ),
49189
- ticketsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
49445
+ ticketsEnabled && canAccessPath("/tickets") && /* @__PURE__ */ jsxRuntime.jsxs(
49190
49446
  "button",
49191
49447
  {
49192
49448
  onClick: handleMobileNavClick(handleTicketsClick),
@@ -49198,7 +49454,7 @@ var SideNavBar = React26.memo(({
49198
49454
  ]
49199
49455
  }
49200
49456
  ),
49201
- /* @__PURE__ */ jsxRuntime.jsxs(
49457
+ canAccessPath("/help") && /* @__PURE__ */ jsxRuntime.jsxs(
49202
49458
  "button",
49203
49459
  {
49204
49460
  onClick: handleMobileNavClick(handleHelpClick),
@@ -51454,13 +51710,41 @@ var InviteUserDialog = ({
51454
51710
  const [selectedRole, setSelectedRole] = React26.useState("supervisor");
51455
51711
  const [selectedLines, setSelectedLines] = React26.useState([]);
51456
51712
  const [selectedFactories, setSelectedFactories] = React26.useState([]);
51713
+ const [lineSearch, setLineSearch] = React26.useState("");
51714
+ const [factorySearch, setFactorySearch] = React26.useState("");
51457
51715
  const [isSubmitting, setIsSubmitting] = React26.useState(false);
51458
51716
  const [error, setError] = React26.useState(null);
51459
- const [isLinesDropdownOpen, setIsLinesDropdownOpen] = React26.useState(false);
51460
- const [isFactoriesDropdownOpen, setIsFactoriesDropdownOpen] = React26.useState(false);
51717
+ const isSuperAdminOptifye = user?.role_level === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
51718
+ const canInviteOwner = isSuperAdminOptifye;
51461
51719
  const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
51462
51720
  const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
51463
51721
  const canInviteSupervisor = ["owner", "it", "plant_head", "optifye"].includes(user?.role_level || "");
51722
+ const invitableRoles = React26.useMemo(() => {
51723
+ const roles = [];
51724
+ if (canInviteOwner) roles.push("owner");
51725
+ if (canInviteIT) roles.push("it");
51726
+ if (canInvitePlantHead) roles.push("plant_head");
51727
+ if (canInviteSupervisor) roles.push("supervisor");
51728
+ return roles;
51729
+ }, [canInviteOwner, canInviteIT, canInvitePlantHead, canInviteSupervisor]);
51730
+ const filteredLines = React26.useMemo(() => {
51731
+ const search = lineSearch.trim().toLowerCase();
51732
+ if (!search) return availableLines;
51733
+ return availableLines.filter((line) => line.name.toLowerCase().includes(search));
51734
+ }, [availableLines, lineSearch]);
51735
+ const filteredFactories = React26.useMemo(() => {
51736
+ const search = factorySearch.trim().toLowerCase();
51737
+ if (!search) return availableFactories;
51738
+ return availableFactories.filter((factory) => factory.name.toLowerCase().includes(search));
51739
+ }, [availableFactories, factorySearch]);
51740
+ const selectedLineItems = React26.useMemo(
51741
+ () => availableLines.filter((line) => selectedLines.includes(line.id)),
51742
+ [availableLines, selectedLines]
51743
+ );
51744
+ const selectedFactoryItems = React26.useMemo(
51745
+ () => availableFactories.filter((factory) => selectedFactories.includes(factory.id)),
51746
+ [availableFactories, selectedFactories]
51747
+ );
51464
51748
  React26.useEffect(() => {
51465
51749
  if (!isOpen) {
51466
51750
  setEmail("");
@@ -51469,11 +51753,19 @@ var InviteUserDialog = ({
51469
51753
  setSelectedRole("supervisor");
51470
51754
  setSelectedLines([]);
51471
51755
  setSelectedFactories([]);
51756
+ setLineSearch("");
51757
+ setFactorySearch("");
51472
51758
  setError(null);
51473
- setIsLinesDropdownOpen(false);
51474
- setIsFactoriesDropdownOpen(false);
51475
51759
  }
51476
51760
  }, [isOpen]);
51761
+ React26.useEffect(() => {
51762
+ if (!isOpen || invitableRoles.length === 0) {
51763
+ return;
51764
+ }
51765
+ if (!invitableRoles.includes(selectedRole)) {
51766
+ setSelectedRole(invitableRoles[0]);
51767
+ }
51768
+ }, [isOpen, invitableRoles, selectedRole]);
51477
51769
  const validateEmail = (email2) => {
51478
51770
  const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$/;
51479
51771
  return emailRegex.test(email2);
@@ -51503,6 +51795,14 @@ var InviteUserDialog = ({
51503
51795
  setError("Please enter a valid email address");
51504
51796
  return;
51505
51797
  }
51798
+ if (!invitableRoles.includes(selectedRole)) {
51799
+ setError("You do not have permission to assign this role.");
51800
+ return;
51801
+ }
51802
+ if (selectedRole === "owner" && !canInviteOwner) {
51803
+ setError("Only super-admin Optifye users can create owner users.");
51804
+ return;
51805
+ }
51506
51806
  const companyId = entityConfig?.companyId || user?.properties?.company_id;
51507
51807
  if (!companyId) {
51508
51808
  setError("Company ID not found. Please refresh and try again.");
@@ -51540,7 +51840,6 @@ var InviteUserDialog = ({
51540
51840
  try {
51541
51841
  const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
51542
51842
  if (dashboardUrl) {
51543
- console.log("Sending welcome email to:", email.trim());
51544
51843
  const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
51545
51844
  body: {
51546
51845
  action: "send-welcome-email",
@@ -51553,13 +51852,8 @@ var InviteUserDialog = ({
51553
51852
  console.error("Failed to send welcome email:", emailError);
51554
51853
  sonner.toast.warning("User added successfully, but welcome email could not be sent");
51555
51854
  } else if (emailData?.success) {
51556
- console.log("Welcome email sent successfully:", emailData);
51557
51855
  sonner.toast.success("User added and welcome email sent!");
51558
- } else {
51559
- console.log("Welcome email response:", emailData);
51560
51856
  }
51561
- } else {
51562
- console.warn("Dashboard URL not available, skipping welcome email");
51563
51857
  }
51564
51858
  } catch (emailErr) {
51565
51859
  console.error("Error sending welcome email:", emailErr);
@@ -51585,7 +51879,7 @@ var InviteUserDialog = ({
51585
51879
  children: /* @__PURE__ */ jsxRuntime.jsxs(
51586
51880
  "div",
51587
51881
  {
51588
- className: "bg-white rounded-xl shadow-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto transform transition-all animate-in fade-in duration-200",
51882
+ className: "bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto transform transition-all animate-in fade-in duration-200",
51589
51883
  onClick: (e) => e.stopPropagation(),
51590
51884
  children: [
51591
51885
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
@@ -51613,7 +51907,7 @@ var InviteUserDialog = ({
51613
51907
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
51614
51908
  ] }),
51615
51909
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5", children: [
51616
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
51910
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [
51617
51911
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
51618
51912
  /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "firstName", className: "block text-sm font-medium text-gray-700 mb-2", children: [
51619
51913
  "First Name ",
@@ -51689,6 +51983,33 @@ var InviteUserDialog = ({
51689
51983
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
51690
51984
  ] }),
51691
51985
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
51986
+ canInviteOwner && /* @__PURE__ */ jsxRuntime.jsxs(
51987
+ "label",
51988
+ {
51989
+ className: cn(
51990
+ "flex items-start gap-3 p-4 border-2 rounded-lg cursor-pointer transition-all duration-200",
51991
+ selectedRole === "owner" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"
51992
+ ),
51993
+ children: [
51994
+ /* @__PURE__ */ jsxRuntime.jsx(
51995
+ "input",
51996
+ {
51997
+ type: "radio",
51998
+ name: "role",
51999
+ value: "owner",
52000
+ checked: selectedRole === "owner",
52001
+ onChange: (e) => setSelectedRole(e.target.value),
52002
+ className: "mt-1",
52003
+ disabled: isSubmitting
52004
+ }
52005
+ ),
52006
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
52007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 mb-1", children: /* @__PURE__ */ jsxRuntime.jsx(RoleBadge_default, { role: "owner", size: "sm" }) }),
52008
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: "Company owner access. Full management permissions for the selected company dashboard." })
52009
+ ] })
52010
+ ]
52011
+ }
52012
+ ),
51692
52013
  canInviteIT && /* @__PURE__ */ jsxRuntime.jsxs(
51693
52014
  "label",
51694
52015
  {
@@ -51772,124 +52093,192 @@ var InviteUserDialog = ({
51772
52093
  )
51773
52094
  ] })
51774
52095
  ] }),
51775
- selectedRole === "plant_head" && availableFactories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51776
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51777
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "w-4 h-4 inline mr-1" }),
51778
- "Assign to Factories"
52096
+ selectedRole === "plant_head" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
52097
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
52098
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
52099
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
52100
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "w-4 h-4 text-blue-600" }),
52101
+ "Factory Access"
52102
+ ] }),
52103
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the factories this plant head will manage." })
52104
+ ] }),
52105
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
52106
+ selectedFactories.length,
52107
+ " selected"
52108
+ ] })
51779
52109
  ] }),
51780
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple factories this plant head will manage" }),
51781
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51782
- /* @__PURE__ */ jsxRuntime.jsx(
51783
- "button",
52110
+ availableFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-gray-300 bg-white px-3 py-4 text-sm text-gray-500", children: "No factories available for assignment." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52111
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
52112
+ /* @__PURE__ */ jsxRuntime.jsx(
52113
+ "button",
52114
+ {
52115
+ type: "button",
52116
+ onClick: () => setSelectedFactories(availableFactories.map((factory) => factory.id)),
52117
+ className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
52118
+ disabled: isSubmitting,
52119
+ children: "Select all"
52120
+ }
52121
+ ),
52122
+ /* @__PURE__ */ jsxRuntime.jsx(
52123
+ "button",
52124
+ {
52125
+ type: "button",
52126
+ onClick: () => setSelectedFactories([]),
52127
+ className: "px-2.5 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors",
52128
+ disabled: isSubmitting || selectedFactories.length === 0,
52129
+ children: "Clear"
52130
+ }
52131
+ )
52132
+ ] }),
52133
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
52134
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
52135
+ /* @__PURE__ */ jsxRuntime.jsx(
52136
+ "input",
52137
+ {
52138
+ type: "text",
52139
+ value: factorySearch,
52140
+ onChange: (e) => setFactorySearch(e.target.value),
52141
+ placeholder: "Search factories",
52142
+ className: "w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
52143
+ disabled: isSubmitting
52144
+ }
52145
+ )
52146
+ ] }),
52147
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto rounded-lg border border-gray-200 bg-white divide-y divide-gray-100", children: filteredFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No factories match your search." }) : filteredFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
52148
+ "label",
51784
52149
  {
51785
- type: "button",
51786
- onClick: () => setIsFactoriesDropdownOpen(!isFactoriesDropdownOpen),
51787
- disabled: isSubmitting,
51788
- className: "w-full min-h-[42px] px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left",
51789
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51790
- selectedFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Select factories..." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedFactories.map((factoryId) => {
51791
- const factory = availableFactories.find((f) => f.id === factoryId);
51792
- return factory ? /* @__PURE__ */ jsxRuntime.jsxs(
51793
- "span",
52150
+ className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
52151
+ children: [
52152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
52153
+ /* @__PURE__ */ jsxRuntime.jsx(
52154
+ "input",
51794
52155
  {
51795
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51796
- children: [
51797
- factory.name,
51798
- /* @__PURE__ */ jsxRuntime.jsx(
51799
- "button",
51800
- {
51801
- type: "button",
51802
- onClick: (e) => {
51803
- e.stopPropagation();
51804
- toggleFactorySelection(factory.id);
51805
- },
51806
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51807
- disabled: isSubmitting,
51808
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51809
- }
51810
- )
51811
- ]
51812
- },
51813
- factory.id
51814
- ) : null;
51815
- }) }),
51816
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isFactoriesDropdownOpen && "rotate-180") })
51817
- ] })
51818
- }
51819
- ),
51820
- isFactoriesDropdownOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto", children: availableFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
51821
- "div",
52156
+ type: "checkbox",
52157
+ checked: selectedFactories.includes(factory.id),
52158
+ onChange: () => toggleFactorySelection(factory.id),
52159
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
52160
+ disabled: isSubmitting
52161
+ }
52162
+ ),
52163
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-800 truncate", children: factory.name })
52164
+ ] }),
52165
+ selectedFactories.includes(factory.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
52166
+ ]
52167
+ },
52168
+ factory.id
52169
+ )) }),
52170
+ selectedFactoryItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedFactoryItems.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
52171
+ "span",
51822
52172
  {
51823
- onClick: () => toggleFactorySelection(factory.id),
51824
- className: cn(
51825
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51826
- selectedFactories.includes(factory.id) && "bg-blue-50"
51827
- ),
52173
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51828
52174
  children: [
51829
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: factory.name }),
51830
- selectedFactories.includes(factory.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600" })
52175
+ factory.name,
52176
+ /* @__PURE__ */ jsxRuntime.jsx(
52177
+ "button",
52178
+ {
52179
+ type: "button",
52180
+ onClick: () => toggleFactorySelection(factory.id),
52181
+ className: "hover:bg-blue-200 rounded-sm transition-colors",
52182
+ disabled: isSubmitting,
52183
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
52184
+ }
52185
+ )
51831
52186
  ]
51832
52187
  },
51833
52188
  factory.id
51834
52189
  )) })
51835
52190
  ] })
51836
52191
  ] }),
51837
- selectedRole === "supervisor" && availableLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51838
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51839
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "w-4 h-4 inline mr-1" }),
51840
- "Assign to Lines"
52192
+ selectedRole === "supervisor" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
52193
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
52194
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
52195
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
52196
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "w-4 h-4 text-blue-600" }),
52197
+ "Line Access"
52198
+ ] }),
52199
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the lines this supervisor will monitor." })
52200
+ ] }),
52201
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
52202
+ selectedLines.length,
52203
+ " selected"
52204
+ ] })
51841
52205
  ] }),
51842
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple lines this supervisor will monitor" }),
51843
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51844
- /* @__PURE__ */ jsxRuntime.jsx(
51845
- "button",
52206
+ availableLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-gray-300 bg-white px-3 py-4 text-sm text-gray-500", children: "No lines available for assignment." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52207
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
52208
+ /* @__PURE__ */ jsxRuntime.jsx(
52209
+ "button",
52210
+ {
52211
+ type: "button",
52212
+ onClick: () => setSelectedLines(availableLines.map((line) => line.id)),
52213
+ className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
52214
+ disabled: isSubmitting,
52215
+ children: "Select all"
52216
+ }
52217
+ ),
52218
+ /* @__PURE__ */ jsxRuntime.jsx(
52219
+ "button",
52220
+ {
52221
+ type: "button",
52222
+ onClick: () => setSelectedLines([]),
52223
+ className: "px-2.5 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors",
52224
+ disabled: isSubmitting || selectedLines.length === 0,
52225
+ children: "Clear"
52226
+ }
52227
+ )
52228
+ ] }),
52229
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
52230
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
52231
+ /* @__PURE__ */ jsxRuntime.jsx(
52232
+ "input",
52233
+ {
52234
+ type: "text",
52235
+ value: lineSearch,
52236
+ onChange: (e) => setLineSearch(e.target.value),
52237
+ placeholder: "Search lines",
52238
+ className: "w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
52239
+ disabled: isSubmitting
52240
+ }
52241
+ )
52242
+ ] }),
52243
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto rounded-lg border border-gray-200 bg-white divide-y divide-gray-100", children: filteredLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No lines match your search." }) : filteredLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
52244
+ "label",
51846
52245
  {
51847
- type: "button",
51848
- onClick: () => setIsLinesDropdownOpen(!isLinesDropdownOpen),
51849
- disabled: isSubmitting,
51850
- className: "w-full min-h-[42px] px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left",
51851
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51852
- selectedLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Select lines..." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedLines.map((lineId) => {
51853
- const line = availableLines.find((l) => l.id === lineId);
51854
- return line ? /* @__PURE__ */ jsxRuntime.jsxs(
51855
- "span",
52246
+ className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
52247
+ children: [
52248
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
52249
+ /* @__PURE__ */ jsxRuntime.jsx(
52250
+ "input",
51856
52251
  {
51857
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51858
- children: [
51859
- line.name,
51860
- /* @__PURE__ */ jsxRuntime.jsx(
51861
- "button",
51862
- {
51863
- type: "button",
51864
- onClick: (e) => {
51865
- e.stopPropagation();
51866
- toggleLineSelection(line.id);
51867
- },
51868
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51869
- disabled: isSubmitting,
51870
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51871
- }
51872
- )
51873
- ]
51874
- },
51875
- line.id
51876
- ) : null;
51877
- }) }),
51878
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isLinesDropdownOpen && "rotate-180") })
51879
- ] })
51880
- }
51881
- ),
51882
- isLinesDropdownOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto", children: availableLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
51883
- "div",
52252
+ type: "checkbox",
52253
+ checked: selectedLines.includes(line.id),
52254
+ onChange: () => toggleLineSelection(line.id),
52255
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
52256
+ disabled: isSubmitting
52257
+ }
52258
+ ),
52259
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-800 truncate", children: line.name })
52260
+ ] }),
52261
+ selectedLines.includes(line.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
52262
+ ]
52263
+ },
52264
+ line.id
52265
+ )) }),
52266
+ selectedLineItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedLineItems.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
52267
+ "span",
51884
52268
  {
51885
- onClick: () => toggleLineSelection(line.id),
51886
- className: cn(
51887
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51888
- selectedLines.includes(line.id) && "bg-blue-50"
51889
- ),
52269
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51890
52270
  children: [
51891
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: line.name }),
51892
- selectedLines.includes(line.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600" })
52271
+ line.name,
52272
+ /* @__PURE__ */ jsxRuntime.jsx(
52273
+ "button",
52274
+ {
52275
+ type: "button",
52276
+ onClick: () => toggleLineSelection(line.id),
52277
+ className: "hover:bg-blue-200 rounded-sm transition-colors",
52278
+ disabled: isSubmitting,
52279
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
52280
+ }
52281
+ )
51893
52282
  ]
51894
52283
  },
51895
52284
  line.id
@@ -52424,10 +52813,22 @@ var LineAssignmentDropdown = ({
52424
52813
  canEdit,
52425
52814
  onUpdate
52426
52815
  }) => {
52816
+ const VIEWPORT_MARGIN = 8;
52817
+ const DROPDOWN_GAP = 6;
52818
+ const MIN_DROPDOWN_WIDTH = 300;
52819
+ const MAX_DROPDOWN_WIDTH = 420;
52820
+ const PREFERRED_DROPDOWN_HEIGHT = 420;
52821
+ const MIN_DROPDOWN_HEIGHT = 180;
52427
52822
  const [isOpen, setIsOpen] = React26.useState(false);
52428
52823
  const [selectedIds, setSelectedIds] = React26.useState(currentLineIds);
52429
52824
  const [isSaving, setIsSaving] = React26.useState(false);
52430
- const [position, setPosition] = React26.useState({ top: 0, left: 0, width: 0 });
52825
+ const [position, setPosition] = React26.useState({
52826
+ top: 0,
52827
+ left: 0,
52828
+ width: MIN_DROPDOWN_WIDTH,
52829
+ maxHeight: PREFERRED_DROPDOWN_HEIGHT,
52830
+ openAbove: false
52831
+ });
52431
52832
  const buttonRef = React26.useRef(null);
52432
52833
  const dropdownRef = React26.useRef(null);
52433
52834
  React26.useEffect(() => {
@@ -52437,10 +52838,31 @@ var LineAssignmentDropdown = ({
52437
52838
  const updatePosition = () => {
52438
52839
  if (isOpen && buttonRef.current) {
52439
52840
  const rect = buttonRef.current.getBoundingClientRect();
52841
+ const viewportWidth = window.innerWidth;
52842
+ const viewportHeight = window.innerHeight;
52843
+ const width = Math.min(
52844
+ Math.max(rect.width, MIN_DROPDOWN_WIDTH),
52845
+ Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
52846
+ );
52847
+ const left = Math.min(
52848
+ Math.max(VIEWPORT_MARGIN, rect.left),
52849
+ viewportWidth - width - VIEWPORT_MARGIN
52850
+ );
52851
+ const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
52852
+ const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
52853
+ const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
52854
+ const availableSpace = openAbove ? spaceAbove : spaceBelow;
52855
+ const maxHeight = Math.max(
52856
+ MIN_DROPDOWN_HEIGHT,
52857
+ Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
52858
+ );
52859
+ const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
52440
52860
  setPosition({
52441
- top: rect.bottom,
52442
- left: rect.left,
52443
- width: rect.width
52861
+ top,
52862
+ left,
52863
+ width,
52864
+ maxHeight,
52865
+ openAbove
52444
52866
  });
52445
52867
  }
52446
52868
  };
@@ -52518,7 +52940,7 @@ var LineAssignmentDropdown = ({
52518
52940
  assignedLines.length - 2
52519
52941
  ] });
52520
52942
  };
52521
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentLineIds.sort());
52943
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentLineIds].sort());
52522
52944
  if (!canEdit) {
52523
52945
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: getDisplayText() });
52524
52946
  }
@@ -52548,13 +52970,13 @@ var LineAssignmentDropdown = ({
52548
52970
  "div",
52549
52971
  {
52550
52972
  ref: dropdownRef,
52551
- className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
52973
+ className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
52552
52974
  style: {
52553
- top: `${position.top + 4}px`,
52975
+ top: `${position.top}px`,
52554
52976
  left: `${position.left}px`,
52555
- minWidth: `${Math.max(position.width, 300)}px`,
52556
- maxWidth: "400px",
52557
- maxHeight: "calc(100vh - 100px)"
52977
+ width: `${position.width}px`,
52978
+ maxHeight: `${position.maxHeight}px`,
52979
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52558
52980
  },
52559
52981
  children: [
52560
52982
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
@@ -52571,7 +52993,7 @@ var LineAssignmentDropdown = ({
52571
52993
  }
52572
52994
  )
52573
52995
  ] }) }),
52574
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
52996
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
52575
52997
  "label",
52576
52998
  {
52577
52999
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -52631,10 +53053,22 @@ var FactoryAssignmentDropdown = ({
52631
53053
  canEdit,
52632
53054
  onUpdate
52633
53055
  }) => {
53056
+ const VIEWPORT_MARGIN = 8;
53057
+ const DROPDOWN_GAP = 6;
53058
+ const MIN_DROPDOWN_WIDTH = 300;
53059
+ const MAX_DROPDOWN_WIDTH = 420;
53060
+ const PREFERRED_DROPDOWN_HEIGHT = 420;
53061
+ const MIN_DROPDOWN_HEIGHT = 180;
52634
53062
  const [isOpen, setIsOpen] = React26.useState(false);
52635
53063
  const [selectedIds, setSelectedIds] = React26.useState(currentFactoryIds);
52636
53064
  const [isSaving, setIsSaving] = React26.useState(false);
52637
- const [position, setPosition] = React26.useState({ top: 0, left: 0, width: 0 });
53065
+ const [position, setPosition] = React26.useState({
53066
+ top: 0,
53067
+ left: 0,
53068
+ width: MIN_DROPDOWN_WIDTH,
53069
+ maxHeight: PREFERRED_DROPDOWN_HEIGHT,
53070
+ openAbove: false
53071
+ });
52638
53072
  const buttonRef = React26.useRef(null);
52639
53073
  const dropdownRef = React26.useRef(null);
52640
53074
  React26.useEffect(() => {
@@ -52644,10 +53078,31 @@ var FactoryAssignmentDropdown = ({
52644
53078
  const updatePosition = () => {
52645
53079
  if (isOpen && buttonRef.current) {
52646
53080
  const rect = buttonRef.current.getBoundingClientRect();
53081
+ const viewportWidth = window.innerWidth;
53082
+ const viewportHeight = window.innerHeight;
53083
+ const width = Math.min(
53084
+ Math.max(rect.width, MIN_DROPDOWN_WIDTH),
53085
+ Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
53086
+ );
53087
+ const left = Math.min(
53088
+ Math.max(VIEWPORT_MARGIN, rect.left),
53089
+ viewportWidth - width - VIEWPORT_MARGIN
53090
+ );
53091
+ const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
53092
+ const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
53093
+ const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
53094
+ const availableSpace = openAbove ? spaceAbove : spaceBelow;
53095
+ const maxHeight = Math.max(
53096
+ MIN_DROPDOWN_HEIGHT,
53097
+ Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
53098
+ );
53099
+ const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
52647
53100
  setPosition({
52648
- top: rect.bottom,
52649
- left: rect.left,
52650
- width: rect.width
53101
+ top,
53102
+ left,
53103
+ width,
53104
+ maxHeight,
53105
+ openAbove
52651
53106
  });
52652
53107
  }
52653
53108
  };
@@ -52725,7 +53180,7 @@ var FactoryAssignmentDropdown = ({
52725
53180
  assignedFactories.length - 2
52726
53181
  ] });
52727
53182
  };
52728
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentFactoryIds.sort());
53183
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentFactoryIds].sort());
52729
53184
  if (!canEdit) {
52730
53185
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: getDisplayText() });
52731
53186
  }
@@ -52755,13 +53210,13 @@ var FactoryAssignmentDropdown = ({
52755
53210
  "div",
52756
53211
  {
52757
53212
  ref: dropdownRef,
52758
- className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
53213
+ className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
52759
53214
  style: {
52760
- top: `${position.top + 4}px`,
53215
+ top: `${position.top}px`,
52761
53216
  left: `${position.left}px`,
52762
- minWidth: `${Math.max(position.width, 300)}px`,
52763
- maxWidth: "400px",
52764
- maxHeight: "calc(100vh - 100px)"
53217
+ width: `${position.width}px`,
53218
+ maxHeight: `${position.maxHeight}px`,
53219
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52765
53220
  },
52766
53221
  children: [
52767
53222
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
@@ -52778,7 +53233,7 @@ var FactoryAssignmentDropdown = ({
52778
53233
  }
52779
53234
  )
52780
53235
  ] }) }),
52781
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
53236
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
52782
53237
  "label",
52783
53238
  {
52784
53239
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -53426,11 +53881,32 @@ var UserManagementTable = ({
53426
53881
  setSortDirection("asc");
53427
53882
  }
53428
53883
  };
53884
+ const getFactoryIdsFromUser = (user) => {
53885
+ const properties = user.properties || {};
53886
+ const rawFactoryIds = properties.factory_ids ?? properties.factory_id;
53887
+ if (Array.isArray(rawFactoryIds)) {
53888
+ return rawFactoryIds.filter((factoryId) => typeof factoryId === "string" && factoryId.length > 0);
53889
+ }
53890
+ if (typeof rawFactoryIds === "string" && rawFactoryIds.length > 0) {
53891
+ return [rawFactoryIds];
53892
+ }
53893
+ return [];
53894
+ };
53429
53895
  const formatAssignments = (user) => {
53430
53896
  if (user.role_level === "owner" || user.role_level === "it") return "Company-wide";
53431
53897
  if (user.role_level === "optifye") return "All companies";
53432
- if (user.role_level === "plant_head" && user.assigned_factories) {
53433
- return user.assigned_factories.join(", ") || "No factories assigned";
53898
+ if (user.role_level === "plant_head") {
53899
+ if (user.assigned_factories && user.assigned_factories.length > 0) {
53900
+ return user.assigned_factories.join(", ");
53901
+ }
53902
+ const assignedFactoryIds = getFactoryIdsFromUser(user);
53903
+ if (assignedFactoryIds.length === 1) {
53904
+ return "1 factory assigned";
53905
+ }
53906
+ if (assignedFactoryIds.length > 1) {
53907
+ return `${assignedFactoryIds.length} factories assigned`;
53908
+ }
53909
+ return "No factories assigned";
53434
53910
  }
53435
53911
  if (user.role_level === "supervisor" && user.assigned_lines) {
53436
53912
  return user.assigned_lines.join(", ") || "No lines assigned";
@@ -53622,22 +54098,28 @@ var UserManagementTable = ({
53622
54098
  onUpdate: onLineAssignmentUpdate || (async () => {
53623
54099
  })
53624
54100
  }
53625
- ) : user.role_level === "plant_head" ? /* @__PURE__ */ jsxRuntime.jsx(
53626
- FactoryAssignmentDropdown,
53627
- {
53628
- userId: user.user_id,
53629
- currentFactoryIds: user.properties?.factory_ids || [],
53630
- availableFactories: (
53631
- // Filter factories to only show those from the target user's company
53632
- user.properties?.company_id ? availableFactories.filter(
53633
- (factory) => factory.company_id === user.properties?.company_id
53634
- ) : availableFactories
53635
- ),
53636
- canEdit: permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate,
53637
- onUpdate: onFactoryAssignmentUpdate || (async () => {
53638
- })
54101
+ ) : user.role_level === "plant_head" ? (() => {
54102
+ const canEditFactoryAssignments = permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate;
54103
+ if (!canEditFactoryAssignments) {
54104
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) });
53639
54105
  }
53640
- ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
54106
+ return /* @__PURE__ */ jsxRuntime.jsx(
54107
+ FactoryAssignmentDropdown,
54108
+ {
54109
+ userId: user.user_id,
54110
+ currentFactoryIds: getFactoryIdsFromUser(user),
54111
+ availableFactories: (
54112
+ // Filter factories to only show those from the target user's company
54113
+ user.properties?.company_id ? availableFactories.filter(
54114
+ (factory) => factory.company_id === user.properties?.company_id
54115
+ ) : availableFactories
54116
+ ),
54117
+ canEdit: canEditFactoryAssignments,
54118
+ onUpdate: onFactoryAssignmentUpdate || (async () => {
54119
+ })
54120
+ }
54121
+ );
54122
+ })() : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
53641
54123
  showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4", children: user.role_level === "plant_head" || user.role_level === "supervisor" ? /* @__PURE__ */ jsxRuntime.jsx(
53642
54124
  "button",
53643
54125
  {
@@ -54802,52 +55284,18 @@ function HomeView({
54802
55284
  });
54803
55285
  return merged;
54804
55286
  }, [lineNames, dbLines]);
54805
- const isOwner = user?.role_level === "owner";
54806
- const isPlantHead = user?.role_level === "plant_head";
55287
+ const enabledLineIdSet = React26.useMemo(
55288
+ () => new Set(dbLines.filter((line) => line.enable).map((line) => line.id)),
55289
+ [dbLines]
55290
+ );
54807
55291
  const isSupervisor = user?.role_level === "supervisor";
54808
- const assignedLineIds = React26.useMemo(() => {
54809
- const rawLineIds = user?.properties?.line_id || user?.properties?.line_ids || [];
54810
- return Array.isArray(rawLineIds) ? rawLineIds : [];
54811
- }, [user]);
54812
- const assignedLineIdsInConfig = React26.useMemo(() => {
54813
- if (assignedLineIds.length === 0) {
54814
- return [];
54815
- }
54816
- return assignedLineIds.filter((id3) => allLineIds.includes(id3));
54817
- }, [assignedLineIds, allLineIds]);
54818
- const assignedFactoryIds = React26.useMemo(() => {
54819
- const rawFactoryIds = user?.properties?.factory_id || user?.properties?.factory_ids || [];
54820
- return Array.isArray(rawFactoryIds) ? rawFactoryIds : [];
54821
- }, [user]);
54822
- const lineFactoryMap = React26.useMemo(() => {
54823
- const map = /* @__PURE__ */ new Map();
54824
- dbLines.forEach((line) => {
54825
- map.set(line.id, line.factory_id);
54826
- });
54827
- return map;
54828
- }, [dbLines]);
54829
- const plantHeadLineIds = React26.useMemo(() => {
54830
- if (!isPlantHead || assignedFactoryIds.length === 0) {
54831
- return [];
54832
- }
54833
- const assignedFactoryIdSet = new Set(assignedFactoryIds);
54834
- return allLineIds.filter((lineId) => assignedFactoryIdSet.has(lineFactoryMap.get(lineId) || ""));
54835
- }, [isPlantHead, assignedFactoryIds, allLineIds, lineFactoryMap]);
54836
55292
  const visibleLineIds = React26.useMemo(() => {
54837
- if (isOwner) {
54838
- return allLineIds;
54839
- }
54840
- if (isPlantHead) {
54841
- const combinedLineIds = /* @__PURE__ */ new Set();
54842
- plantHeadLineIds.forEach((lineId) => combinedLineIds.add(lineId));
54843
- assignedLineIdsInConfig.forEach((lineId) => combinedLineIds.add(lineId));
54844
- return Array.from(combinedLineIds);
55293
+ const scoped = Array.from(new Set(allLineIds.filter(Boolean)));
55294
+ if (enabledLineIdSet.size === 0) {
55295
+ return scoped;
54845
55296
  }
54846
- if (isSupervisor) {
54847
- return assignedLineIdsInConfig;
54848
- }
54849
- return allLineIds;
54850
- }, [isOwner, isPlantHead, isSupervisor, allLineIds, plantHeadLineIds, assignedLineIdsInConfig]);
55297
+ return scoped.filter((lineId) => enabledLineIdSet.has(lineId));
55298
+ }, [allLineIds, enabledLineIdSet]);
54851
55299
  const fallbackLineId = visibleLineIds[0] || defaultLineId;
54852
55300
  const defaultHomeLineId = fallbackLineId;
54853
55301
  const availableLineIds = React26.useMemo(() => {
@@ -54874,7 +55322,7 @@ function HomeView({
54874
55322
  return defaultHomeLineId;
54875
55323
  });
54876
55324
  React26.useEffect(() => {
54877
- if (!user || !isSupervisor && !isPlantHead || availableLineIds.length === 0) {
55325
+ if (!user || availableLineIds.length === 0) {
54878
55326
  return;
54879
55327
  }
54880
55328
  try {
@@ -54888,13 +55336,14 @@ function HomeView({
54888
55336
  } catch (error) {
54889
55337
  console.warn("Failed to read line filter from sessionStorage:", error);
54890
55338
  }
55339
+ if (availableLineIds.includes(selectedLineId)) {
55340
+ return;
55341
+ }
54891
55342
  if (defaultHomeLineId !== selectedLineId) {
54892
55343
  setSelectedLineId(defaultHomeLineId);
54893
55344
  }
54894
55345
  }, [
54895
55346
  user,
54896
- isSupervisor,
54897
- isPlantHead,
54898
55347
  availableLineIds,
54899
55348
  defaultHomeLineId,
54900
55349
  selectedLineId,
@@ -54913,14 +55362,14 @@ function HomeView({
54913
55362
  const [diagnoses, setDiagnoses] = React26.useState([]);
54914
55363
  const timezone = useAppTimezone();
54915
55364
  const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
54916
- allLineIds,
55365
+ visibleLineIds,
54917
55366
  dashboardConfig?.shiftConfig
54918
55367
  );
54919
55368
  React26.useEffect(() => {
54920
55369
  const initDisplayNames = async () => {
54921
55370
  try {
54922
55371
  if (selectedLineId === factoryViewId) {
54923
- for (const lineId of allLineIds) {
55372
+ for (const lineId of visibleLineIds) {
54924
55373
  await preInitializeWorkspaceDisplayNames(lineId);
54925
55374
  }
54926
55375
  } else {
@@ -54933,7 +55382,7 @@ function HomeView({
54933
55382
  }
54934
55383
  };
54935
55384
  initDisplayNames();
54936
- }, [selectedLineId, factoryViewId, allLineIds]);
55385
+ }, [selectedLineId, factoryViewId, visibleLineIds]);
54937
55386
  const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
54938
55387
  const {
54939
55388
  displayNames: workspaceDisplayNames,
@@ -58684,8 +59133,32 @@ var KPIsOverviewView = ({
58684
59133
  const dbTimezone = useAppTimezone();
58685
59134
  const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
58686
59135
  const { startDate: monthStartDate, endDate: monthEndDateKey, monthEndDate } = getMonthDateInfo(configuredTimezone);
58687
- const isSupervisor = user?.role_level === "supervisor";
58688
- const assignedLineIdsForLeaderboard = isSupervisor ? lineIds : void 0;
59136
+ const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
59137
+ const scopedLineIds = React26__namespace.default.useMemo(
59138
+ () => Array.isArray(user?.access_scope?.line_ids) ? user.access_scope.line_ids.filter((lineId) => typeof lineId === "string" && lineId.length > 0) : [],
59139
+ [user?.access_scope?.line_ids]
59140
+ );
59141
+ const hasCanonicalScope = !!user?.scope_mode || !!user?.access_scope;
59142
+ const scopeRole = (user?.role_level || user?.role || "").toLowerCase();
59143
+ const isStrictLineScopedRole = scopeRole === "supervisor" || scopeRole === "plant_head";
59144
+ const resolvedAssignedLineIds = React26__namespace.default.useMemo(() => {
59145
+ if (isSuperAdmin) return [];
59146
+ if (scopedLineIds.length > 0) return scopedLineIds;
59147
+ if (lineIds && lineIds.length > 0) return lineIds;
59148
+ if (isStrictLineScopedRole && hasCanonicalScope) return [];
59149
+ return [];
59150
+ }, [isSuperAdmin, scopedLineIds, lineIds, isStrictLineScopedRole, hasCanonicalScope]);
59151
+ const assignedLineIdSet = React26__namespace.default.useMemo(
59152
+ () => new Set(resolvedAssignedLineIds),
59153
+ [resolvedAssignedLineIds]
59154
+ );
59155
+ const metricsLineIds = React26__namespace.default.useMemo(() => {
59156
+ if (isSuperAdmin) {
59157
+ return lineIds ?? [];
59158
+ }
59159
+ return resolvedAssignedLineIds;
59160
+ }, [isSuperAdmin, lineIds, resolvedAssignedLineIds]);
59161
+ const assignedLineIdsForLeaderboard = isSuperAdmin ? void 0 : resolvedAssignedLineIds;
58689
59162
  const leaderboardLinesForView = React26__namespace.default.useMemo(() => {
58690
59163
  const targetMode = viewType === "machine" ? "uptime" : "output";
58691
59164
  return leaderboardLines.filter((line) => (line.monitoring_mode ?? "output") === targetMode);
@@ -58737,7 +59210,7 @@ var KPIsOverviewView = ({
58737
59210
  error: metricsError
58738
59211
  } = useDashboardMetrics({
58739
59212
  lineId: factoryViewId,
58740
- userAccessibleLineIds: lineIds
59213
+ userAccessibleLineIds: metricsLineIds
58741
59214
  });
58742
59215
  const defaultKPIs = React26__namespace.default.useMemo(() => createDefaultKPIs(), []);
58743
59216
  const kpisByLineId = React26__namespace.default.useMemo(() => {
@@ -58791,16 +59264,16 @@ var KPIsOverviewView = ({
58791
59264
  console.log("[KPIsOverviewView] Fetching lines with lineIds filter:", lineIds);
58792
59265
  const allLines = await dashboardService.getAllLines();
58793
59266
  let filteredLines = allLines;
58794
- if (lineIds && lineIds.length > 0) {
58795
- filteredLines = allLines.filter((line) => lineIds.includes(line.id));
58796
- console.log("[KPIsOverviewView] Filtered lines:", {
59267
+ if (!isSuperAdmin) {
59268
+ filteredLines = allLines.filter((line) => assignedLineIdSet.has(line.id));
59269
+ console.log("[KPIsOverviewView] Applied scoped line filter:", {
58797
59270
  total: allLines.length,
58798
59271
  filtered: filteredLines.length,
58799
- lineIds,
59272
+ allowedLineIds: resolvedAssignedLineIds,
58800
59273
  filteredLineIds: filteredLines.map((l) => l.id)
58801
59274
  });
58802
59275
  } else {
58803
- console.log("[KPIsOverviewView] No lineIds filter, showing all lines:", allLines.length);
59276
+ console.log("[KPIsOverviewView] Super admin view, showing all lines:", allLines.length);
58804
59277
  }
58805
59278
  setLines(filteredLines);
58806
59279
  } catch (err) {
@@ -58811,34 +59284,36 @@ var KPIsOverviewView = ({
58811
59284
  }
58812
59285
  };
58813
59286
  fetchLines();
58814
- }, [supabase, dashboardConfig, lineIds]);
59287
+ }, [supabase, dashboardConfig, lineIds, isSuperAdmin, assignedLineIdSet, resolvedAssignedLineIds]);
58815
59288
  React26.useEffect(() => {
58816
59289
  let isMounted = true;
58817
59290
  const fetchLeaderboardLines = async () => {
58818
59291
  if (!supabase || !resolvedCompanyId) {
58819
- setLeaderboardLines(lines);
59292
+ setLeaderboardLines(lines.filter((line) => line.enable !== false));
58820
59293
  return;
58821
59294
  }
58822
59295
  setLeaderboardLinesLoading(true);
58823
59296
  try {
58824
- const linesService2 = new LinesService(supabase);
58825
- const companyLines = await linesService2.getLinesByCompanyId(resolvedCompanyId);
59297
+ const data = await fetchBackendJson(
59298
+ supabase,
59299
+ `/api/dashboard/leaderboard-lines?company_id=${encodeURIComponent(resolvedCompanyId)}`
59300
+ );
58826
59301
  if (!isMounted) return;
58827
- const transformed = companyLines.map((line) => ({
59302
+ const transformed = (data.lines || []).filter((line) => line.enable !== false).map((line) => ({
58828
59303
  id: line.id,
58829
- line_name: line.name,
58830
- factory_id: line.factoryId || "",
59304
+ line_name: line.line_name,
59305
+ factory_id: line.factory_id || "",
58831
59306
  factory_name: "N/A",
58832
- company_id: line.companyId,
59307
+ company_id: line.company_id,
58833
59308
  company_name: "",
58834
- enable: line.isActive ?? true,
58835
- monitoring_mode: line.monitoringMode ?? "output"
59309
+ enable: line.enable ?? true,
59310
+ monitoring_mode: line.monitoring_mode ?? "output"
58836
59311
  }));
58837
59312
  setLeaderboardLines(transformed);
58838
59313
  } catch (err) {
58839
59314
  console.error("[KPIsOverviewView] Failed to load leaderboard lines:", err);
58840
59315
  if (!isMounted) return;
58841
- setLeaderboardLines(lines);
59316
+ setLeaderboardLines(lines.filter((line) => line.enable !== false));
58842
59317
  } finally {
58843
59318
  if (isMounted) setLeaderboardLinesLoading(false);
58844
59319
  }
@@ -58919,11 +59394,10 @@ var KPIsOverviewView = ({
58919
59394
  lineMode: viewType === "machine" ? "uptime" : "output"
58920
59395
  });
58921
59396
  const nextMap = /* @__PURE__ */ new Map();
59397
+ targetLineIds.forEach((lineId) => nextMap.set(lineId, 0));
58922
59398
  entries.forEach((entry) => {
58923
59399
  const value = Number(entry.avg_efficiency);
58924
- if (Number.isFinite(value)) {
58925
- nextMap.set(entry.line_id, value);
58926
- }
59400
+ nextMap.set(entry.line_id, Number.isFinite(value) ? value : 0);
58927
59401
  });
58928
59402
  setTodayEfficiencyByLineId(nextMap);
58929
59403
  } catch (err) {
@@ -59006,6 +59480,9 @@ var KPIsOverviewView = ({
59006
59480
  };
59007
59481
  };
59008
59482
  const handleLineClick = (line, kpis) => {
59483
+ if (!isSuperAdmin && !assignedLineIdSet.has(line.id)) {
59484
+ return;
59485
+ }
59009
59486
  const trackProps = {
59010
59487
  line_id: line.id,
59011
59488
  line_name: line.line_name,
@@ -59518,6 +59995,7 @@ var MobileWorkspaceCard = React26.memo(({
59518
59995
  workspace,
59519
59996
  rank,
59520
59997
  cardClass,
59998
+ isClickable,
59521
59999
  onWorkspaceClick,
59522
60000
  getMedalIcon,
59523
60001
  efficiencyLabel
@@ -59530,8 +60008,8 @@ var MobileWorkspaceCard = React26.memo(({
59530
60008
  transition: {
59531
60009
  layout: { duration: 0.3, ease: "easeInOut" }
59532
60010
  },
59533
- onClick: () => onWorkspaceClick(workspace, rank),
59534
- className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] cursor-pointer`,
60011
+ onClick: isClickable ? () => onWorkspaceClick(workspace, rank) : void 0,
60012
+ className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] ${isClickable ? "cursor-pointer" : "cursor-not-allowed opacity-75"}`,
59535
60013
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
59536
60014
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
59537
60015
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -59553,13 +60031,14 @@ var MobileWorkspaceCard = React26.memo(({
59553
60031
  ] })
59554
60032
  }
59555
60033
  ), (prevProps, nextProps) => {
59556
- 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;
60034
+ 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;
59557
60035
  });
59558
60036
  MobileWorkspaceCard.displayName = "MobileWorkspaceCard";
59559
60037
  var DesktopWorkspaceRow = React26.memo(({
59560
60038
  workspace,
59561
60039
  index,
59562
60040
  rowClass,
60041
+ isClickable,
59563
60042
  onWorkspaceClick,
59564
60043
  getMedalIcon
59565
60044
  }) => /* @__PURE__ */ jsxRuntime.jsxs(
@@ -59569,8 +60048,8 @@ var DesktopWorkspaceRow = React26.memo(({
59569
60048
  layoutId: `row-${workspace.workspace_uuid}`,
59570
60049
  initial: false,
59571
60050
  transition: { layout: { duration: 0.3, ease: "easeInOut" } },
59572
- onClick: () => onWorkspaceClick(workspace, index + 1),
59573
- className: `${rowClass} hover:bg-gray-50/90 transition-colors duration-150 cursor-pointer group`,
60051
+ onClick: isClickable ? () => onWorkspaceClick(workspace, index + 1) : void 0,
60052
+ className: `${rowClass} transition-colors duration-150 ${isClickable ? "hover:bg-gray-50/90 cursor-pointer group" : "cursor-not-allowed opacity-75"}`,
59574
60053
  children: [
59575
60054
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap group-hover:font-medium", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
59576
60055
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: index + 1 }),
@@ -59582,7 +60061,7 @@ var DesktopWorkspaceRow = React26.memo(({
59582
60061
  ]
59583
60062
  }
59584
60063
  ), (prevProps, nextProps) => {
59585
- 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;
60064
+ 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;
59586
60065
  });
59587
60066
  DesktopWorkspaceRow.displayName = "DesktopWorkspaceRow";
59588
60067
  var LeaderboardDetailView = React26.memo(({
@@ -59742,6 +60221,16 @@ var LeaderboardDetailView = React26.memo(({
59742
60221
  }
59743
60222
  return allLineIds;
59744
60223
  }, [entityConfig, userAccessibleLineIds]);
60224
+ const accessibleLineIdSet = React26.useMemo(
60225
+ () => new Set((userAccessibleLineIds || []).filter(Boolean)),
60226
+ [userAccessibleLineIds]
60227
+ );
60228
+ const canOpenWorkspace = React26.useCallback((workspaceLineId) => {
60229
+ if (!workspaceLineId) return false;
60230
+ if (!userAccessibleLineIds) return true;
60231
+ if (accessibleLineIdSet.size === 0) return false;
60232
+ return accessibleLineIdSet.has(workspaceLineId);
60233
+ }, [accessibleLineIdSet, userAccessibleLineIds]);
59745
60234
  const { hasUptime: lineModeHasUptime, hasOutput: lineModeHasOutput } = React26.useMemo(() => {
59746
60235
  if (!lines || lines.length === 0) {
59747
60236
  return { hasUptime: false, hasOutput: false };
@@ -60139,6 +60628,9 @@ var LeaderboardDetailView = React26.memo(({
60139
60628
  return `${startDate.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${endDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
60140
60629
  }, [normalizedRange]);
60141
60630
  const handleWorkspaceClick = React26.useCallback((workspace, rank) => {
60631
+ if (!canOpenWorkspace(workspace.line_id)) {
60632
+ return;
60633
+ }
60142
60634
  trackCoreEvent("Workspace from Leaderboard Clicked", {
60143
60635
  workspace_name: workspace.workspace_name,
60144
60636
  workspace_id: workspace.workspace_uuid,
@@ -60171,7 +60663,7 @@ var LeaderboardDetailView = React26.memo(({
60171
60663
  const combinedParams = navParams ? `${navParams}&${contextParamString}` : `?${contextParamString}`;
60172
60664
  navigation.navigate(`/workspace/${workspace.workspace_uuid}${combinedParams}`);
60173
60665
  }
60174
- }, [onWorkspaceClick, navigation, date, shiftId]);
60666
+ }, [canOpenWorkspace, onWorkspaceClick, navigation, date, shiftId]);
60175
60667
  React26.useEffect(() => {
60176
60668
  workspacesLengthRef.current = activeEntries.length || 0;
60177
60669
  }, [activeEntries.length]);
@@ -60462,6 +60954,7 @@ var LeaderboardDetailView = React26.memo(({
60462
60954
  workspace: ws,
60463
60955
  rank,
60464
60956
  cardClass,
60957
+ isClickable: canOpenWorkspace(ws.line_id),
60465
60958
  onWorkspaceClick: stableHandleWorkspaceClick,
60466
60959
  getMedalIcon: stableGetMedalIcon,
60467
60960
  efficiencyLabel
@@ -60486,6 +60979,7 @@ var LeaderboardDetailView = React26.memo(({
60486
60979
  workspace: ws,
60487
60980
  index,
60488
60981
  rowClass,
60982
+ isClickable: canOpenWorkspace(ws.line_id),
60489
60983
  onWorkspaceClick: stableHandleWorkspaceClick,
60490
60984
  getMedalIcon: stableGetMedalIcon
60491
60985
  },
@@ -60496,7 +60990,7 @@ var LeaderboardDetailView = React26.memo(({
60496
60990
  ) })
60497
60991
  ] });
60498
60992
  }, (prevProps, nextProps) => {
60499
- 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;
60993
+ 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;
60500
60994
  });
60501
60995
  LeaderboardDetailView.displayName = "LeaderboardDetailView";
60502
60996
  var LeaderboardDetailViewWithDisplayNames = withAllWorkspaceDisplayNames(LeaderboardDetailView);
@@ -60911,6 +61405,20 @@ var ProfileView = () => {
60911
61405
  ] });
60912
61406
  };
60913
61407
  var ProfileView_default = ProfileView;
61408
+
61409
+ // src/lib/constants/actions.ts
61410
+ var ACTION_NAMES = {
61411
+ /** Assembly operations */
61412
+ ASSEMBLY: "Assembly",
61413
+ /** Packaging operations */
61414
+ PACKAGING: "Packaging",
61415
+ /** Inspection operations */
61416
+ INSPECTION: "Inspection",
61417
+ /** Testing operations */
61418
+ TESTING: "Testing",
61419
+ /** Quality control operations */
61420
+ QUALITY_CONTROL: "Quality Control"
61421
+ };
60914
61422
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
60915
61423
  if (!startTime || !endTime) return 8;
60916
61424
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -60924,6 +61432,43 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
60924
61432
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
60925
61433
  return Number(hoursDiff.toFixed(1));
60926
61434
  };
61435
+ var calculateBreakDuration2 = (startTime, endTime) => {
61436
+ const [startHour, startMinute] = startTime.split(":").map(Number);
61437
+ const [endHour, endMinute] = endTime.split(":").map(Number);
61438
+ let startMinutes = startHour * 60 + startMinute;
61439
+ let endMinutes = endHour * 60 + endMinute;
61440
+ if (endMinutes < startMinutes) {
61441
+ endMinutes += 24 * 60;
61442
+ }
61443
+ return endMinutes - startMinutes;
61444
+ };
61445
+ var parseBreaksFromDB = (dbBreaks) => {
61446
+ if (!dbBreaks) return [];
61447
+ if (Array.isArray(dbBreaks)) {
61448
+ return dbBreaks.map((breakItem) => ({
61449
+ startTime: breakItem.start || breakItem.startTime || "00:00",
61450
+ endTime: breakItem.end || breakItem.endTime || "00:00",
61451
+ duration: calculateBreakDuration2(
61452
+ breakItem.start || breakItem.startTime || "00:00",
61453
+ breakItem.end || breakItem.endTime || "00:00"
61454
+ ),
61455
+ remarks: breakItem.remarks || breakItem.name || ""
61456
+ }));
61457
+ } else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
61458
+ return dbBreaks.breaks.map((breakItem) => ({
61459
+ startTime: breakItem.start || breakItem.startTime || "00:00",
61460
+ endTime: breakItem.end || breakItem.endTime || "00:00",
61461
+ duration: calculateBreakDuration2(
61462
+ breakItem.start || breakItem.startTime || "00:00",
61463
+ breakItem.end || breakItem.endTime || "00:00"
61464
+ ),
61465
+ remarks: breakItem.remarks || breakItem.name || ""
61466
+ }));
61467
+ } else {
61468
+ console.warn("Unexpected breaks format:", dbBreaks);
61469
+ return [];
61470
+ }
61471
+ };
60927
61472
  var getStoredLineState = (lineId) => {
60928
61473
  try {
60929
61474
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -60940,6 +61485,7 @@ var formatBreaks = (breaks) => {
60940
61485
  }))
60941
61486
  };
60942
61487
  };
61488
+ var SHIFT_HOURS_EPSILON = 1e-3;
60943
61489
  var BreakRow = React26.memo(({
60944
61490
  break: breakItem,
60945
61491
  onUpdate,
@@ -61235,16 +61781,22 @@ var ShiftsView = ({
61235
61781
  );
61236
61782
  const [loading, setLoading] = React26.useState(true);
61237
61783
  const [error, setError] = React26.useState(null);
61784
+ const [saveStatusMessages, setSaveStatusMessages] = React26.useState(
61785
+ () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
61786
+ );
61238
61787
  const showToast = React26.useCallback((type, message) => {
61239
61788
  if (onToast) {
61240
- onToast(type, message);
61241
- } else {
61242
- if (type === "success") {
61243
- sonner.toast.success(message);
61244
- } else {
61245
- sonner.toast.error(message);
61789
+ try {
61790
+ onToast(type, message);
61791
+ } catch (error2) {
61792
+ console.warn("[ShiftsView] onToast callback failed, falling back to sonner toast:", error2);
61246
61793
  }
61247
61794
  }
61795
+ if (type === "success") {
61796
+ sonner.toast.success(message);
61797
+ } else {
61798
+ sonner.toast.error(message);
61799
+ }
61248
61800
  }, [onToast]);
61249
61801
  React26.useEffect(() => {
61250
61802
  const fetchShiftConfigs = async () => {
@@ -61395,15 +61947,196 @@ var ShiftsView = ({
61395
61947
  return config;
61396
61948
  }));
61397
61949
  }, []);
61950
+ const getOperationalDateForLine = React26.useCallback((lineConfig) => {
61951
+ const sortedShifts = [...lineConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
61952
+ const dayBoundaryStart = sortedShifts[0]?.startTime || "06:00";
61953
+ return getOperationalDate(lineConfig.timezone || "UTC", /* @__PURE__ */ new Date(), dayBoundaryStart);
61954
+ }, []);
61955
+ const recalculateTargetsForShiftHourChanges = React26.useCallback(
61956
+ async ({
61957
+ lineId,
61958
+ lineConfig,
61959
+ previousShiftHours,
61960
+ updatedBy
61961
+ }) => {
61962
+ const primaryOperationalDate = getOperationalDate(lineConfig.timezone || "UTC");
61963
+ const alternateOperationalDate = getOperationalDateForLine(lineConfig);
61964
+ const candidateOperationalDates = primaryOperationalDate === alternateOperationalDate ? [primaryOperationalDate] : [primaryOperationalDate, alternateOperationalDate];
61965
+ let recalculatedCount = 0;
61966
+ const { data: lineRow, error: lineRowError } = await supabase.from("lines").select("factory_id").eq("id", lineId).maybeSingle();
61967
+ if (lineRowError) {
61968
+ throw new Error(`Failed to resolve line factory for target recalculation: ${lineRowError.message}`);
61969
+ }
61970
+ const lineFactoryId = lineRow?.factory_id || null;
61971
+ for (const shift of lineConfig.shifts || []) {
61972
+ const oldShiftHours = previousShiftHours[shift.shiftId];
61973
+ const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
61974
+ if (oldShiftHours === void 0) continue;
61975
+ if (!Number.isFinite(newShiftHours) || newShiftHours <= 0) {
61976
+ console.warn(
61977
+ `[ShiftsView] Skipping target recalculation for line ${lineId}, shift ${shift.shiftId} due to invalid new shift hours: ${newShiftHours}`
61978
+ );
61979
+ continue;
61980
+ }
61981
+ if (Math.abs(newShiftHours - oldShiftHours) <= SHIFT_HOURS_EPSILON) continue;
61982
+ let thresholdDateForShift = candidateOperationalDates[0];
61983
+ let currentThresholds = [];
61984
+ for (const candidateDate of candidateOperationalDates) {
61985
+ const { data: thresholdRows, error: thresholdsFetchError } = await supabase.from("action_thresholds").select("line_id, shift_id, action_id, workspace_id, date, pph_threshold, ideal_cycle_time, total_day_output, action_name, sku_id").eq("line_id", lineId).eq("date", candidateDate).eq("shift_id", shift.shiftId);
61986
+ if (thresholdsFetchError) {
61987
+ throw new Error(
61988
+ `Failed to fetch action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsFetchError.message}`
61989
+ );
61990
+ }
61991
+ if ((thresholdRows || []).length > 0) {
61992
+ thresholdDateForShift = candidateDate;
61993
+ currentThresholds = thresholdRows || [];
61994
+ break;
61995
+ }
61996
+ }
61997
+ if (currentThresholds.length === 0) {
61998
+ continue;
61999
+ }
62000
+ const actionIds = Array.from(
62001
+ new Set(currentThresholds.map((threshold) => threshold.action_id).filter(Boolean))
62002
+ );
62003
+ const actionNameById = /* @__PURE__ */ new Map();
62004
+ if (actionIds.length > 0) {
62005
+ const { data: actionRows, error: actionsError } = await supabase.from("actions").select("id, action_name").in("id", actionIds);
62006
+ if (actionsError) {
62007
+ console.warn(
62008
+ `[ShiftsView] Failed to resolve action names for line ${lineId}, shift ${shift.shiftId}: ${actionsError.message}`
62009
+ );
62010
+ } else {
62011
+ (actionRows || []).forEach((actionRow) => {
62012
+ if (actionRow.id && actionRow.action_name) {
62013
+ actionNameById.set(actionRow.id, actionRow.action_name);
62014
+ }
62015
+ });
62016
+ }
62017
+ }
62018
+ const expandedHours = newShiftHours > oldShiftHours;
62019
+ const recalculatedThresholds = currentThresholds.map((threshold) => {
62020
+ const existingPPH = Number(threshold.pph_threshold) || 0;
62021
+ const existingDayOutput = Number(threshold.total_day_output) || 0;
62022
+ const baselineDayOutput = existingDayOutput > 0 ? existingDayOutput : Math.round(existingPPH * Math.max(oldShiftHours, 0));
62023
+ let nextPPH = existingPPH;
62024
+ let nextDayOutput = existingDayOutput;
62025
+ if (expandedHours) {
62026
+ const pphToKeep = existingPPH > 0 ? existingPPH : oldShiftHours > 0 ? Math.round(baselineDayOutput / oldShiftHours) : 0;
62027
+ nextPPH = pphToKeep;
62028
+ nextDayOutput = Math.round(pphToKeep * newShiftHours);
62029
+ } else {
62030
+ const dayOutputToKeep = baselineDayOutput;
62031
+ nextDayOutput = dayOutputToKeep;
62032
+ nextPPH = newShiftHours > 0 ? Math.round(dayOutputToKeep / newShiftHours) : 0;
62033
+ }
62034
+ const resolvedActionName = (typeof threshold.action_name === "string" && threshold.action_name.trim().length > 0 ? threshold.action_name : actionNameById.get(threshold.action_id)) || ACTION_NAMES.ASSEMBLY;
62035
+ return {
62036
+ line_id: threshold.line_id || lineId,
62037
+ shift_id: shift.shiftId,
62038
+ action_id: threshold.action_id,
62039
+ workspace_id: threshold.workspace_id,
62040
+ date: threshold.date || thresholdDateForShift,
62041
+ pph_threshold: Math.max(0, Math.round(Number.isFinite(nextPPH) ? nextPPH : 0)),
62042
+ ideal_cycle_time: Number(threshold.ideal_cycle_time) || 0,
62043
+ total_day_output: Math.max(0, Math.round(Number.isFinite(nextDayOutput) ? nextDayOutput : 0)),
62044
+ action_name: resolvedActionName,
62045
+ updated_by: updatedBy,
62046
+ ...threshold.sku_id ? { sku_id: threshold.sku_id } : {}
62047
+ };
62048
+ });
62049
+ const { error: thresholdsUpsertError } = await supabase.from("action_thresholds").upsert(recalculatedThresholds);
62050
+ if (thresholdsUpsertError) {
62051
+ throw new Error(
62052
+ `Failed to update action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsUpsertError.message}`
62053
+ );
62054
+ }
62055
+ const packagingActionIds = new Set(
62056
+ Array.from(actionNameById.entries()).filter(([, actionName]) => actionName.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase()).map(([actionId]) => actionId)
62057
+ );
62058
+ const packagingThresholds = recalculatedThresholds.filter((threshold) => {
62059
+ if (packagingActionIds.has(threshold.action_id)) return true;
62060
+ return typeof threshold.action_name === "string" && threshold.action_name.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase();
62061
+ });
62062
+ const thresholdDayOutput = packagingThresholds.reduce(
62063
+ (sum, threshold) => sum + (Number(threshold.total_day_output) || 0),
62064
+ 0
62065
+ );
62066
+ const thresholdPPH = packagingThresholds.reduce(
62067
+ (sum, threshold) => sum + (Number(threshold.pph_threshold) || 0),
62068
+ 0
62069
+ );
62070
+ const { data: existingLineThreshold, error: existingLineThresholdError } = await supabase.from("line_thresholds").select("factory_id, product_code, sku_id").eq("line_id", lineId).eq("date", thresholdDateForShift).eq("shift_id", shift.shiftId).maybeSingle();
62071
+ if (existingLineThresholdError) {
62072
+ console.warn(
62073
+ `[ShiftsView] Failed to read existing line threshold for line ${lineId}, shift ${shift.shiftId}: ${existingLineThresholdError.message}`
62074
+ );
62075
+ }
62076
+ const factoryId = existingLineThreshold?.factory_id || lineFactoryId;
62077
+ if (factoryId) {
62078
+ const lineThresholdPayload = {
62079
+ factory_id: factoryId,
62080
+ line_id: lineId,
62081
+ date: thresholdDateForShift,
62082
+ shift_id: shift.shiftId,
62083
+ product_code: existingLineThreshold?.product_code || "",
62084
+ threshold_day_output: thresholdDayOutput,
62085
+ threshold_pph: thresholdPPH
62086
+ };
62087
+ if (existingLineThreshold?.sku_id) {
62088
+ lineThresholdPayload.sku_id = existingLineThreshold.sku_id;
62089
+ }
62090
+ const { error: lineThresholdUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdPayload, { onConflict: "factory_id,line_id,date,shift_id" });
62091
+ if (lineThresholdUpsertError) {
62092
+ throw new Error(
62093
+ `Failed to update line thresholds for line ${lineId}, shift ${shift.shiftId}: ${lineThresholdUpsertError.message}`
62094
+ );
62095
+ }
62096
+ } else {
62097
+ console.warn(
62098
+ `[ShiftsView] Missing factory_id while updating line thresholds for line ${lineId}, shift ${shift.shiftId}`
62099
+ );
62100
+ }
62101
+ recalculatedCount += recalculatedThresholds.length;
62102
+ }
62103
+ return recalculatedCount;
62104
+ },
62105
+ [getOperationalDateForLine, supabase]
62106
+ );
61398
62107
  const handleSaveShifts = React26.useCallback(async (lineId) => {
62108
+ const userId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
61399
62109
  setLineConfigs((prev) => prev.map(
61400
62110
  (config) => config.id === lineId ? { ...config, isSaving: true, saveSuccess: false } : config
61401
62111
  ));
62112
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61402
62113
  try {
61403
62114
  const lineConfig = lineConfigs.find((config) => config.id === lineId);
61404
62115
  if (!lineConfig) {
61405
62116
  throw new Error("Line configuration not found");
61406
62117
  }
62118
+ const { data: existingRows, error: existingRowsError } = await supabase.from("line_operating_hours").select("shift_id, start_time, end_time, breaks").eq("line_id", lineId);
62119
+ if (existingRowsError) {
62120
+ throw new Error(`Failed to read existing shift timings: ${existingRowsError.message}`);
62121
+ }
62122
+ const previousShiftHours = (existingRows || []).reduce(
62123
+ (acc, row) => {
62124
+ acc[row.shift_id] = calculateShiftHours(
62125
+ row.start_time,
62126
+ row.end_time,
62127
+ parseBreaksFromDB(row.breaks)
62128
+ );
62129
+ return acc;
62130
+ },
62131
+ {}
62132
+ );
62133
+ const changedShiftCount = (lineConfig.shifts || []).reduce((count, shift) => {
62134
+ const oldShiftHours = previousShiftHours[shift.shiftId];
62135
+ if (oldShiftHours === void 0) return count;
62136
+ const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
62137
+ if (!Number.isFinite(newShiftHours)) return count;
62138
+ return Math.abs(newShiftHours - oldShiftHours) > SHIFT_HOURS_EPSILON ? count + 1 : count;
62139
+ }, 0);
61407
62140
  const allSavedRows = [];
61408
62141
  for (const shift of lineConfig.shifts || []) {
61409
62142
  const shiftData = {
@@ -61425,23 +62158,65 @@ var ShiftsView = ({
61425
62158
  if (allSavedRows.length > 0) {
61426
62159
  shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
61427
62160
  }
62161
+ let recalculatedTargetsCount = 0;
62162
+ try {
62163
+ recalculatedTargetsCount = await recalculateTargetsForShiftHourChanges({
62164
+ lineId,
62165
+ lineConfig,
62166
+ previousShiftHours,
62167
+ updatedBy: userId
62168
+ });
62169
+ } catch (recalcError) {
62170
+ console.error("[ShiftsView] Shift timings were saved but target recalculation failed:", recalcError);
62171
+ showToast("error", "Shift timings saved, but target recalculation failed. Please review targets.");
62172
+ setSaveStatusMessages((prev) => ({
62173
+ ...prev,
62174
+ [lineId]: {
62175
+ message: "Shift timings saved, but target recalculation failed. Please review targets.",
62176
+ tone: "error"
62177
+ }
62178
+ }));
62179
+ }
61428
62180
  setLineConfigs((prev) => prev.map(
61429
62181
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
61430
62182
  ));
61431
- showToast("success", "Shift configurations saved successfully");
62183
+ let successMessage = "Shift configurations saved successfully.";
62184
+ if (changedShiftCount > 0 && recalculatedTargetsCount > 0) {
62185
+ successMessage = `Shift configurations saved. Targets updated successfully for ${recalculatedTargetsCount} workspace${recalculatedTargetsCount === 1 ? "" : "s"}.`;
62186
+ } else if (changedShiftCount > 0) {
62187
+ successMessage = "Shift configurations saved. Target recalculation completed successfully.";
62188
+ }
62189
+ showToast("success", successMessage);
62190
+ setSaveStatusMessages((prev) => ({
62191
+ ...prev,
62192
+ [lineId]: {
62193
+ message: successMessage,
62194
+ tone: "success"
62195
+ }
62196
+ }));
61432
62197
  setTimeout(() => {
61433
62198
  setLineConfigs((prev) => prev.map(
61434
62199
  (config) => config.id === lineId ? { ...config, saveSuccess: false } : config
61435
62200
  ));
61436
62201
  }, 3e3);
62202
+ setTimeout(() => {
62203
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
62204
+ }, 7e3);
61437
62205
  } catch (error2) {
61438
62206
  console.error("Error saving shift configurations:", error2);
61439
62207
  showToast("error", "Failed to save shift configurations");
62208
+ setSaveStatusMessages((prev) => ({
62209
+ ...prev,
62210
+ [lineId]: {
62211
+ message: "Failed to save shift configurations",
62212
+ tone: "error"
62213
+ }
62214
+ }));
61440
62215
  setLineConfigs((prev) => prev.map(
61441
62216
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: false } : config
61442
62217
  ));
61443
62218
  }
61444
- }, [lineConfigs, supabase, showToast]);
62219
+ }, [lineConfigs, recalculateTargetsForShiftHourChanges, supabase, showToast]);
61445
62220
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
61446
62221
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-4 md:px-6 lg:px-8 py-3 sm:py-4", children: [
61447
62222
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
@@ -61496,7 +62271,14 @@ var ShiftsView = ({
61496
62271
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-4 w-full sm:w-auto", children: [
61497
62272
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
61498
62273
  config.isSaving && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-blue-600", children: "Saving..." }),
61499
- config.saveSuccess && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" })
62274
+ config.saveSuccess && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" }),
62275
+ saveStatusMessages[config.id]?.message && /* @__PURE__ */ jsxRuntime.jsx(
62276
+ "span",
62277
+ {
62278
+ className: `text-xs sm:text-sm font-medium ${saveStatusMessages[config.id]?.tone === "error" ? "text-red-600" : saveStatusMessages[config.id]?.tone === "info" ? "text-blue-600" : "text-green-600"}`,
62279
+ children: saveStatusMessages[config.id]?.message
62280
+ }
62281
+ )
61500
62282
  ] }),
61501
62283
  /* @__PURE__ */ jsxRuntime.jsxs(
61502
62284
  "button",
@@ -61553,20 +62335,6 @@ var ShiftsView = ({
61553
62335
  var AuthenticatedShiftsView = withAuth(React26__namespace.default.memo(ShiftsView));
61554
62336
  var ShiftsView_default = ShiftsView;
61555
62337
 
61556
- // src/lib/constants/actions.ts
61557
- var ACTION_NAMES = {
61558
- /** Assembly operations */
61559
- ASSEMBLY: "Assembly",
61560
- /** Packaging operations */
61561
- PACKAGING: "Packaging",
61562
- /** Inspection operations */
61563
- INSPECTION: "Inspection",
61564
- /** Testing operations */
61565
- TESTING: "Testing",
61566
- /** Quality control operations */
61567
- QUALITY_CONTROL: "Quality Control"
61568
- };
61569
-
61570
62338
  // src/views/TargetsView.utils.ts
61571
62339
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
61572
62340
  if (cycleTime === "" || cycleTime === 0) return "";
@@ -65983,9 +66751,38 @@ var TeamManagementView = ({
65983
66751
  optifye: 0
65984
66752
  });
65985
66753
  const [isAddUserDialogOpen, setIsAddUserDialogOpen] = React26.useState(false);
65986
- const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
66754
+ const normalizeIds = React26.useCallback((value) => {
66755
+ if (Array.isArray(value)) {
66756
+ return value.filter((id3) => typeof id3 === "string" && id3.length > 0);
66757
+ }
66758
+ if (typeof value === "string" && value.length > 0) {
66759
+ return [value];
66760
+ }
66761
+ return [];
66762
+ }, []);
66763
+ const plantHeadFactoryIds = React26.useMemo(() => {
66764
+ if (user?.role_level !== "plant_head") return [];
66765
+ const scopedFactoryIds = normalizeIds(user?.access_scope?.factory_ids);
66766
+ if (scopedFactoryIds.length > 0) {
66767
+ return Array.from(new Set(scopedFactoryIds));
66768
+ }
66769
+ const propertyFactoryIds = normalizeIds(
66770
+ user?.properties?.factory_ids ?? user?.properties?.factory_id
66771
+ );
66772
+ if (propertyFactoryIds.length > 0) {
66773
+ return Array.from(new Set(propertyFactoryIds));
66774
+ }
66775
+ return entityConfig?.factoryId ? [entityConfig.factoryId] : [];
66776
+ }, [user, entityConfig?.factoryId, normalizeIds]);
66777
+ const notifyScopeRefresh = React26.useCallback(() => {
66778
+ if (typeof window !== "undefined") {
66779
+ window.dispatchEvent(new Event("rbac:refresh-scope"));
66780
+ }
66781
+ }, []);
66782
+ const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "plant_head" || user?.role_level === "optifye";
65987
66783
  const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
65988
- const companyIdForUsage = entityConfig?.companyId || user?.properties?.company_id;
66784
+ const pageCompanyId = entityConfig?.companyId || user?.properties?.company_id;
66785
+ const companyIdForUsage = pageCompanyId;
65989
66786
  const usageDateRange = React26.useMemo(() => {
65990
66787
  const today = /* @__PURE__ */ new Date();
65991
66788
  const dayOfWeek = today.getDay();
@@ -66016,8 +66813,8 @@ var TeamManagementView = ({
66016
66813
  return acc;
66017
66814
  }, {});
66018
66815
  }, [usageData, usageDateRange.daysElapsed]);
66019
- const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
66020
- 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";
66816
+ const pageTitle = "Team Management";
66817
+ 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";
66021
66818
  const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "it", "plant_head"].includes(user.role_level) : false;
66022
66819
  const loadData = React26.useCallback(async () => {
66023
66820
  if (!supabase) {
@@ -66025,20 +66822,16 @@ var TeamManagementView = ({
66025
66822
  setIsLoading(false);
66026
66823
  return;
66027
66824
  }
66028
- const isOptifyeUser = user?.role_level === "optifye";
66029
- if (!isOptifyeUser) {
66030
- const companyId2 = entityConfig?.companyId || user?.properties?.company_id;
66031
- if (!companyId2) {
66032
- setError("Company not found. Please contact your administrator.");
66033
- setIsLoading(false);
66034
- return;
66035
- }
66825
+ const companyId = pageCompanyId;
66826
+ if (!companyId) {
66827
+ setError("Company not found. Please contact your administrator.");
66828
+ setIsLoading(false);
66829
+ return;
66036
66830
  }
66037
- const companyId = entityConfig?.companyId || user?.properties?.company_id;
66038
66831
  console.log("[TeamManagementView] Loading data:", {
66039
66832
  role: user?.role_level,
66040
- isOptifye: isOptifyeUser,
66041
- companyId: isOptifyeUser ? "ALL" : companyId
66833
+ isOptifye: user?.role_level === "optifye",
66834
+ companyId
66042
66835
  });
66043
66836
  setIsLoading(true);
66044
66837
  setError(void 0);
@@ -66053,23 +66846,7 @@ var TeamManagementView = ({
66053
66846
  if (!backendUrl) {
66054
66847
  throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
66055
66848
  }
66056
- if (isOptifyeUser) {
66057
- const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").order("factory_name");
66058
- const linesResponse = await fetch(`${backendUrl}/api/lines`, {
66059
- headers: {
66060
- "Authorization": `Bearer ${token}`,
66061
- "Content-Type": "application/json"
66062
- }
66063
- });
66064
- if (!linesResponse.ok) {
66065
- throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
66066
- }
66067
- const linesData = await linesResponse.json();
66068
- const lines = linesData.lines || [];
66069
- setAvailableFactories(factories || []);
66070
- setAvailableLines(lines);
66071
- console.log("[TeamManagementView] Optifye - Loaded factories:", factories?.length, "lines:", lines?.length, "Sample lines:", lines?.slice(0, 3));
66072
- } else if ((user?.role_level === "owner" || user?.role_level === "it") && companyId) {
66849
+ if ((user?.role_level === "optifye" || user?.role_level === "owner" || user?.role_level === "it") && companyId) {
66073
66850
  const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").eq("company_id", companyId).order("factory_name");
66074
66851
  const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
66075
66852
  headers: {
@@ -66084,9 +66861,8 @@ var TeamManagementView = ({
66084
66861
  const lines = linesData.lines || [];
66085
66862
  setAvailableFactories(factories || []);
66086
66863
  setAvailableLines(lines);
66087
- console.log("[TeamManagementView] Owner/IT - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
66864
+ console.log("[TeamManagementView] Company-scoped team view - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
66088
66865
  } else if (user?.role_level === "plant_head") {
66089
- const plantHeadFactoryIds = user?.properties?.factory_ids || [];
66090
66866
  if (plantHeadFactoryIds.length > 0) {
66091
66867
  if (companyId) {
66092
66868
  const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
@@ -66139,35 +66915,46 @@ var TeamManagementView = ({
66139
66915
  setAvailableFactories([]);
66140
66916
  console.log("[TeamManagementView] Fallback - Company:", companyId, "Loaded lines:", lines?.length);
66141
66917
  }
66142
- if (isOptifyeUser) {
66143
- const [usersData, userStats] = await Promise.all([
66144
- userManagementService.getAllUsers(),
66145
- userManagementService.getAllUserStats()
66146
- ]);
66147
- setUsers(usersData);
66148
- setStats({
66149
- totalUsers: userStats.total,
66150
- owners: userStats.owners,
66151
- it: userStats.it,
66152
- plantHeads: userStats.plant_heads,
66153
- supervisors: userStats.supervisors,
66154
- optifye: userStats.optifye
66155
- });
66156
- } else {
66157
- const [usersData, userStats] = await Promise.all([
66158
- user?.role_level === "plant_head" && entityConfig?.factoryId ? userManagementService.getFactoryUsers(entityConfig.factoryId) : userManagementService.getCompanyUsers(companyId),
66159
- userManagementService.getUserStats(companyId)
66160
- ]);
66161
- setUsers(usersData);
66162
- setStats({
66163
- totalUsers: userStats.total,
66164
- owners: userStats.owners,
66165
- it: userStats.it,
66166
- plantHeads: userStats.plant_heads,
66167
- supervisors: userStats.supervisors,
66168
- optifye: 0
66169
- });
66170
- }
66918
+ const usersPromise = user?.role_level === "plant_head" ? (async () => {
66919
+ if (plantHeadFactoryIds.length === 0) {
66920
+ return [];
66921
+ }
66922
+ const results = await Promise.allSettled(
66923
+ plantHeadFactoryIds.map((factoryId) => userManagementService.getFactoryUsers(factoryId))
66924
+ );
66925
+ const successful = results.filter(
66926
+ (result) => result.status === "fulfilled"
66927
+ ).flatMap((result) => result.value || []);
66928
+ if (successful.length > 0) {
66929
+ const byUserId = /* @__PURE__ */ new Map();
66930
+ successful.forEach((u) => {
66931
+ if (u?.user_id) {
66932
+ byUserId.set(u.user_id, u);
66933
+ }
66934
+ });
66935
+ return Array.from(byUserId.values());
66936
+ }
66937
+ const firstRejected = results.find(
66938
+ (result) => result.status === "rejected"
66939
+ );
66940
+ if (firstRejected) {
66941
+ throw firstRejected.reason;
66942
+ }
66943
+ return [];
66944
+ })() : userManagementService.getCompanyUsers(companyId);
66945
+ const [usersData, userStats] = await Promise.all([
66946
+ usersPromise,
66947
+ userManagementService.getUserStats(companyId)
66948
+ ]);
66949
+ setUsers(usersData);
66950
+ setStats({
66951
+ totalUsers: userStats.total,
66952
+ owners: userStats.owners,
66953
+ it: userStats.it,
66954
+ plantHeads: userStats.plant_heads,
66955
+ supervisors: userStats.supervisors,
66956
+ optifye: 0
66957
+ });
66171
66958
  } catch (err) {
66172
66959
  console.error("Error loading team management data:", err);
66173
66960
  setError(err instanceof Error ? err.message : "Failed to load data");
@@ -66175,16 +66962,16 @@ var TeamManagementView = ({
66175
66962
  } finally {
66176
66963
  setIsLoading(false);
66177
66964
  }
66178
- }, [supabase, user, entityConfig]);
66965
+ }, [supabase, user, pageCompanyId, entityConfig?.factoryId, plantHeadFactoryIds]);
66179
66966
  React26.useEffect(() => {
66180
- const companyId = entityConfig?.companyId || user?.properties?.company_id;
66181
- const canLoad = hasAccess && user && (user.role_level === "optifye" || !!companyId);
66967
+ const companyId = pageCompanyId;
66968
+ const canLoad = hasAccess && user && !!companyId;
66182
66969
  if (canLoad) {
66183
66970
  loadData();
66184
66971
  } else if (!user) {
66185
66972
  setIsLoading(true);
66186
66973
  }
66187
- }, [hasAccess, loadData, user, entityConfig?.companyId]);
66974
+ }, [hasAccess, loadData, user, pageCompanyId]);
66188
66975
  const handleUserAdded = React26.useCallback(() => {
66189
66976
  loadData();
66190
66977
  }, [loadData]);
@@ -66198,12 +66985,13 @@ var TeamManagementView = ({
66198
66985
  updated_by: user.id
66199
66986
  });
66200
66987
  sonner.toast.success("User role updated successfully");
66988
+ notifyScopeRefresh();
66201
66989
  loadData();
66202
66990
  } catch (err) {
66203
66991
  console.error("Error updating user role:", err);
66204
66992
  sonner.toast.error("Failed to update user role");
66205
66993
  }
66206
- }, [supabase, user, loadData]);
66994
+ }, [supabase, user, loadData, notifyScopeRefresh]);
66207
66995
  const handleRemoveUser = React26.useCallback(async (userId) => {
66208
66996
  if (!supabase || !user) return;
66209
66997
  try {
@@ -66226,12 +67014,13 @@ var TeamManagementView = ({
66226
67014
  assigned_by: user.id
66227
67015
  });
66228
67016
  sonner.toast.success("Line assignments updated successfully");
67017
+ notifyScopeRefresh();
66229
67018
  loadData();
66230
67019
  } catch (err) {
66231
67020
  console.error("Error updating line assignments:", err);
66232
67021
  sonner.toast.error("Failed to update line assignments");
66233
67022
  }
66234
- }, [supabase, user, loadData]);
67023
+ }, [supabase, user, loadData, notifyScopeRefresh]);
66235
67024
  const handleFactoryAssignmentUpdate = React26.useCallback(async (userId, factoryIds) => {
66236
67025
  if (!supabase || !user) return;
66237
67026
  try {
@@ -66242,12 +67031,13 @@ var TeamManagementView = ({
66242
67031
  assigned_by: user.id
66243
67032
  });
66244
67033
  sonner.toast.success("Factory assignments updated successfully");
67034
+ notifyScopeRefresh();
66245
67035
  loadData();
66246
67036
  } catch (err) {
66247
67037
  console.error("Error updating factory assignments:", err);
66248
67038
  sonner.toast.error("Failed to update factory assignments");
66249
67039
  }
66250
- }, [supabase, user, loadData]);
67040
+ }, [supabase, user, loadData, notifyScopeRefresh]);
66251
67041
  const handleProfileUpdate = React26.useCallback(async (userId, firstName, lastName, profilePhotoUrl) => {
66252
67042
  if (!supabase || !user) return;
66253
67043
  try {