@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.css +7 -6
- package/dist/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +1198 -408
- package/dist/index.mjs +1198 -408
- 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,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
|
|
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
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
|
-
|
|
48883
|
-
|
|
48884
|
-
|
|
48885
|
-
|
|
48886
|
-
|
|
48887
|
-
|
|
48888
|
-
|
|
48889
|
-
|
|
48890
|
-
|
|
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 &&
|
|
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 &&
|
|
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
|
|
51460
|
-
const
|
|
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-
|
|
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" &&
|
|
51776
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
51777
|
-
/* @__PURE__ */ jsxRuntime.
|
|
51778
|
-
|
|
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("
|
|
51781
|
-
|
|
51782
|
-
|
|
51783
|
-
|
|
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
|
-
|
|
51786
|
-
|
|
51787
|
-
|
|
51788
|
-
|
|
51789
|
-
|
|
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
|
-
|
|
51796
|
-
|
|
51797
|
-
|
|
51798
|
-
|
|
51799
|
-
|
|
51800
|
-
|
|
51801
|
-
|
|
51802
|
-
|
|
51803
|
-
|
|
51804
|
-
|
|
51805
|
-
|
|
51806
|
-
|
|
51807
|
-
|
|
51808
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51830
|
-
|
|
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" &&
|
|
51838
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
51839
|
-
/* @__PURE__ */ jsxRuntime.
|
|
51840
|
-
|
|
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("
|
|
51843
|
-
|
|
51844
|
-
|
|
51845
|
-
|
|
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
|
-
|
|
51848
|
-
|
|
51849
|
-
|
|
51850
|
-
|
|
51851
|
-
|
|
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
|
-
|
|
51858
|
-
|
|
51859
|
-
|
|
51860
|
-
|
|
51861
|
-
|
|
51862
|
-
|
|
51863
|
-
|
|
51864
|
-
|
|
51865
|
-
|
|
51866
|
-
|
|
51867
|
-
|
|
51868
|
-
|
|
51869
|
-
|
|
51870
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51892
|
-
|
|
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({
|
|
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
|
|
52442
|
-
left
|
|
52443
|
-
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
|
|
52975
|
+
top: `${position.top}px`,
|
|
52554
52976
|
left: `${position.left}px`,
|
|
52555
|
-
|
|
52556
|
-
|
|
52557
|
-
|
|
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: "
|
|
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({
|
|
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
|
|
52649
|
-
left
|
|
52650
|
-
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
|
|
53215
|
+
top: `${position.top}px`,
|
|
52761
53216
|
left: `${position.left}px`,
|
|
52762
|
-
|
|
52763
|
-
|
|
52764
|
-
|
|
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: "
|
|
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"
|
|
53433
|
-
|
|
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" ?
|
|
53626
|
-
|
|
53627
|
-
{
|
|
53628
|
-
|
|
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
|
-
|
|
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
|
|
54806
|
-
|
|
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
|
-
|
|
54838
|
-
|
|
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
|
-
|
|
54847
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
58688
|
-
const
|
|
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:
|
|
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 (
|
|
58795
|
-
filteredLines = allLines.filter((line) =>
|
|
58796
|
-
console.log("[KPIsOverviewView]
|
|
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
|
-
|
|
59272
|
+
allowedLineIds: resolvedAssignedLineIds,
|
|
58800
59273
|
filteredLineIds: filteredLines.map((l) => l.id)
|
|
58801
59274
|
});
|
|
58802
59275
|
} else {
|
|
58803
|
-
console.log("[KPIsOverviewView]
|
|
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
|
|
58825
|
-
|
|
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 =
|
|
59302
|
+
const transformed = (data.lines || []).filter((line) => line.enable !== false).map((line) => ({
|
|
58828
59303
|
id: line.id,
|
|
58829
|
-
line_name: line.
|
|
58830
|
-
factory_id: line.
|
|
59304
|
+
line_name: line.line_name,
|
|
59305
|
+
factory_id: line.factory_id || "",
|
|
58831
59306
|
factory_name: "N/A",
|
|
58832
|
-
company_id: line.
|
|
59307
|
+
company_id: line.company_id,
|
|
58833
59308
|
company_name: "",
|
|
58834
|
-
enable: line.
|
|
58835
|
-
monitoring_mode: line.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
61241
|
-
|
|
61242
|
-
|
|
61243
|
-
sonner
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
66020
|
-
const pageDescription = user?.role_level === "
|
|
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
|
|
66029
|
-
if (!
|
|
66030
|
-
|
|
66031
|
-
|
|
66032
|
-
|
|
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:
|
|
66041
|
-
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 (
|
|
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]
|
|
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
|
-
|
|
66143
|
-
|
|
66144
|
-
|
|
66145
|
-
|
|
66146
|
-
|
|
66147
|
-
|
|
66148
|
-
|
|
66149
|
-
|
|
66150
|
-
|
|
66151
|
-
|
|
66152
|
-
|
|
66153
|
-
|
|
66154
|
-
|
|
66155
|
-
|
|
66156
|
-
|
|
66157
|
-
|
|
66158
|
-
|
|
66159
|
-
|
|
66160
|
-
|
|
66161
|
-
|
|
66162
|
-
|
|
66163
|
-
|
|
66164
|
-
|
|
66165
|
-
|
|
66166
|
-
|
|
66167
|
-
|
|
66168
|
-
|
|
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 =
|
|
66181
|
-
const canLoad = hasAccess && user &&
|
|
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,
|
|
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 {
|