@optifye/dashboard-core 6.10.47 → 6.10.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +4 -0
- package/dist/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +619 -252
- package/dist/index.mjs +619 -252
- package/package.json +1 -1
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
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
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
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:
|
|
16440
|
+
accessibleLineIds: all,
|
|
16441
|
+
defaultLineId: all[0]
|
|
16308
16442
|
};
|
|
16309
16443
|
}
|
|
16310
|
-
if (
|
|
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:
|
|
16313
|
-
defaultLineId:
|
|
16448
|
+
accessibleLineIds: scoped,
|
|
16449
|
+
defaultLineId: scoped[0]
|
|
16314
16450
|
};
|
|
16315
16451
|
}
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
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,17 +28506,76 @@ 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
|
|
28338
|
-
|
|
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
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen
|
|
28341
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }),
|
|
28342
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600", children: "Loading..." })
|
|
28343
|
-
] }) });
|
|
28556
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-screen items-center justify-center bg-slate-50", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading Dashboard..." }) });
|
|
28344
28557
|
}
|
|
28345
28558
|
if (!user) {
|
|
28346
28559
|
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
28560
|
}
|
|
28561
|
+
const hasAccess = canAccessPath(pathToCheck);
|
|
28562
|
+
if (!hasAccess) {
|
|
28563
|
+
if (UnauthorizedComponent) {
|
|
28564
|
+
return /* @__PURE__ */ jsxRuntime.jsx(UnauthorizedComponent, {});
|
|
28565
|
+
}
|
|
28566
|
+
if (typeof window !== "undefined") {
|
|
28567
|
+
router$1.replace(redirectTo);
|
|
28568
|
+
}
|
|
28569
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
28570
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Access Denied" }),
|
|
28571
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-4", children: "You don't have permission to access this page." }),
|
|
28572
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500", children: [
|
|
28573
|
+
"Your role: ",
|
|
28574
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: role || "Unknown" })
|
|
28575
|
+
] }),
|
|
28576
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Redirecting to home page..." })
|
|
28577
|
+
] }) });
|
|
28578
|
+
}
|
|
28348
28579
|
return /* @__PURE__ */ jsxRuntime.jsx(WrappedComponent2, { ...props });
|
|
28349
28580
|
};
|
|
28350
28581
|
WithAccessControlComponent.displayName = `withAccessControl(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
|
|
@@ -48658,9 +48889,29 @@ var SideNavBar = React26.memo(({
|
|
|
48658
48889
|
}) => {
|
|
48659
48890
|
const router$1 = router.useRouter();
|
|
48660
48891
|
const { navigate } = useNavigation();
|
|
48661
|
-
const { signOut } = useAuth();
|
|
48892
|
+
const { signOut, user } = useAuth();
|
|
48662
48893
|
const entityConfig = useEntityConfig();
|
|
48663
48894
|
const dashboardConfig = useDashboardConfig();
|
|
48895
|
+
const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
|
|
48896
|
+
const role = user?.role_level;
|
|
48897
|
+
const roleAccessMap = {
|
|
48898
|
+
optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48899
|
+
owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48900
|
+
it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48901
|
+
plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48902
|
+
supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/tickets", "/improvement-center"]
|
|
48903
|
+
};
|
|
48904
|
+
const getBasePath = React26.useCallback((path) => {
|
|
48905
|
+
const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
|
|
48906
|
+
return firstSegment ? `/${firstSegment}` : "/";
|
|
48907
|
+
}, []);
|
|
48908
|
+
const canAccessPath = React26.useCallback((path) => {
|
|
48909
|
+
if (!role) return false;
|
|
48910
|
+
if (isSuperAdmin) return true;
|
|
48911
|
+
const basePath = getBasePath(path);
|
|
48912
|
+
const allowedPaths = roleAccessMap[role] || [];
|
|
48913
|
+
return allowedPaths.includes(basePath);
|
|
48914
|
+
}, [role, isSuperAdmin, getBasePath]);
|
|
48664
48915
|
const lineId = entityConfig.defaultLineId || LINE_1_UUID;
|
|
48665
48916
|
const skuEnabled = dashboardConfig?.skuConfig?.enabled || false;
|
|
48666
48917
|
dashboardConfig?.supervisorConfig?.enabled || false;
|
|
@@ -48826,7 +49077,7 @@ var SideNavBar = React26.memo(({
|
|
|
48826
49077
|
const settingsTriggerRef = React26.useRef(null);
|
|
48827
49078
|
const settingsItems = React26.useMemo(() => {
|
|
48828
49079
|
const items = [
|
|
48829
|
-
{
|
|
49080
|
+
...canAccessPath("/targets") ? [{
|
|
48830
49081
|
key: "targets",
|
|
48831
49082
|
label: "Targets",
|
|
48832
49083
|
icon: outline.AdjustmentsHorizontalIcon,
|
|
@@ -48835,8 +49086,8 @@ var SideNavBar = React26.memo(({
|
|
|
48835
49086
|
setIsSettingsOpen(false);
|
|
48836
49087
|
},
|
|
48837
49088
|
isActive: pathname === "/targets" || pathname.startsWith("/targets/")
|
|
48838
|
-
},
|
|
48839
|
-
{
|
|
49089
|
+
}] : [],
|
|
49090
|
+
...canAccessPath("/shifts") ? [{
|
|
48840
49091
|
key: "shifts",
|
|
48841
49092
|
label: "Shifts",
|
|
48842
49093
|
icon: outline.ClockIcon,
|
|
@@ -48845,8 +49096,8 @@ var SideNavBar = React26.memo(({
|
|
|
48845
49096
|
setIsSettingsOpen(false);
|
|
48846
49097
|
},
|
|
48847
49098
|
isActive: pathname === "/shifts" || pathname.startsWith("/shifts/")
|
|
48848
|
-
},
|
|
48849
|
-
{
|
|
49099
|
+
}] : [],
|
|
49100
|
+
...canAccessPath("/team-management") ? [{
|
|
48850
49101
|
key: "teams",
|
|
48851
49102
|
label: "Teams",
|
|
48852
49103
|
icon: outline.UsersIcon,
|
|
@@ -48855,8 +49106,8 @@ var SideNavBar = React26.memo(({
|
|
|
48855
49106
|
setIsSettingsOpen(false);
|
|
48856
49107
|
},
|
|
48857
49108
|
isActive: pathname === "/team-management" || pathname.startsWith("/team-management/")
|
|
48858
|
-
},
|
|
48859
|
-
{
|
|
49109
|
+
}] : [],
|
|
49110
|
+
...canAccessPath("/profile") ? [{
|
|
48860
49111
|
key: "profile",
|
|
48861
49112
|
label: "Profile",
|
|
48862
49113
|
icon: outline.UserCircleIcon,
|
|
@@ -48865,9 +49116,9 @@ var SideNavBar = React26.memo(({
|
|
|
48865
49116
|
setIsSettingsOpen(false);
|
|
48866
49117
|
},
|
|
48867
49118
|
isActive: pathname === "/profile" || pathname.startsWith("/profile/")
|
|
48868
|
-
}
|
|
49119
|
+
}] : []
|
|
48869
49120
|
];
|
|
48870
|
-
if (ticketsEnabled) {
|
|
49121
|
+
if (ticketsEnabled && canAccessPath("/tickets")) {
|
|
48871
49122
|
items.push({
|
|
48872
49123
|
key: "tickets",
|
|
48873
49124
|
label: "Tickets",
|
|
@@ -48879,18 +49130,20 @@ var SideNavBar = React26.memo(({
|
|
|
48879
49130
|
isActive: pathname === "/tickets" || pathname.startsWith("/tickets/")
|
|
48880
49131
|
});
|
|
48881
49132
|
}
|
|
48882
|
-
|
|
48883
|
-
|
|
48884
|
-
|
|
48885
|
-
|
|
48886
|
-
|
|
48887
|
-
|
|
48888
|
-
|
|
48889
|
-
|
|
48890
|
-
|
|
48891
|
-
|
|
49133
|
+
if (canAccessPath("/help")) {
|
|
49134
|
+
items.push({
|
|
49135
|
+
key: "help",
|
|
49136
|
+
label: "Help",
|
|
49137
|
+
icon: outline.QuestionMarkCircleIcon,
|
|
49138
|
+
onClick: () => {
|
|
49139
|
+
handleHelpClick();
|
|
49140
|
+
setIsSettingsOpen(false);
|
|
49141
|
+
},
|
|
49142
|
+
isActive: pathname === "/help" || pathname.startsWith("/help/")
|
|
49143
|
+
});
|
|
49144
|
+
}
|
|
48892
49145
|
return items;
|
|
48893
|
-
}, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled]);
|
|
49146
|
+
}, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled, canAccessPath]);
|
|
48894
49147
|
const handleLogout = React26.useCallback(async () => {
|
|
48895
49148
|
setIsSettingsOpen(false);
|
|
48896
49149
|
try {
|
|
@@ -48928,7 +49181,7 @@ var SideNavBar = React26.memo(({
|
|
|
48928
49181
|
}
|
|
48929
49182
|
) }),
|
|
48930
49183
|
/* @__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(
|
|
49184
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6", children: canAccessPath("/") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48932
49185
|
"button",
|
|
48933
49186
|
{
|
|
48934
49187
|
onClick: handleHomeClick,
|
|
@@ -48944,7 +49197,7 @@ var SideNavBar = React26.memo(({
|
|
|
48944
49197
|
}
|
|
48945
49198
|
) }),
|
|
48946
49199
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
48947
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49200
|
+
canAccessPath("/leaderboard") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48948
49201
|
"button",
|
|
48949
49202
|
{
|
|
48950
49203
|
onClick: handleLeaderboardClick,
|
|
@@ -48959,7 +49212,7 @@ var SideNavBar = React26.memo(({
|
|
|
48959
49212
|
]
|
|
48960
49213
|
}
|
|
48961
49214
|
),
|
|
48962
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49215
|
+
canAccessPath("/kpis") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48963
49216
|
"button",
|
|
48964
49217
|
{
|
|
48965
49218
|
onClick: handleKPIsClick,
|
|
@@ -48974,7 +49227,7 @@ var SideNavBar = React26.memo(({
|
|
|
48974
49227
|
]
|
|
48975
49228
|
}
|
|
48976
49229
|
),
|
|
48977
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49230
|
+
canAccessPath("/improvement-center") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48978
49231
|
"button",
|
|
48979
49232
|
{
|
|
48980
49233
|
onClick: handleImprovementClick,
|
|
@@ -48990,7 +49243,7 @@ var SideNavBar = React26.memo(({
|
|
|
48990
49243
|
}
|
|
48991
49244
|
),
|
|
48992
49245
|
showSupervisorManagement,
|
|
48993
|
-
skuEnabled &&
|
|
49246
|
+
skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48994
49247
|
"button",
|
|
48995
49248
|
{
|
|
48996
49249
|
onClick: handleSKUsClick,
|
|
@@ -49005,7 +49258,7 @@ var SideNavBar = React26.memo(({
|
|
|
49005
49258
|
]
|
|
49006
49259
|
}
|
|
49007
49260
|
),
|
|
49008
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49261
|
+
canAccessPath("/health") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49009
49262
|
"button",
|
|
49010
49263
|
{
|
|
49011
49264
|
onClick: handleHealthClick,
|
|
@@ -49022,7 +49275,7 @@ var SideNavBar = React26.memo(({
|
|
|
49022
49275
|
)
|
|
49023
49276
|
] })
|
|
49024
49277
|
] }),
|
|
49025
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49278
|
+
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
49279
|
"button",
|
|
49027
49280
|
{
|
|
49028
49281
|
ref: settingsTriggerRef,
|
|
@@ -49060,7 +49313,7 @@ var SideNavBar = React26.memo(({
|
|
|
49060
49313
|
};
|
|
49061
49314
|
};
|
|
49062
49315
|
return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "px-5 py-6", children: [
|
|
49063
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49316
|
+
canAccessPath("/") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49064
49317
|
"button",
|
|
49065
49318
|
{
|
|
49066
49319
|
onClick: handleMobileNavClick(handleHomeClick),
|
|
@@ -49073,7 +49326,7 @@ var SideNavBar = React26.memo(({
|
|
|
49073
49326
|
}
|
|
49074
49327
|
),
|
|
49075
49328
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 space-y-2", children: [
|
|
49076
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49329
|
+
canAccessPath("/leaderboard") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49077
49330
|
"button",
|
|
49078
49331
|
{
|
|
49079
49332
|
onClick: handleMobileNavClick(handleLeaderboardClick),
|
|
@@ -49085,7 +49338,7 @@ var SideNavBar = React26.memo(({
|
|
|
49085
49338
|
]
|
|
49086
49339
|
}
|
|
49087
49340
|
),
|
|
49088
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49341
|
+
canAccessPath("/kpis") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49089
49342
|
"button",
|
|
49090
49343
|
{
|
|
49091
49344
|
onClick: handleMobileNavClick(handleKPIsClick),
|
|
@@ -49097,7 +49350,7 @@ var SideNavBar = React26.memo(({
|
|
|
49097
49350
|
]
|
|
49098
49351
|
}
|
|
49099
49352
|
),
|
|
49100
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49353
|
+
canAccessPath("/improvement-center") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49101
49354
|
"button",
|
|
49102
49355
|
{
|
|
49103
49356
|
onClick: handleMobileNavClick(handleImprovementClick),
|
|
@@ -49110,7 +49363,7 @@ var SideNavBar = React26.memo(({
|
|
|
49110
49363
|
}
|
|
49111
49364
|
),
|
|
49112
49365
|
showSupervisorManagement,
|
|
49113
|
-
skuEnabled &&
|
|
49366
|
+
skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49114
49367
|
"button",
|
|
49115
49368
|
{
|
|
49116
49369
|
onClick: handleMobileNavClick(handleSKUsClick),
|
|
@@ -49122,7 +49375,7 @@ var SideNavBar = React26.memo(({
|
|
|
49122
49375
|
]
|
|
49123
49376
|
}
|
|
49124
49377
|
),
|
|
49125
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49378
|
+
canAccessPath("/health") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49126
49379
|
"button",
|
|
49127
49380
|
{
|
|
49128
49381
|
onClick: handleMobileNavClick(handleHealthClick),
|
|
@@ -49135,10 +49388,10 @@ var SideNavBar = React26.memo(({
|
|
|
49135
49388
|
}
|
|
49136
49389
|
)
|
|
49137
49390
|
] }),
|
|
49138
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
|
|
49391
|
+
settingsItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
|
|
49139
49392
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "px-5 mb-3 text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Settings & Support" }),
|
|
49140
49393
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
49141
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49394
|
+
canAccessPath("/targets") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49142
49395
|
"button",
|
|
49143
49396
|
{
|
|
49144
49397
|
onClick: handleMobileNavClick(handleTargetsClick),
|
|
@@ -49150,7 +49403,7 @@ var SideNavBar = React26.memo(({
|
|
|
49150
49403
|
]
|
|
49151
49404
|
}
|
|
49152
49405
|
),
|
|
49153
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49406
|
+
canAccessPath("/shifts") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49154
49407
|
"button",
|
|
49155
49408
|
{
|
|
49156
49409
|
onClick: handleMobileNavClick(handleShiftsClick),
|
|
@@ -49162,7 +49415,7 @@ var SideNavBar = React26.memo(({
|
|
|
49162
49415
|
]
|
|
49163
49416
|
}
|
|
49164
49417
|
),
|
|
49165
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49418
|
+
canAccessPath("/team-management") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49166
49419
|
"button",
|
|
49167
49420
|
{
|
|
49168
49421
|
onClick: handleMobileNavClick(handleTeamManagementClick),
|
|
@@ -49174,7 +49427,7 @@ var SideNavBar = React26.memo(({
|
|
|
49174
49427
|
]
|
|
49175
49428
|
}
|
|
49176
49429
|
),
|
|
49177
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49430
|
+
canAccessPath("/profile") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49178
49431
|
"button",
|
|
49179
49432
|
{
|
|
49180
49433
|
onClick: handleMobileNavClick(handleProfileClick),
|
|
@@ -49186,7 +49439,7 @@ var SideNavBar = React26.memo(({
|
|
|
49186
49439
|
]
|
|
49187
49440
|
}
|
|
49188
49441
|
),
|
|
49189
|
-
ticketsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49442
|
+
ticketsEnabled && canAccessPath("/tickets") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49190
49443
|
"button",
|
|
49191
49444
|
{
|
|
49192
49445
|
onClick: handleMobileNavClick(handleTicketsClick),
|
|
@@ -49198,7 +49451,7 @@ var SideNavBar = React26.memo(({
|
|
|
49198
49451
|
]
|
|
49199
49452
|
}
|
|
49200
49453
|
),
|
|
49201
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49454
|
+
canAccessPath("/help") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49202
49455
|
"button",
|
|
49203
49456
|
{
|
|
49204
49457
|
onClick: handleMobileNavClick(handleHelpClick),
|
|
@@ -51458,9 +51711,19 @@ var InviteUserDialog = ({
|
|
|
51458
51711
|
const [factorySearch, setFactorySearch] = React26.useState("");
|
|
51459
51712
|
const [isSubmitting, setIsSubmitting] = React26.useState(false);
|
|
51460
51713
|
const [error, setError] = React26.useState(null);
|
|
51714
|
+
const isSuperAdminOptifye = user?.role_level === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
|
|
51715
|
+
const canInviteOwner = isSuperAdminOptifye;
|
|
51461
51716
|
const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
|
|
51462
51717
|
const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
|
|
51463
51718
|
const canInviteSupervisor = ["owner", "it", "plant_head", "optifye"].includes(user?.role_level || "");
|
|
51719
|
+
const invitableRoles = React26.useMemo(() => {
|
|
51720
|
+
const roles = [];
|
|
51721
|
+
if (canInviteOwner) roles.push("owner");
|
|
51722
|
+
if (canInviteIT) roles.push("it");
|
|
51723
|
+
if (canInvitePlantHead) roles.push("plant_head");
|
|
51724
|
+
if (canInviteSupervisor) roles.push("supervisor");
|
|
51725
|
+
return roles;
|
|
51726
|
+
}, [canInviteOwner, canInviteIT, canInvitePlantHead, canInviteSupervisor]);
|
|
51464
51727
|
const filteredLines = React26.useMemo(() => {
|
|
51465
51728
|
const search = lineSearch.trim().toLowerCase();
|
|
51466
51729
|
if (!search) return availableLines;
|
|
@@ -51492,6 +51755,14 @@ var InviteUserDialog = ({
|
|
|
51492
51755
|
setError(null);
|
|
51493
51756
|
}
|
|
51494
51757
|
}, [isOpen]);
|
|
51758
|
+
React26.useEffect(() => {
|
|
51759
|
+
if (!isOpen || invitableRoles.length === 0) {
|
|
51760
|
+
return;
|
|
51761
|
+
}
|
|
51762
|
+
if (!invitableRoles.includes(selectedRole)) {
|
|
51763
|
+
setSelectedRole(invitableRoles[0]);
|
|
51764
|
+
}
|
|
51765
|
+
}, [isOpen, invitableRoles, selectedRole]);
|
|
51495
51766
|
const validateEmail = (email2) => {
|
|
51496
51767
|
const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$/;
|
|
51497
51768
|
return emailRegex.test(email2);
|
|
@@ -51521,6 +51792,14 @@ var InviteUserDialog = ({
|
|
|
51521
51792
|
setError("Please enter a valid email address");
|
|
51522
51793
|
return;
|
|
51523
51794
|
}
|
|
51795
|
+
if (!invitableRoles.includes(selectedRole)) {
|
|
51796
|
+
setError("You do not have permission to assign this role.");
|
|
51797
|
+
return;
|
|
51798
|
+
}
|
|
51799
|
+
if (selectedRole === "owner" && !canInviteOwner) {
|
|
51800
|
+
setError("Only super-admin Optifye users can create owner users.");
|
|
51801
|
+
return;
|
|
51802
|
+
}
|
|
51524
51803
|
const companyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
51525
51804
|
if (!companyId) {
|
|
51526
51805
|
setError("Company ID not found. Please refresh and try again.");
|
|
@@ -51701,6 +51980,33 @@ var InviteUserDialog = ({
|
|
|
51701
51980
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
51702
51981
|
] }),
|
|
51703
51982
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
51983
|
+
canInviteOwner && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
51984
|
+
"label",
|
|
51985
|
+
{
|
|
51986
|
+
className: cn(
|
|
51987
|
+
"flex items-start gap-3 p-4 border-2 rounded-lg cursor-pointer transition-all duration-200",
|
|
51988
|
+
selectedRole === "owner" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"
|
|
51989
|
+
),
|
|
51990
|
+
children: [
|
|
51991
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
51992
|
+
"input",
|
|
51993
|
+
{
|
|
51994
|
+
type: "radio",
|
|
51995
|
+
name: "role",
|
|
51996
|
+
value: "owner",
|
|
51997
|
+
checked: selectedRole === "owner",
|
|
51998
|
+
onChange: (e) => setSelectedRole(e.target.value),
|
|
51999
|
+
className: "mt-1",
|
|
52000
|
+
disabled: isSubmitting
|
|
52001
|
+
}
|
|
52002
|
+
),
|
|
52003
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
52004
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 mb-1", children: /* @__PURE__ */ jsxRuntime.jsx(RoleBadge_default, { role: "owner", size: "sm" }) }),
|
|
52005
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: "Company owner access. Full management permissions for the selected company dashboard." })
|
|
52006
|
+
] })
|
|
52007
|
+
]
|
|
52008
|
+
}
|
|
52009
|
+
),
|
|
51704
52010
|
canInviteIT && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
51705
52011
|
"label",
|
|
51706
52012
|
{
|
|
@@ -53572,11 +53878,32 @@ var UserManagementTable = ({
|
|
|
53572
53878
|
setSortDirection("asc");
|
|
53573
53879
|
}
|
|
53574
53880
|
};
|
|
53881
|
+
const getFactoryIdsFromUser = (user) => {
|
|
53882
|
+
const properties = user.properties || {};
|
|
53883
|
+
const rawFactoryIds = properties.factory_ids ?? properties.factory_id;
|
|
53884
|
+
if (Array.isArray(rawFactoryIds)) {
|
|
53885
|
+
return rawFactoryIds.filter((factoryId) => typeof factoryId === "string" && factoryId.length > 0);
|
|
53886
|
+
}
|
|
53887
|
+
if (typeof rawFactoryIds === "string" && rawFactoryIds.length > 0) {
|
|
53888
|
+
return [rawFactoryIds];
|
|
53889
|
+
}
|
|
53890
|
+
return [];
|
|
53891
|
+
};
|
|
53575
53892
|
const formatAssignments = (user) => {
|
|
53576
53893
|
if (user.role_level === "owner" || user.role_level === "it") return "Company-wide";
|
|
53577
53894
|
if (user.role_level === "optifye") return "All companies";
|
|
53578
|
-
if (user.role_level === "plant_head"
|
|
53579
|
-
|
|
53895
|
+
if (user.role_level === "plant_head") {
|
|
53896
|
+
if (user.assigned_factories && user.assigned_factories.length > 0) {
|
|
53897
|
+
return user.assigned_factories.join(", ");
|
|
53898
|
+
}
|
|
53899
|
+
const assignedFactoryIds = getFactoryIdsFromUser(user);
|
|
53900
|
+
if (assignedFactoryIds.length === 1) {
|
|
53901
|
+
return "1 factory assigned";
|
|
53902
|
+
}
|
|
53903
|
+
if (assignedFactoryIds.length > 1) {
|
|
53904
|
+
return `${assignedFactoryIds.length} factories assigned`;
|
|
53905
|
+
}
|
|
53906
|
+
return "No factories assigned";
|
|
53580
53907
|
}
|
|
53581
53908
|
if (user.role_level === "supervisor" && user.assigned_lines) {
|
|
53582
53909
|
return user.assigned_lines.join(", ") || "No lines assigned";
|
|
@@ -53768,22 +54095,28 @@ var UserManagementTable = ({
|
|
|
53768
54095
|
onUpdate: onLineAssignmentUpdate || (async () => {
|
|
53769
54096
|
})
|
|
53770
54097
|
}
|
|
53771
|
-
) : user.role_level === "plant_head" ?
|
|
53772
|
-
|
|
53773
|
-
{
|
|
53774
|
-
|
|
53775
|
-
currentFactoryIds: user.properties?.factory_ids || [],
|
|
53776
|
-
availableFactories: (
|
|
53777
|
-
// Filter factories to only show those from the target user's company
|
|
53778
|
-
user.properties?.company_id ? availableFactories.filter(
|
|
53779
|
-
(factory) => factory.company_id === user.properties?.company_id
|
|
53780
|
-
) : availableFactories
|
|
53781
|
-
),
|
|
53782
|
-
canEdit: permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate,
|
|
53783
|
-
onUpdate: onFactoryAssignmentUpdate || (async () => {
|
|
53784
|
-
})
|
|
54098
|
+
) : user.role_level === "plant_head" ? (() => {
|
|
54099
|
+
const canEditFactoryAssignments = permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate;
|
|
54100
|
+
if (!canEditFactoryAssignments) {
|
|
54101
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) });
|
|
53785
54102
|
}
|
|
53786
|
-
|
|
54103
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
54104
|
+
FactoryAssignmentDropdown,
|
|
54105
|
+
{
|
|
54106
|
+
userId: user.user_id,
|
|
54107
|
+
currentFactoryIds: getFactoryIdsFromUser(user),
|
|
54108
|
+
availableFactories: (
|
|
54109
|
+
// Filter factories to only show those from the target user's company
|
|
54110
|
+
user.properties?.company_id ? availableFactories.filter(
|
|
54111
|
+
(factory) => factory.company_id === user.properties?.company_id
|
|
54112
|
+
) : availableFactories
|
|
54113
|
+
),
|
|
54114
|
+
canEdit: canEditFactoryAssignments,
|
|
54115
|
+
onUpdate: onFactoryAssignmentUpdate || (async () => {
|
|
54116
|
+
})
|
|
54117
|
+
}
|
|
54118
|
+
);
|
|
54119
|
+
})() : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
|
|
53787
54120
|
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4", children: user.role_level === "plant_head" || user.role_level === "supervisor" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
53788
54121
|
"button",
|
|
53789
54122
|
{
|
|
@@ -54948,52 +55281,18 @@ function HomeView({
|
|
|
54948
55281
|
});
|
|
54949
55282
|
return merged;
|
|
54950
55283
|
}, [lineNames, dbLines]);
|
|
54951
|
-
const
|
|
54952
|
-
|
|
55284
|
+
const enabledLineIdSet = React26.useMemo(
|
|
55285
|
+
() => new Set(dbLines.filter((line) => line.enable).map((line) => line.id)),
|
|
55286
|
+
[dbLines]
|
|
55287
|
+
);
|
|
54953
55288
|
const isSupervisor = user?.role_level === "supervisor";
|
|
54954
|
-
const assignedLineIds = React26.useMemo(() => {
|
|
54955
|
-
const rawLineIds = user?.properties?.line_id || user?.properties?.line_ids || [];
|
|
54956
|
-
return Array.isArray(rawLineIds) ? rawLineIds : [];
|
|
54957
|
-
}, [user]);
|
|
54958
|
-
const assignedLineIdsInConfig = React26.useMemo(() => {
|
|
54959
|
-
if (assignedLineIds.length === 0) {
|
|
54960
|
-
return [];
|
|
54961
|
-
}
|
|
54962
|
-
return assignedLineIds.filter((id3) => allLineIds.includes(id3));
|
|
54963
|
-
}, [assignedLineIds, allLineIds]);
|
|
54964
|
-
const assignedFactoryIds = React26.useMemo(() => {
|
|
54965
|
-
const rawFactoryIds = user?.properties?.factory_id || user?.properties?.factory_ids || [];
|
|
54966
|
-
return Array.isArray(rawFactoryIds) ? rawFactoryIds : [];
|
|
54967
|
-
}, [user]);
|
|
54968
|
-
const lineFactoryMap = React26.useMemo(() => {
|
|
54969
|
-
const map = /* @__PURE__ */ new Map();
|
|
54970
|
-
dbLines.forEach((line) => {
|
|
54971
|
-
map.set(line.id, line.factory_id);
|
|
54972
|
-
});
|
|
54973
|
-
return map;
|
|
54974
|
-
}, [dbLines]);
|
|
54975
|
-
const plantHeadLineIds = React26.useMemo(() => {
|
|
54976
|
-
if (!isPlantHead || assignedFactoryIds.length === 0) {
|
|
54977
|
-
return [];
|
|
54978
|
-
}
|
|
54979
|
-
const assignedFactoryIdSet = new Set(assignedFactoryIds);
|
|
54980
|
-
return allLineIds.filter((lineId) => assignedFactoryIdSet.has(lineFactoryMap.get(lineId) || ""));
|
|
54981
|
-
}, [isPlantHead, assignedFactoryIds, allLineIds, lineFactoryMap]);
|
|
54982
55289
|
const visibleLineIds = React26.useMemo(() => {
|
|
54983
|
-
|
|
54984
|
-
|
|
54985
|
-
|
|
54986
|
-
if (isPlantHead) {
|
|
54987
|
-
const combinedLineIds = /* @__PURE__ */ new Set();
|
|
54988
|
-
plantHeadLineIds.forEach((lineId) => combinedLineIds.add(lineId));
|
|
54989
|
-
assignedLineIdsInConfig.forEach((lineId) => combinedLineIds.add(lineId));
|
|
54990
|
-
return Array.from(combinedLineIds);
|
|
55290
|
+
const scoped = Array.from(new Set(allLineIds.filter(Boolean)));
|
|
55291
|
+
if (enabledLineIdSet.size === 0) {
|
|
55292
|
+
return scoped;
|
|
54991
55293
|
}
|
|
54992
|
-
|
|
54993
|
-
|
|
54994
|
-
}
|
|
54995
|
-
return allLineIds;
|
|
54996
|
-
}, [isOwner, isPlantHead, isSupervisor, allLineIds, plantHeadLineIds, assignedLineIdsInConfig]);
|
|
55294
|
+
return scoped.filter((lineId) => enabledLineIdSet.has(lineId));
|
|
55295
|
+
}, [allLineIds, enabledLineIdSet]);
|
|
54997
55296
|
const fallbackLineId = visibleLineIds[0] || defaultLineId;
|
|
54998
55297
|
const defaultHomeLineId = fallbackLineId;
|
|
54999
55298
|
const availableLineIds = React26.useMemo(() => {
|
|
@@ -55020,7 +55319,7 @@ function HomeView({
|
|
|
55020
55319
|
return defaultHomeLineId;
|
|
55021
55320
|
});
|
|
55022
55321
|
React26.useEffect(() => {
|
|
55023
|
-
if (!user ||
|
|
55322
|
+
if (!user || availableLineIds.length === 0) {
|
|
55024
55323
|
return;
|
|
55025
55324
|
}
|
|
55026
55325
|
try {
|
|
@@ -55034,13 +55333,14 @@ function HomeView({
|
|
|
55034
55333
|
} catch (error) {
|
|
55035
55334
|
console.warn("Failed to read line filter from sessionStorage:", error);
|
|
55036
55335
|
}
|
|
55336
|
+
if (availableLineIds.includes(selectedLineId)) {
|
|
55337
|
+
return;
|
|
55338
|
+
}
|
|
55037
55339
|
if (defaultHomeLineId !== selectedLineId) {
|
|
55038
55340
|
setSelectedLineId(defaultHomeLineId);
|
|
55039
55341
|
}
|
|
55040
55342
|
}, [
|
|
55041
55343
|
user,
|
|
55042
|
-
isSupervisor,
|
|
55043
|
-
isPlantHead,
|
|
55044
55344
|
availableLineIds,
|
|
55045
55345
|
defaultHomeLineId,
|
|
55046
55346
|
selectedLineId,
|
|
@@ -55059,14 +55359,14 @@ function HomeView({
|
|
|
55059
55359
|
const [diagnoses, setDiagnoses] = React26.useState([]);
|
|
55060
55360
|
const timezone = useAppTimezone();
|
|
55061
55361
|
const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
|
|
55062
|
-
|
|
55362
|
+
visibleLineIds,
|
|
55063
55363
|
dashboardConfig?.shiftConfig
|
|
55064
55364
|
);
|
|
55065
55365
|
React26.useEffect(() => {
|
|
55066
55366
|
const initDisplayNames = async () => {
|
|
55067
55367
|
try {
|
|
55068
55368
|
if (selectedLineId === factoryViewId) {
|
|
55069
|
-
for (const lineId of
|
|
55369
|
+
for (const lineId of visibleLineIds) {
|
|
55070
55370
|
await preInitializeWorkspaceDisplayNames(lineId);
|
|
55071
55371
|
}
|
|
55072
55372
|
} else {
|
|
@@ -55079,7 +55379,7 @@ function HomeView({
|
|
|
55079
55379
|
}
|
|
55080
55380
|
};
|
|
55081
55381
|
initDisplayNames();
|
|
55082
|
-
}, [selectedLineId, factoryViewId,
|
|
55382
|
+
}, [selectedLineId, factoryViewId, visibleLineIds]);
|
|
55083
55383
|
const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
|
|
55084
55384
|
const {
|
|
55085
55385
|
displayNames: workspaceDisplayNames,
|
|
@@ -58830,8 +59130,32 @@ var KPIsOverviewView = ({
|
|
|
58830
59130
|
const dbTimezone = useAppTimezone();
|
|
58831
59131
|
const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
58832
59132
|
const { startDate: monthStartDate, endDate: monthEndDateKey, monthEndDate } = getMonthDateInfo(configuredTimezone);
|
|
58833
|
-
const
|
|
58834
|
-
const
|
|
59133
|
+
const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
|
|
59134
|
+
const scopedLineIds = React26__namespace.default.useMemo(
|
|
59135
|
+
() => Array.isArray(user?.access_scope?.line_ids) ? user.access_scope.line_ids.filter((lineId) => typeof lineId === "string" && lineId.length > 0) : [],
|
|
59136
|
+
[user?.access_scope?.line_ids]
|
|
59137
|
+
);
|
|
59138
|
+
const hasCanonicalScope = !!user?.scope_mode || !!user?.access_scope;
|
|
59139
|
+
const scopeRole = (user?.role_level || user?.role || "").toLowerCase();
|
|
59140
|
+
const isStrictLineScopedRole = scopeRole === "supervisor" || scopeRole === "plant_head";
|
|
59141
|
+
const resolvedAssignedLineIds = React26__namespace.default.useMemo(() => {
|
|
59142
|
+
if (isSuperAdmin) return [];
|
|
59143
|
+
if (scopedLineIds.length > 0) return scopedLineIds;
|
|
59144
|
+
if (lineIds && lineIds.length > 0) return lineIds;
|
|
59145
|
+
if (isStrictLineScopedRole && hasCanonicalScope) return [];
|
|
59146
|
+
return [];
|
|
59147
|
+
}, [isSuperAdmin, scopedLineIds, lineIds, isStrictLineScopedRole, hasCanonicalScope]);
|
|
59148
|
+
const assignedLineIdSet = React26__namespace.default.useMemo(
|
|
59149
|
+
() => new Set(resolvedAssignedLineIds),
|
|
59150
|
+
[resolvedAssignedLineIds]
|
|
59151
|
+
);
|
|
59152
|
+
const metricsLineIds = React26__namespace.default.useMemo(() => {
|
|
59153
|
+
if (isSuperAdmin) {
|
|
59154
|
+
return lineIds ?? [];
|
|
59155
|
+
}
|
|
59156
|
+
return resolvedAssignedLineIds;
|
|
59157
|
+
}, [isSuperAdmin, lineIds, resolvedAssignedLineIds]);
|
|
59158
|
+
const assignedLineIdsForLeaderboard = isSuperAdmin ? void 0 : resolvedAssignedLineIds;
|
|
58835
59159
|
const leaderboardLinesForView = React26__namespace.default.useMemo(() => {
|
|
58836
59160
|
const targetMode = viewType === "machine" ? "uptime" : "output";
|
|
58837
59161
|
return leaderboardLines.filter((line) => (line.monitoring_mode ?? "output") === targetMode);
|
|
@@ -58883,7 +59207,7 @@ var KPIsOverviewView = ({
|
|
|
58883
59207
|
error: metricsError
|
|
58884
59208
|
} = useDashboardMetrics({
|
|
58885
59209
|
lineId: factoryViewId,
|
|
58886
|
-
userAccessibleLineIds:
|
|
59210
|
+
userAccessibleLineIds: metricsLineIds
|
|
58887
59211
|
});
|
|
58888
59212
|
const defaultKPIs = React26__namespace.default.useMemo(() => createDefaultKPIs(), []);
|
|
58889
59213
|
const kpisByLineId = React26__namespace.default.useMemo(() => {
|
|
@@ -58937,16 +59261,16 @@ var KPIsOverviewView = ({
|
|
|
58937
59261
|
console.log("[KPIsOverviewView] Fetching lines with lineIds filter:", lineIds);
|
|
58938
59262
|
const allLines = await dashboardService.getAllLines();
|
|
58939
59263
|
let filteredLines = allLines;
|
|
58940
|
-
if (
|
|
58941
|
-
filteredLines = allLines.filter((line) =>
|
|
58942
|
-
console.log("[KPIsOverviewView]
|
|
59264
|
+
if (!isSuperAdmin) {
|
|
59265
|
+
filteredLines = allLines.filter((line) => assignedLineIdSet.has(line.id));
|
|
59266
|
+
console.log("[KPIsOverviewView] Applied scoped line filter:", {
|
|
58943
59267
|
total: allLines.length,
|
|
58944
59268
|
filtered: filteredLines.length,
|
|
58945
|
-
|
|
59269
|
+
allowedLineIds: resolvedAssignedLineIds,
|
|
58946
59270
|
filteredLineIds: filteredLines.map((l) => l.id)
|
|
58947
59271
|
});
|
|
58948
59272
|
} else {
|
|
58949
|
-
console.log("[KPIsOverviewView]
|
|
59273
|
+
console.log("[KPIsOverviewView] Super admin view, showing all lines:", allLines.length);
|
|
58950
59274
|
}
|
|
58951
59275
|
setLines(filteredLines);
|
|
58952
59276
|
} catch (err) {
|
|
@@ -58957,34 +59281,36 @@ var KPIsOverviewView = ({
|
|
|
58957
59281
|
}
|
|
58958
59282
|
};
|
|
58959
59283
|
fetchLines();
|
|
58960
|
-
}, [supabase, dashboardConfig, lineIds]);
|
|
59284
|
+
}, [supabase, dashboardConfig, lineIds, isSuperAdmin, assignedLineIdSet, resolvedAssignedLineIds]);
|
|
58961
59285
|
React26.useEffect(() => {
|
|
58962
59286
|
let isMounted = true;
|
|
58963
59287
|
const fetchLeaderboardLines = async () => {
|
|
58964
59288
|
if (!supabase || !resolvedCompanyId) {
|
|
58965
|
-
setLeaderboardLines(lines);
|
|
59289
|
+
setLeaderboardLines(lines.filter((line) => line.enable !== false));
|
|
58966
59290
|
return;
|
|
58967
59291
|
}
|
|
58968
59292
|
setLeaderboardLinesLoading(true);
|
|
58969
59293
|
try {
|
|
58970
|
-
const
|
|
58971
|
-
|
|
59294
|
+
const data = await fetchBackendJson(
|
|
59295
|
+
supabase,
|
|
59296
|
+
`/api/dashboard/leaderboard-lines?company_id=${encodeURIComponent(resolvedCompanyId)}`
|
|
59297
|
+
);
|
|
58972
59298
|
if (!isMounted) return;
|
|
58973
|
-
const transformed =
|
|
59299
|
+
const transformed = (data.lines || []).filter((line) => line.enable !== false).map((line) => ({
|
|
58974
59300
|
id: line.id,
|
|
58975
|
-
line_name: line.
|
|
58976
|
-
factory_id: line.
|
|
59301
|
+
line_name: line.line_name,
|
|
59302
|
+
factory_id: line.factory_id || "",
|
|
58977
59303
|
factory_name: "N/A",
|
|
58978
|
-
company_id: line.
|
|
59304
|
+
company_id: line.company_id,
|
|
58979
59305
|
company_name: "",
|
|
58980
|
-
enable: line.
|
|
58981
|
-
monitoring_mode: line.
|
|
59306
|
+
enable: line.enable ?? true,
|
|
59307
|
+
monitoring_mode: line.monitoring_mode ?? "output"
|
|
58982
59308
|
}));
|
|
58983
59309
|
setLeaderboardLines(transformed);
|
|
58984
59310
|
} catch (err) {
|
|
58985
59311
|
console.error("[KPIsOverviewView] Failed to load leaderboard lines:", err);
|
|
58986
59312
|
if (!isMounted) return;
|
|
58987
|
-
setLeaderboardLines(lines);
|
|
59313
|
+
setLeaderboardLines(lines.filter((line) => line.enable !== false));
|
|
58988
59314
|
} finally {
|
|
58989
59315
|
if (isMounted) setLeaderboardLinesLoading(false);
|
|
58990
59316
|
}
|
|
@@ -59065,11 +59391,10 @@ var KPIsOverviewView = ({
|
|
|
59065
59391
|
lineMode: viewType === "machine" ? "uptime" : "output"
|
|
59066
59392
|
});
|
|
59067
59393
|
const nextMap = /* @__PURE__ */ new Map();
|
|
59394
|
+
targetLineIds.forEach((lineId) => nextMap.set(lineId, 0));
|
|
59068
59395
|
entries.forEach((entry) => {
|
|
59069
59396
|
const value = Number(entry.avg_efficiency);
|
|
59070
|
-
|
|
59071
|
-
nextMap.set(entry.line_id, value);
|
|
59072
|
-
}
|
|
59397
|
+
nextMap.set(entry.line_id, Number.isFinite(value) ? value : 0);
|
|
59073
59398
|
});
|
|
59074
59399
|
setTodayEfficiencyByLineId(nextMap);
|
|
59075
59400
|
} catch (err) {
|
|
@@ -59152,6 +59477,9 @@ var KPIsOverviewView = ({
|
|
|
59152
59477
|
};
|
|
59153
59478
|
};
|
|
59154
59479
|
const handleLineClick = (line, kpis) => {
|
|
59480
|
+
if (!isSuperAdmin && !assignedLineIdSet.has(line.id)) {
|
|
59481
|
+
return;
|
|
59482
|
+
}
|
|
59155
59483
|
const trackProps = {
|
|
59156
59484
|
line_id: line.id,
|
|
59157
59485
|
line_name: line.line_name,
|
|
@@ -59664,6 +59992,7 @@ var MobileWorkspaceCard = React26.memo(({
|
|
|
59664
59992
|
workspace,
|
|
59665
59993
|
rank,
|
|
59666
59994
|
cardClass,
|
|
59995
|
+
isClickable,
|
|
59667
59996
|
onWorkspaceClick,
|
|
59668
59997
|
getMedalIcon,
|
|
59669
59998
|
efficiencyLabel
|
|
@@ -59676,8 +60005,8 @@ var MobileWorkspaceCard = React26.memo(({
|
|
|
59676
60005
|
transition: {
|
|
59677
60006
|
layout: { duration: 0.3, ease: "easeInOut" }
|
|
59678
60007
|
},
|
|
59679
|
-
onClick: () => onWorkspaceClick(workspace, rank),
|
|
59680
|
-
className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] cursor-pointer`,
|
|
60008
|
+
onClick: isClickable ? () => onWorkspaceClick(workspace, rank) : void 0,
|
|
60009
|
+
className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] ${isClickable ? "cursor-pointer" : "cursor-not-allowed opacity-75"}`,
|
|
59681
60010
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
59682
60011
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
59683
60012
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -59699,13 +60028,14 @@ var MobileWorkspaceCard = React26.memo(({
|
|
|
59699
60028
|
] })
|
|
59700
60029
|
}
|
|
59701
60030
|
), (prevProps, nextProps) => {
|
|
59702
|
-
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;
|
|
60031
|
+
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;
|
|
59703
60032
|
});
|
|
59704
60033
|
MobileWorkspaceCard.displayName = "MobileWorkspaceCard";
|
|
59705
60034
|
var DesktopWorkspaceRow = React26.memo(({
|
|
59706
60035
|
workspace,
|
|
59707
60036
|
index,
|
|
59708
60037
|
rowClass,
|
|
60038
|
+
isClickable,
|
|
59709
60039
|
onWorkspaceClick,
|
|
59710
60040
|
getMedalIcon
|
|
59711
60041
|
}) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -59715,8 +60045,8 @@ var DesktopWorkspaceRow = React26.memo(({
|
|
|
59715
60045
|
layoutId: `row-${workspace.workspace_uuid}`,
|
|
59716
60046
|
initial: false,
|
|
59717
60047
|
transition: { layout: { duration: 0.3, ease: "easeInOut" } },
|
|
59718
|
-
onClick: () => onWorkspaceClick(workspace, index + 1),
|
|
59719
|
-
className: `${rowClass} hover:bg-gray-50/90
|
|
60048
|
+
onClick: isClickable ? () => onWorkspaceClick(workspace, index + 1) : void 0,
|
|
60049
|
+
className: `${rowClass} transition-colors duration-150 ${isClickable ? "hover:bg-gray-50/90 cursor-pointer group" : "cursor-not-allowed opacity-75"}`,
|
|
59720
60050
|
children: [
|
|
59721
60051
|
/* @__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: [
|
|
59722
60052
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: index + 1 }),
|
|
@@ -59728,7 +60058,7 @@ var DesktopWorkspaceRow = React26.memo(({
|
|
|
59728
60058
|
]
|
|
59729
60059
|
}
|
|
59730
60060
|
), (prevProps, nextProps) => {
|
|
59731
|
-
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;
|
|
60061
|
+
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;
|
|
59732
60062
|
});
|
|
59733
60063
|
DesktopWorkspaceRow.displayName = "DesktopWorkspaceRow";
|
|
59734
60064
|
var LeaderboardDetailView = React26.memo(({
|
|
@@ -59888,6 +60218,16 @@ var LeaderboardDetailView = React26.memo(({
|
|
|
59888
60218
|
}
|
|
59889
60219
|
return allLineIds;
|
|
59890
60220
|
}, [entityConfig, userAccessibleLineIds]);
|
|
60221
|
+
const accessibleLineIdSet = React26.useMemo(
|
|
60222
|
+
() => new Set((userAccessibleLineIds || []).filter(Boolean)),
|
|
60223
|
+
[userAccessibleLineIds]
|
|
60224
|
+
);
|
|
60225
|
+
const canOpenWorkspace = React26.useCallback((workspaceLineId) => {
|
|
60226
|
+
if (!workspaceLineId) return false;
|
|
60227
|
+
if (!userAccessibleLineIds) return true;
|
|
60228
|
+
if (accessibleLineIdSet.size === 0) return false;
|
|
60229
|
+
return accessibleLineIdSet.has(workspaceLineId);
|
|
60230
|
+
}, [accessibleLineIdSet, userAccessibleLineIds]);
|
|
59891
60231
|
const { hasUptime: lineModeHasUptime, hasOutput: lineModeHasOutput } = React26.useMemo(() => {
|
|
59892
60232
|
if (!lines || lines.length === 0) {
|
|
59893
60233
|
return { hasUptime: false, hasOutput: false };
|
|
@@ -60285,6 +60625,9 @@ var LeaderboardDetailView = React26.memo(({
|
|
|
60285
60625
|
return `${startDate.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${endDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
|
|
60286
60626
|
}, [normalizedRange]);
|
|
60287
60627
|
const handleWorkspaceClick = React26.useCallback((workspace, rank) => {
|
|
60628
|
+
if (!canOpenWorkspace(workspace.line_id)) {
|
|
60629
|
+
return;
|
|
60630
|
+
}
|
|
60288
60631
|
trackCoreEvent("Workspace from Leaderboard Clicked", {
|
|
60289
60632
|
workspace_name: workspace.workspace_name,
|
|
60290
60633
|
workspace_id: workspace.workspace_uuid,
|
|
@@ -60317,7 +60660,7 @@ var LeaderboardDetailView = React26.memo(({
|
|
|
60317
60660
|
const combinedParams = navParams ? `${navParams}&${contextParamString}` : `?${contextParamString}`;
|
|
60318
60661
|
navigation.navigate(`/workspace/${workspace.workspace_uuid}${combinedParams}`);
|
|
60319
60662
|
}
|
|
60320
|
-
}, [onWorkspaceClick, navigation, date, shiftId]);
|
|
60663
|
+
}, [canOpenWorkspace, onWorkspaceClick, navigation, date, shiftId]);
|
|
60321
60664
|
React26.useEffect(() => {
|
|
60322
60665
|
workspacesLengthRef.current = activeEntries.length || 0;
|
|
60323
60666
|
}, [activeEntries.length]);
|
|
@@ -60608,6 +60951,7 @@ var LeaderboardDetailView = React26.memo(({
|
|
|
60608
60951
|
workspace: ws,
|
|
60609
60952
|
rank,
|
|
60610
60953
|
cardClass,
|
|
60954
|
+
isClickable: canOpenWorkspace(ws.line_id),
|
|
60611
60955
|
onWorkspaceClick: stableHandleWorkspaceClick,
|
|
60612
60956
|
getMedalIcon: stableGetMedalIcon,
|
|
60613
60957
|
efficiencyLabel
|
|
@@ -60632,6 +60976,7 @@ var LeaderboardDetailView = React26.memo(({
|
|
|
60632
60976
|
workspace: ws,
|
|
60633
60977
|
index,
|
|
60634
60978
|
rowClass,
|
|
60979
|
+
isClickable: canOpenWorkspace(ws.line_id),
|
|
60635
60980
|
onWorkspaceClick: stableHandleWorkspaceClick,
|
|
60636
60981
|
getMedalIcon: stableGetMedalIcon
|
|
60637
60982
|
},
|
|
@@ -60642,7 +60987,7 @@ var LeaderboardDetailView = React26.memo(({
|
|
|
60642
60987
|
) })
|
|
60643
60988
|
] });
|
|
60644
60989
|
}, (prevProps, nextProps) => {
|
|
60645
|
-
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;
|
|
60990
|
+
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;
|
|
60646
60991
|
});
|
|
60647
60992
|
LeaderboardDetailView.displayName = "LeaderboardDetailView";
|
|
60648
60993
|
var LeaderboardDetailViewWithDisplayNames = withAllWorkspaceDisplayNames(LeaderboardDetailView);
|
|
@@ -66403,9 +66748,38 @@ var TeamManagementView = ({
|
|
|
66403
66748
|
optifye: 0
|
|
66404
66749
|
});
|
|
66405
66750
|
const [isAddUserDialogOpen, setIsAddUserDialogOpen] = React26.useState(false);
|
|
66751
|
+
const normalizeIds = React26.useCallback((value) => {
|
|
66752
|
+
if (Array.isArray(value)) {
|
|
66753
|
+
return value.filter((id3) => typeof id3 === "string" && id3.length > 0);
|
|
66754
|
+
}
|
|
66755
|
+
if (typeof value === "string" && value.length > 0) {
|
|
66756
|
+
return [value];
|
|
66757
|
+
}
|
|
66758
|
+
return [];
|
|
66759
|
+
}, []);
|
|
66760
|
+
const plantHeadFactoryIds = React26.useMemo(() => {
|
|
66761
|
+
if (user?.role_level !== "plant_head") return [];
|
|
66762
|
+
const scopedFactoryIds = normalizeIds(user?.access_scope?.factory_ids);
|
|
66763
|
+
if (scopedFactoryIds.length > 0) {
|
|
66764
|
+
return Array.from(new Set(scopedFactoryIds));
|
|
66765
|
+
}
|
|
66766
|
+
const propertyFactoryIds = normalizeIds(
|
|
66767
|
+
user?.properties?.factory_ids ?? user?.properties?.factory_id
|
|
66768
|
+
);
|
|
66769
|
+
if (propertyFactoryIds.length > 0) {
|
|
66770
|
+
return Array.from(new Set(propertyFactoryIds));
|
|
66771
|
+
}
|
|
66772
|
+
return entityConfig?.factoryId ? [entityConfig.factoryId] : [];
|
|
66773
|
+
}, [user, entityConfig?.factoryId, normalizeIds]);
|
|
66774
|
+
const notifyScopeRefresh = React26.useCallback(() => {
|
|
66775
|
+
if (typeof window !== "undefined") {
|
|
66776
|
+
window.dispatchEvent(new Event("rbac:refresh-scope"));
|
|
66777
|
+
}
|
|
66778
|
+
}, []);
|
|
66406
66779
|
const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "plant_head" || user?.role_level === "optifye";
|
|
66407
66780
|
const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
|
|
66408
|
-
const
|
|
66781
|
+
const pageCompanyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
66782
|
+
const companyIdForUsage = pageCompanyId;
|
|
66409
66783
|
const usageDateRange = React26.useMemo(() => {
|
|
66410
66784
|
const today = /* @__PURE__ */ new Date();
|
|
66411
66785
|
const dayOfWeek = today.getDay();
|
|
@@ -66436,8 +66810,8 @@ var TeamManagementView = ({
|
|
|
66436
66810
|
return acc;
|
|
66437
66811
|
}, {});
|
|
66438
66812
|
}, [usageData, usageDateRange.daysElapsed]);
|
|
66439
|
-
const pageTitle =
|
|
66440
|
-
const pageDescription = user?.role_level === "
|
|
66813
|
+
const pageTitle = "Team Management";
|
|
66814
|
+
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";
|
|
66441
66815
|
const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "it", "plant_head"].includes(user.role_level) : false;
|
|
66442
66816
|
const loadData = React26.useCallback(async () => {
|
|
66443
66817
|
if (!supabase) {
|
|
@@ -66445,20 +66819,16 @@ var TeamManagementView = ({
|
|
|
66445
66819
|
setIsLoading(false);
|
|
66446
66820
|
return;
|
|
66447
66821
|
}
|
|
66448
|
-
const
|
|
66449
|
-
if (!
|
|
66450
|
-
|
|
66451
|
-
|
|
66452
|
-
|
|
66453
|
-
setIsLoading(false);
|
|
66454
|
-
return;
|
|
66455
|
-
}
|
|
66822
|
+
const companyId = pageCompanyId;
|
|
66823
|
+
if (!companyId) {
|
|
66824
|
+
setError("Company not found. Please contact your administrator.");
|
|
66825
|
+
setIsLoading(false);
|
|
66826
|
+
return;
|
|
66456
66827
|
}
|
|
66457
|
-
const companyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
66458
66828
|
console.log("[TeamManagementView] Loading data:", {
|
|
66459
66829
|
role: user?.role_level,
|
|
66460
|
-
isOptifye:
|
|
66461
|
-
companyId
|
|
66830
|
+
isOptifye: user?.role_level === "optifye",
|
|
66831
|
+
companyId
|
|
66462
66832
|
});
|
|
66463
66833
|
setIsLoading(true);
|
|
66464
66834
|
setError(void 0);
|
|
@@ -66473,23 +66843,7 @@ var TeamManagementView = ({
|
|
|
66473
66843
|
if (!backendUrl) {
|
|
66474
66844
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
66475
66845
|
}
|
|
66476
|
-
if (
|
|
66477
|
-
const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").order("factory_name");
|
|
66478
|
-
const linesResponse = await fetch(`${backendUrl}/api/lines`, {
|
|
66479
|
-
headers: {
|
|
66480
|
-
"Authorization": `Bearer ${token}`,
|
|
66481
|
-
"Content-Type": "application/json"
|
|
66482
|
-
}
|
|
66483
|
-
});
|
|
66484
|
-
if (!linesResponse.ok) {
|
|
66485
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
66486
|
-
}
|
|
66487
|
-
const linesData = await linesResponse.json();
|
|
66488
|
-
const lines = linesData.lines || [];
|
|
66489
|
-
setAvailableFactories(factories || []);
|
|
66490
|
-
setAvailableLines(lines);
|
|
66491
|
-
console.log("[TeamManagementView] Optifye - Loaded factories:", factories?.length, "lines:", lines?.length, "Sample lines:", lines?.slice(0, 3));
|
|
66492
|
-
} else if ((user?.role_level === "owner" || user?.role_level === "it") && companyId) {
|
|
66846
|
+
if ((user?.role_level === "optifye" || user?.role_level === "owner" || user?.role_level === "it") && companyId) {
|
|
66493
66847
|
const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").eq("company_id", companyId).order("factory_name");
|
|
66494
66848
|
const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
|
|
66495
66849
|
headers: {
|
|
@@ -66504,9 +66858,8 @@ var TeamManagementView = ({
|
|
|
66504
66858
|
const lines = linesData.lines || [];
|
|
66505
66859
|
setAvailableFactories(factories || []);
|
|
66506
66860
|
setAvailableLines(lines);
|
|
66507
|
-
console.log("[TeamManagementView]
|
|
66861
|
+
console.log("[TeamManagementView] Company-scoped team view - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
|
|
66508
66862
|
} else if (user?.role_level === "plant_head") {
|
|
66509
|
-
const plantHeadFactoryIds = user?.properties?.factory_ids || [];
|
|
66510
66863
|
if (plantHeadFactoryIds.length > 0) {
|
|
66511
66864
|
if (companyId) {
|
|
66512
66865
|
const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
|
|
@@ -66559,35 +66912,46 @@ var TeamManagementView = ({
|
|
|
66559
66912
|
setAvailableFactories([]);
|
|
66560
66913
|
console.log("[TeamManagementView] Fallback - Company:", companyId, "Loaded lines:", lines?.length);
|
|
66561
66914
|
}
|
|
66562
|
-
|
|
66563
|
-
|
|
66564
|
-
|
|
66565
|
-
|
|
66566
|
-
|
|
66567
|
-
|
|
66568
|
-
|
|
66569
|
-
|
|
66570
|
-
|
|
66571
|
-
|
|
66572
|
-
|
|
66573
|
-
|
|
66574
|
-
|
|
66575
|
-
|
|
66576
|
-
|
|
66577
|
-
|
|
66578
|
-
|
|
66579
|
-
|
|
66580
|
-
|
|
66581
|
-
|
|
66582
|
-
|
|
66583
|
-
|
|
66584
|
-
|
|
66585
|
-
|
|
66586
|
-
|
|
66587
|
-
|
|
66588
|
-
|
|
66589
|
-
|
|
66590
|
-
|
|
66915
|
+
const usersPromise = user?.role_level === "plant_head" ? (async () => {
|
|
66916
|
+
if (plantHeadFactoryIds.length === 0) {
|
|
66917
|
+
return [];
|
|
66918
|
+
}
|
|
66919
|
+
const results = await Promise.allSettled(
|
|
66920
|
+
plantHeadFactoryIds.map((factoryId) => userManagementService.getFactoryUsers(factoryId))
|
|
66921
|
+
);
|
|
66922
|
+
const successful = results.filter(
|
|
66923
|
+
(result) => result.status === "fulfilled"
|
|
66924
|
+
).flatMap((result) => result.value || []);
|
|
66925
|
+
if (successful.length > 0) {
|
|
66926
|
+
const byUserId = /* @__PURE__ */ new Map();
|
|
66927
|
+
successful.forEach((u) => {
|
|
66928
|
+
if (u?.user_id) {
|
|
66929
|
+
byUserId.set(u.user_id, u);
|
|
66930
|
+
}
|
|
66931
|
+
});
|
|
66932
|
+
return Array.from(byUserId.values());
|
|
66933
|
+
}
|
|
66934
|
+
const firstRejected = results.find(
|
|
66935
|
+
(result) => result.status === "rejected"
|
|
66936
|
+
);
|
|
66937
|
+
if (firstRejected) {
|
|
66938
|
+
throw firstRejected.reason;
|
|
66939
|
+
}
|
|
66940
|
+
return [];
|
|
66941
|
+
})() : userManagementService.getCompanyUsers(companyId);
|
|
66942
|
+
const [usersData, userStats] = await Promise.all([
|
|
66943
|
+
usersPromise,
|
|
66944
|
+
userManagementService.getUserStats(companyId)
|
|
66945
|
+
]);
|
|
66946
|
+
setUsers(usersData);
|
|
66947
|
+
setStats({
|
|
66948
|
+
totalUsers: userStats.total,
|
|
66949
|
+
owners: userStats.owners,
|
|
66950
|
+
it: userStats.it,
|
|
66951
|
+
plantHeads: userStats.plant_heads,
|
|
66952
|
+
supervisors: userStats.supervisors,
|
|
66953
|
+
optifye: 0
|
|
66954
|
+
});
|
|
66591
66955
|
} catch (err) {
|
|
66592
66956
|
console.error("Error loading team management data:", err);
|
|
66593
66957
|
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
@@ -66595,16 +66959,16 @@ var TeamManagementView = ({
|
|
|
66595
66959
|
} finally {
|
|
66596
66960
|
setIsLoading(false);
|
|
66597
66961
|
}
|
|
66598
|
-
}, [supabase, user, entityConfig]);
|
|
66962
|
+
}, [supabase, user, pageCompanyId, entityConfig?.factoryId, plantHeadFactoryIds]);
|
|
66599
66963
|
React26.useEffect(() => {
|
|
66600
|
-
const companyId =
|
|
66601
|
-
const canLoad = hasAccess && user &&
|
|
66964
|
+
const companyId = pageCompanyId;
|
|
66965
|
+
const canLoad = hasAccess && user && !!companyId;
|
|
66602
66966
|
if (canLoad) {
|
|
66603
66967
|
loadData();
|
|
66604
66968
|
} else if (!user) {
|
|
66605
66969
|
setIsLoading(true);
|
|
66606
66970
|
}
|
|
66607
|
-
}, [hasAccess, loadData, user,
|
|
66971
|
+
}, [hasAccess, loadData, user, pageCompanyId]);
|
|
66608
66972
|
const handleUserAdded = React26.useCallback(() => {
|
|
66609
66973
|
loadData();
|
|
66610
66974
|
}, [loadData]);
|
|
@@ -66618,12 +66982,13 @@ var TeamManagementView = ({
|
|
|
66618
66982
|
updated_by: user.id
|
|
66619
66983
|
});
|
|
66620
66984
|
sonner.toast.success("User role updated successfully");
|
|
66985
|
+
notifyScopeRefresh();
|
|
66621
66986
|
loadData();
|
|
66622
66987
|
} catch (err) {
|
|
66623
66988
|
console.error("Error updating user role:", err);
|
|
66624
66989
|
sonner.toast.error("Failed to update user role");
|
|
66625
66990
|
}
|
|
66626
|
-
}, [supabase, user, loadData]);
|
|
66991
|
+
}, [supabase, user, loadData, notifyScopeRefresh]);
|
|
66627
66992
|
const handleRemoveUser = React26.useCallback(async (userId) => {
|
|
66628
66993
|
if (!supabase || !user) return;
|
|
66629
66994
|
try {
|
|
@@ -66646,12 +67011,13 @@ var TeamManagementView = ({
|
|
|
66646
67011
|
assigned_by: user.id
|
|
66647
67012
|
});
|
|
66648
67013
|
sonner.toast.success("Line assignments updated successfully");
|
|
67014
|
+
notifyScopeRefresh();
|
|
66649
67015
|
loadData();
|
|
66650
67016
|
} catch (err) {
|
|
66651
67017
|
console.error("Error updating line assignments:", err);
|
|
66652
67018
|
sonner.toast.error("Failed to update line assignments");
|
|
66653
67019
|
}
|
|
66654
|
-
}, [supabase, user, loadData]);
|
|
67020
|
+
}, [supabase, user, loadData, notifyScopeRefresh]);
|
|
66655
67021
|
const handleFactoryAssignmentUpdate = React26.useCallback(async (userId, factoryIds) => {
|
|
66656
67022
|
if (!supabase || !user) return;
|
|
66657
67023
|
try {
|
|
@@ -66662,12 +67028,13 @@ var TeamManagementView = ({
|
|
|
66662
67028
|
assigned_by: user.id
|
|
66663
67029
|
});
|
|
66664
67030
|
sonner.toast.success("Factory assignments updated successfully");
|
|
67031
|
+
notifyScopeRefresh();
|
|
66665
67032
|
loadData();
|
|
66666
67033
|
} catch (err) {
|
|
66667
67034
|
console.error("Error updating factory assignments:", err);
|
|
66668
67035
|
sonner.toast.error("Failed to update factory assignments");
|
|
66669
67036
|
}
|
|
66670
|
-
}, [supabase, user, loadData]);
|
|
67037
|
+
}, [supabase, user, loadData, notifyScopeRefresh]);
|
|
66671
67038
|
const handleProfileUpdate = React26.useCallback(async (userId, firstName, lastName, profilePhotoUrl) => {
|
|
66672
67039
|
if (!supabase || !user) return;
|
|
66673
67040
|
try {
|