@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.mjs
CHANGED
|
@@ -3918,6 +3918,10 @@ var AuthService = class {
|
|
|
3918
3918
|
email: enrichedUser.email,
|
|
3919
3919
|
role: enrichedUser.role,
|
|
3920
3920
|
role_level: enrichedUser.role,
|
|
3921
|
+
scope_mode: enrichedUser.scope_mode,
|
|
3922
|
+
scope_generated_at: enrichedUser.scope_generated_at,
|
|
3923
|
+
scope_ttl_seconds: enrichedUser.scope_ttl_seconds,
|
|
3924
|
+
access_scope: enrichedUser.access_scope,
|
|
3921
3925
|
company_id: enrichedUser.company_id,
|
|
3922
3926
|
first_login_completed: enrichedUser.profile.first_login_completed,
|
|
3923
3927
|
properties: {
|
|
@@ -8124,6 +8128,8 @@ var AuthProvider = ({ children }) => {
|
|
|
8124
8128
|
const isFetchingRef = useRef(false);
|
|
8125
8129
|
const lastProcessedSessionRef = useRef(null);
|
|
8126
8130
|
const hasAuthenticatedRef = useRef(false);
|
|
8131
|
+
const scopeFetchedAtRef = useRef(0);
|
|
8132
|
+
const scopeTtlSecondsRef = useRef(300);
|
|
8127
8133
|
const fetchSession = useCallback(async (supabaseSession, isReauth = false, allowRetry = true) => {
|
|
8128
8134
|
if (isFetchingRef.current) {
|
|
8129
8135
|
console.log("[AuthContext] Already fetching, skipping duplicate request");
|
|
@@ -8136,6 +8142,9 @@ var AuthProvider = ({ children }) => {
|
|
|
8136
8142
|
}
|
|
8137
8143
|
try {
|
|
8138
8144
|
const enrichedUser = await AuthService.getSession(supabaseSession.access_token);
|
|
8145
|
+
const parsedScopeGeneratedAt = enrichedUser.scope_generated_at ? Date.parse(enrichedUser.scope_generated_at) : NaN;
|
|
8146
|
+
scopeFetchedAtRef.current = Number.isFinite(parsedScopeGeneratedAt) ? parsedScopeGeneratedAt : Date.now();
|
|
8147
|
+
scopeTtlSecondsRef.current = enrichedUser.scope_ttl_seconds || 300;
|
|
8139
8148
|
const authUser = AuthService.toAuthUser(enrichedUser);
|
|
8140
8149
|
setUser(authUser);
|
|
8141
8150
|
setSession(supabaseSession);
|
|
@@ -8225,6 +8234,8 @@ var AuthProvider = ({ children }) => {
|
|
|
8225
8234
|
setError(null);
|
|
8226
8235
|
setShowOnboarding(false);
|
|
8227
8236
|
hasAuthenticatedRef.current = false;
|
|
8237
|
+
scopeFetchedAtRef.current = 0;
|
|
8238
|
+
scopeTtlSecondsRef.current = 300;
|
|
8228
8239
|
router.push("/login");
|
|
8229
8240
|
} catch (err) {
|
|
8230
8241
|
console.error("[AuthContext] Sign out error:", err);
|
|
@@ -8287,6 +8298,44 @@ var AuthProvider = ({ children }) => {
|
|
|
8287
8298
|
clearInterval(intervalId);
|
|
8288
8299
|
};
|
|
8289
8300
|
}, [session, supabase]);
|
|
8301
|
+
useEffect(() => {
|
|
8302
|
+
if (!session || !user) {
|
|
8303
|
+
return;
|
|
8304
|
+
}
|
|
8305
|
+
const refreshScopeIfExpired = async () => {
|
|
8306
|
+
if (!session || isFetchingRef.current) {
|
|
8307
|
+
return;
|
|
8308
|
+
}
|
|
8309
|
+
const ttlSeconds = user.scope_ttl_seconds || scopeTtlSecondsRef.current || 300;
|
|
8310
|
+
const fetchedAt = scopeFetchedAtRef.current || 0;
|
|
8311
|
+
const shouldRefresh = fetchedAt === 0 || Date.now() - fetchedAt >= ttlSeconds * 1e3;
|
|
8312
|
+
if (!shouldRefresh) {
|
|
8313
|
+
return;
|
|
8314
|
+
}
|
|
8315
|
+
try {
|
|
8316
|
+
console.log("[AuthContext] Scope TTL expired, refreshing session scope");
|
|
8317
|
+
await fetchSession(session, true);
|
|
8318
|
+
} catch (err) {
|
|
8319
|
+
console.error("[AuthContext] Scope refresh failed:", err);
|
|
8320
|
+
}
|
|
8321
|
+
};
|
|
8322
|
+
refreshScopeIfExpired();
|
|
8323
|
+
const intervalId = setInterval(refreshScopeIfExpired, 6e4);
|
|
8324
|
+
return () => clearInterval(intervalId);
|
|
8325
|
+
}, [session, user?.id, user?.scope_ttl_seconds, fetchSession]);
|
|
8326
|
+
useEffect(() => {
|
|
8327
|
+
if (typeof window === "undefined") {
|
|
8328
|
+
return;
|
|
8329
|
+
}
|
|
8330
|
+
const handleScopeRefresh = () => {
|
|
8331
|
+
if (!session) {
|
|
8332
|
+
return;
|
|
8333
|
+
}
|
|
8334
|
+
void fetchSession(session, true);
|
|
8335
|
+
};
|
|
8336
|
+
window.addEventListener("rbac:refresh-scope", handleScopeRefresh);
|
|
8337
|
+
return () => window.removeEventListener("rbac:refresh-scope", handleScopeRefresh);
|
|
8338
|
+
}, [session, fetchSession]);
|
|
8290
8339
|
useEffect(() => {
|
|
8291
8340
|
if (!supabase) {
|
|
8292
8341
|
console.log("[AuthContext] No Supabase client, skipping auth initialization");
|
|
@@ -8355,7 +8404,8 @@ var AuthProvider = ({ children }) => {
|
|
|
8355
8404
|
} else if (event === "TOKEN_REFRESHED") {
|
|
8356
8405
|
console.log("[AuthContext] Token refreshed automatically");
|
|
8357
8406
|
if (currentSession) {
|
|
8358
|
-
|
|
8407
|
+
const isReauth = hasAuthenticatedRef.current;
|
|
8408
|
+
await fetchSession(currentSession, isReauth);
|
|
8359
8409
|
const expiresAt = currentSession.expires_at;
|
|
8360
8410
|
if (expiresAt) {
|
|
8361
8411
|
const minutesUntilExpiry = Math.floor((expiresAt * 1e3 - Date.now()) / 6e4);
|
|
@@ -10503,12 +10553,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10503
10553
|
const configuredLineIds = useMemo(() => {
|
|
10504
10554
|
return getConfiguredLineIds(entityConfig);
|
|
10505
10555
|
}, [entityConfig]);
|
|
10556
|
+
const targetFactoryLineIds = useMemo(() => {
|
|
10557
|
+
const sourceLineIds = userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
|
|
10558
|
+
return Array.from(new Set((sourceLineIds || []).filter(Boolean)));
|
|
10559
|
+
}, [userAccessibleLineIds, configuredLineIds]);
|
|
10506
10560
|
const { shiftConfig: staticShiftConfig } = useDashboardConfig();
|
|
10507
10561
|
const {
|
|
10508
10562
|
shiftConfigMap: multiLineShiftConfigMap,
|
|
10509
10563
|
isLoading: isMultiLineShiftConfigLoading
|
|
10510
10564
|
} = useMultiLineShiftConfigs(
|
|
10511
|
-
isFactoryView ?
|
|
10565
|
+
isFactoryView ? targetFactoryLineIds : [],
|
|
10512
10566
|
staticShiftConfig
|
|
10513
10567
|
);
|
|
10514
10568
|
const {
|
|
@@ -10603,7 +10657,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10603
10657
|
return;
|
|
10604
10658
|
}
|
|
10605
10659
|
const isFactory = currentLineIdToUse === factoryViewId;
|
|
10606
|
-
const targetLineIds = isFactory ?
|
|
10660
|
+
const targetLineIds = isFactory ? targetFactoryLineIds : [currentLineIdToUse];
|
|
10607
10661
|
const targetLineIdsKey = targetLineIds.slice().sort().join(",");
|
|
10608
10662
|
const usesShiftGroups = isFactory && shiftGroups.length > 0;
|
|
10609
10663
|
const singleShiftDetails = usesShiftGroups ? null : shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(defaultTimezone, staticShiftConfig);
|
|
@@ -10652,7 +10706,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10652
10706
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
10653
10707
|
}
|
|
10654
10708
|
if (targetLineIds.length === 0) {
|
|
10655
|
-
|
|
10709
|
+
logDebug("[useDashboardMetrics] Skipping fetch: no target line IDs after scope filtering");
|
|
10710
|
+
setMetrics({
|
|
10711
|
+
workspaceMetrics: [],
|
|
10712
|
+
lineMetrics: [],
|
|
10713
|
+
metadata: void 0,
|
|
10714
|
+
efficiencyLegend: DEFAULT_EFFICIENCY_LEGEND
|
|
10715
|
+
});
|
|
10716
|
+
setMetricsLineId(requestLineId);
|
|
10717
|
+
lastFetchKeyRef.current = inFlightFetchKeyRef.current;
|
|
10718
|
+
return;
|
|
10656
10719
|
}
|
|
10657
10720
|
let allWorkspaceMetrics = [];
|
|
10658
10721
|
let allLineMetrics = [];
|
|
@@ -10670,11 +10733,16 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10670
10733
|
lineCount: group.lineIds.length
|
|
10671
10734
|
}))
|
|
10672
10735
|
});
|
|
10736
|
+
const targetLineIdSet = new Set(targetLineIds);
|
|
10673
10737
|
const metricsPromises = shiftGroups.map(async (group) => {
|
|
10674
|
-
const
|
|
10738
|
+
const groupLineIds = group.lineIds.filter((lineGroupId) => targetLineIdSet.has(lineGroupId));
|
|
10739
|
+
if (groupLineIds.length === 0) {
|
|
10740
|
+
return null;
|
|
10741
|
+
}
|
|
10742
|
+
const lineIdsParam = `line_ids=${groupLineIds.join(",")}`;
|
|
10675
10743
|
const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${companyId}${forceParam}`;
|
|
10676
10744
|
logDebug(`[useDashboardMetrics] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
|
|
10677
|
-
lineIds:
|
|
10745
|
+
lineIds: groupLineIds,
|
|
10678
10746
|
date: group.date
|
|
10679
10747
|
});
|
|
10680
10748
|
const response = await fetch(url, {
|
|
@@ -10698,7 +10766,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10698
10766
|
throw new Error(`Invalid JSON response from backend`);
|
|
10699
10767
|
}
|
|
10700
10768
|
});
|
|
10701
|
-
const results = await Promise.all(metricsPromises);
|
|
10769
|
+
const results = (await Promise.all(metricsPromises)).filter((result) => !!result);
|
|
10702
10770
|
hasFlowBuffers = results.some((result) => result?.metadata?.has_flow_buffers);
|
|
10703
10771
|
results.forEach((result) => {
|
|
10704
10772
|
const idleMap = result?.metadata?.idle_time_vlm_by_line;
|
|
@@ -10876,6 +10944,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10876
10944
|
shiftGroups,
|
|
10877
10945
|
shiftGroupsKey,
|
|
10878
10946
|
configuredLineIds,
|
|
10947
|
+
targetFactoryLineIds,
|
|
10879
10948
|
isFactoryView,
|
|
10880
10949
|
multiLineShiftConfigMap,
|
|
10881
10950
|
staticShiftConfig,
|
|
@@ -10968,8 +11037,14 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
10968
11037
|
logDebug("[useDashboardMetrics] Setting up group subscriptions:", {
|
|
10969
11038
|
groupCount: currentShiftGroups.length
|
|
10970
11039
|
});
|
|
11040
|
+
const targetFactoryLineIdsForSubscriptions = currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
|
|
11041
|
+
const targetFactoryLineIdSet = new Set(
|
|
11042
|
+
(targetFactoryLineIdsForSubscriptions || []).filter(Boolean)
|
|
11043
|
+
);
|
|
10971
11044
|
currentShiftGroups.forEach((group, index) => {
|
|
10972
|
-
const groupLineIds = group.lineIds.filter(
|
|
11045
|
+
const groupLineIds = group.lineIds.filter(
|
|
11046
|
+
(id3) => id3 && id3 !== factoryViewIdentifier && targetFactoryLineIdSet.has(id3)
|
|
11047
|
+
);
|
|
10973
11048
|
if (groupLineIds.length === 0) {
|
|
10974
11049
|
logDebug("[useDashboardMetrics] Skipping group subscription: no line IDs after filtering", {
|
|
10975
11050
|
groupIndex: index,
|
|
@@ -16148,6 +16223,7 @@ function useAccessControl() {
|
|
|
16148
16223
|
function useTeamManagementPermissions() {
|
|
16149
16224
|
const { user } = useAuth();
|
|
16150
16225
|
const currentRole = user?.role_level;
|
|
16226
|
+
const isSuperAdminOptifye = currentRole === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
|
|
16151
16227
|
return {
|
|
16152
16228
|
/**
|
|
16153
16229
|
* Can the current user assign lines to this user?
|
|
@@ -16203,7 +16279,10 @@ function useTeamManagementPermissions() {
|
|
|
16203
16279
|
availableRolesToAssign: () => {
|
|
16204
16280
|
if (!currentRole) return [];
|
|
16205
16281
|
if (currentRole === "optifye") {
|
|
16206
|
-
|
|
16282
|
+
if (isSuperAdminOptifye) {
|
|
16283
|
+
return ["owner", "it", "plant_head", "supervisor"];
|
|
16284
|
+
}
|
|
16285
|
+
return ["it", "plant_head", "supervisor"];
|
|
16207
16286
|
}
|
|
16208
16287
|
if (currentRole === "owner") {
|
|
16209
16288
|
return ["it", "plant_head", "supervisor"];
|
|
@@ -16221,7 +16300,12 @@ function useTeamManagementPermissions() {
|
|
|
16221
16300
|
*/
|
|
16222
16301
|
canInviteRole: (role) => {
|
|
16223
16302
|
if (!currentRole) return false;
|
|
16224
|
-
if (currentRole === "optifye")
|
|
16303
|
+
if (currentRole === "optifye") {
|
|
16304
|
+
if (role === "owner") {
|
|
16305
|
+
return isSuperAdminOptifye;
|
|
16306
|
+
}
|
|
16307
|
+
return true;
|
|
16308
|
+
}
|
|
16225
16309
|
if (currentRole === "owner") {
|
|
16226
16310
|
return ["it", "plant_head", "supervisor"].includes(role);
|
|
16227
16311
|
}
|
|
@@ -16255,40 +16339,133 @@ function useTeamManagementPermissions() {
|
|
|
16255
16339
|
}
|
|
16256
16340
|
function useUserLineAccess(configLineIds) {
|
|
16257
16341
|
const { user } = useAuth();
|
|
16342
|
+
const { lines: dbLines } = useLines();
|
|
16258
16343
|
const { accessibleLineIds, defaultLineId } = useMemo(() => {
|
|
16344
|
+
const dbLineIds = dbLines.map((line) => line.id);
|
|
16345
|
+
const dbLineIdSet = new Set(dbLineIds);
|
|
16346
|
+
const fallbackAllLineIds = configLineIds && configLineIds.length > 0 ? configLineIds : dbLineIds;
|
|
16347
|
+
const normalizeIds = (value) => {
|
|
16348
|
+
if (Array.isArray(value)) {
|
|
16349
|
+
return value.filter((id3) => typeof id3 === "string" && !!id3);
|
|
16350
|
+
}
|
|
16351
|
+
if (typeof value === "string" && value) {
|
|
16352
|
+
return [value];
|
|
16353
|
+
}
|
|
16354
|
+
return [];
|
|
16355
|
+
};
|
|
16356
|
+
const uniqueIds = (ids) => Array.from(new Set(ids.filter(Boolean)));
|
|
16357
|
+
const filterToEnabledDb = (lineIds, options = {}) => {
|
|
16358
|
+
const deduped = uniqueIds(lineIds);
|
|
16359
|
+
if (dbLineIdSet.size === 0) {
|
|
16360
|
+
return options.allowWhenDbUnavailable ? deduped : [];
|
|
16361
|
+
}
|
|
16362
|
+
return deduped.filter((lineId) => dbLineIdSet.has(lineId));
|
|
16363
|
+
};
|
|
16259
16364
|
if (!user) {
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16365
|
+
const fallback = filterToEnabledDb(fallbackAllLineIds, { allowWhenDbUnavailable: true });
|
|
16366
|
+
return { accessibleLineIds: fallback, defaultLineId: fallback[0] };
|
|
16367
|
+
}
|
|
16368
|
+
const configSet = new Set(configLineIds || []);
|
|
16369
|
+
const hasScopePayload = !!user.access_scope || !!user.scope_mode;
|
|
16370
|
+
const role = user.role_level || user.role;
|
|
16371
|
+
const scopedLineIds = normalizeIds(user.access_scope?.line_ids);
|
|
16372
|
+
const scopedFactoryIds = normalizeIds(user.access_scope?.factory_ids);
|
|
16373
|
+
const properties = user.properties || {};
|
|
16374
|
+
const propertyLineIds = normalizeIds(properties?.line_ids || properties?.line_id);
|
|
16375
|
+
const propertyFactoryIds = normalizeIds(properties?.factory_ids || properties?.factory_id);
|
|
16376
|
+
const propertyCompanyId = typeof properties?.company_id === "string" ? properties.company_id : user.company_id;
|
|
16377
|
+
const scopedCompanyId = user.access_scope?.company_id || propertyCompanyId;
|
|
16378
|
+
const isExplicitSuperAdmin = user.scope_mode === "SUPER_ADMIN" || !!user.access_scope?.is_super_admin;
|
|
16379
|
+
const isLegacyOptifyeGlobal = !hasScopePayload && role === "optifye" && propertyLineIds.length === 0 && propertyFactoryIds.length === 0 && !propertyCompanyId;
|
|
16380
|
+
const lineFactoryMap = /* @__PURE__ */ new Map();
|
|
16381
|
+
dbLines.forEach((line) => {
|
|
16382
|
+
lineFactoryMap.set(line.id, line.factory_id);
|
|
16383
|
+
});
|
|
16384
|
+
const applyConfigFilter = (lineIds) => {
|
|
16385
|
+
const dedupedLineIds = uniqueIds(lineIds);
|
|
16386
|
+
if (!configLineIds || configLineIds.length === 0) {
|
|
16387
|
+
return dedupedLineIds;
|
|
16270
16388
|
}
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16389
|
+
const filtered = dedupedLineIds.filter((id3) => configSet.has(id3));
|
|
16390
|
+
return filtered.length > 0 ? filtered : dedupedLineIds;
|
|
16391
|
+
};
|
|
16392
|
+
const resolveFactoryScopedLines = (factoryIds) => {
|
|
16393
|
+
const factorySet = new Set(factoryIds);
|
|
16394
|
+
const baseLineIds = configLineIds && configLineIds.length > 0 ? configLineIds : dbLineIds;
|
|
16395
|
+
const filtered = baseLineIds.filter((lineId) => {
|
|
16396
|
+
const factoryId = lineFactoryMap.get(lineId);
|
|
16397
|
+
return !!factoryId && factorySet.has(factoryId);
|
|
16275
16398
|
});
|
|
16399
|
+
if (filtered.length > 0) {
|
|
16400
|
+
return uniqueIds(filtered);
|
|
16401
|
+
}
|
|
16402
|
+
const fromDb = dbLines.filter((line) => !!line.factory_id && factorySet.has(line.factory_id)).map((line) => line.id);
|
|
16403
|
+
if (fromDb.length > 0) {
|
|
16404
|
+
return uniqueIds(fromDb);
|
|
16405
|
+
}
|
|
16406
|
+
return [];
|
|
16407
|
+
};
|
|
16408
|
+
if (isExplicitSuperAdmin || isLegacyOptifyeGlobal) {
|
|
16409
|
+
const all = filterToEnabledDb(fallbackAllLineIds, { allowWhenDbUnavailable: true });
|
|
16276
16410
|
return {
|
|
16277
|
-
accessibleLineIds:
|
|
16278
|
-
defaultLineId:
|
|
16411
|
+
accessibleLineIds: all,
|
|
16412
|
+
defaultLineId: all[0]
|
|
16279
16413
|
};
|
|
16280
16414
|
}
|
|
16281
|
-
if (
|
|
16415
|
+
if (scopedLineIds.length > 0) {
|
|
16416
|
+
const filtered = applyConfigFilter(scopedLineIds);
|
|
16417
|
+
const scoped = filterToEnabledDb(filtered.length > 0 ? filtered : uniqueIds(scopedLineIds));
|
|
16282
16418
|
return {
|
|
16283
|
-
accessibleLineIds:
|
|
16284
|
-
defaultLineId:
|
|
16419
|
+
accessibleLineIds: scoped,
|
|
16420
|
+
defaultLineId: scoped[0]
|
|
16285
16421
|
};
|
|
16286
16422
|
}
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16423
|
+
if (scopedFactoryIds.length > 0) {
|
|
16424
|
+
const scoped = filterToEnabledDb(resolveFactoryScopedLines(scopedFactoryIds));
|
|
16425
|
+
return {
|
|
16426
|
+
accessibleLineIds: scoped,
|
|
16427
|
+
defaultLineId: scoped[0]
|
|
16428
|
+
};
|
|
16429
|
+
}
|
|
16430
|
+
if (scopedCompanyId) {
|
|
16431
|
+
const dbCompanyLineIds = dbLines.filter((line) => !line.company_id || line.company_id === scopedCompanyId).map((line) => line.id);
|
|
16432
|
+
const companyScopedLines = configLineIds && configLineIds.length > 0 ? (() => {
|
|
16433
|
+
const dbCompanySet = new Set(dbCompanyLineIds);
|
|
16434
|
+
const filtered = configLineIds.filter((lineId) => dbCompanySet.has(lineId));
|
|
16435
|
+
if (filtered.length > 0) return filtered;
|
|
16436
|
+
return dbCompanyLineIds;
|
|
16437
|
+
})() : dbCompanyLineIds;
|
|
16438
|
+
const scoped = filterToEnabledDb(companyScopedLines);
|
|
16439
|
+
return {
|
|
16440
|
+
accessibleLineIds: scoped,
|
|
16441
|
+
defaultLineId: scoped[0]
|
|
16442
|
+
};
|
|
16443
|
+
}
|
|
16444
|
+
if (propertyLineIds.length > 0) {
|
|
16445
|
+
const filtered = applyConfigFilter(propertyLineIds);
|
|
16446
|
+
const scoped = filterToEnabledDb(filtered);
|
|
16447
|
+
return {
|
|
16448
|
+
accessibleLineIds: scoped,
|
|
16449
|
+
defaultLineId: scoped[0]
|
|
16450
|
+
};
|
|
16451
|
+
}
|
|
16452
|
+
if (propertyFactoryIds.length > 0) {
|
|
16453
|
+
const scoped = filterToEnabledDb(resolveFactoryScopedLines(propertyFactoryIds));
|
|
16454
|
+
return {
|
|
16455
|
+
accessibleLineIds: scoped,
|
|
16456
|
+
defaultLineId: scoped[0]
|
|
16457
|
+
};
|
|
16458
|
+
}
|
|
16459
|
+
if (role === "owner" || role === "it" || role === "optifye") {
|
|
16460
|
+
const companyScopedLines = scopedCompanyId ? dbLines.filter((line) => !line.company_id || line.company_id === scopedCompanyId).map((line) => line.id) : [];
|
|
16461
|
+
const scoped = filterToEnabledDb(companyScopedLines);
|
|
16462
|
+
return {
|
|
16463
|
+
accessibleLineIds: scoped,
|
|
16464
|
+
defaultLineId: scoped[0]
|
|
16465
|
+
};
|
|
16466
|
+
}
|
|
16467
|
+
return { accessibleLineIds: [], defaultLineId: void 0 };
|
|
16468
|
+
}, [user, configLineIds, dbLines]);
|
|
16292
16469
|
return { accessibleLineIds, defaultLineId, user };
|
|
16293
16470
|
}
|
|
16294
16471
|
function useActiveLineId(queryLineId, configLineIds) {
|
|
@@ -16480,12 +16657,7 @@ var useSupervisorsByLineIds = (lineIds, options) => {
|
|
|
16480
16657
|
const nextSupervisorsByLineId = /* @__PURE__ */ new Map();
|
|
16481
16658
|
const nextAllSupervisorsMap = /* @__PURE__ */ new Map();
|
|
16482
16659
|
if (useBackend && resolvedCompanyId) {
|
|
16483
|
-
const searchParams = new URLSearchParams({
|
|
16484
|
-
company_id: resolvedCompanyId
|
|
16485
|
-
});
|
|
16486
|
-
if (lineIdsKey) {
|
|
16487
|
-
searchParams.set("line_ids", lineIdsKey);
|
|
16488
|
-
}
|
|
16660
|
+
const searchParams = new URLSearchParams({ company_id: resolvedCompanyId });
|
|
16489
16661
|
const data = await fetchBackendJson(supabase, `/api/dashboard/line-supervisors?${searchParams.toString()}`);
|
|
16490
16662
|
(data?.supervisors || []).forEach((row) => {
|
|
16491
16663
|
const assignedLineIds = Array.isArray(row.line_ids) ? row.line_ids : [];
|
|
@@ -28305,8 +28477,52 @@ function withAccessControl(WrappedComponent2, options = {}) {
|
|
|
28305
28477
|
const WithAccessControlComponent = (props) => {
|
|
28306
28478
|
const router = useRouter();
|
|
28307
28479
|
const { user, loading: authLoading } = useAuth();
|
|
28308
|
-
const
|
|
28309
|
-
|
|
28480
|
+
const role = user?.role_level;
|
|
28481
|
+
const scope = user?.access_scope;
|
|
28482
|
+
const isSuperAdmin = !!scope?.is_super_admin || user?.scope_mode === "SUPER_ADMIN";
|
|
28483
|
+
const getBasePath = (path) => {
|
|
28484
|
+
const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
|
|
28485
|
+
return firstSegment ? `/${firstSegment}` : "/";
|
|
28486
|
+
};
|
|
28487
|
+
const getIdFromPath = (path, key) => {
|
|
28488
|
+
const queryValue = router.query[key];
|
|
28489
|
+
if (typeof queryValue === "string") return queryValue;
|
|
28490
|
+
if (Array.isArray(queryValue) && queryValue.length > 0) return queryValue[0];
|
|
28491
|
+
const segments = path.split("?")[0].split("/").filter(Boolean);
|
|
28492
|
+
const keyIndex = segments.findIndex((segment) => segment.toLowerCase() === key.toLowerCase());
|
|
28493
|
+
if (keyIndex >= 0 && keyIndex < segments.length - 1) return segments[keyIndex + 1];
|
|
28494
|
+
return null;
|
|
28495
|
+
};
|
|
28496
|
+
const roleAccessMap = {
|
|
28497
|
+
optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
|
|
28498
|
+
owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
|
|
28499
|
+
it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
|
|
28500
|
+
plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/improvement-center", "/tickets"],
|
|
28501
|
+
supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/improvement-center", "/tickets"]
|
|
28502
|
+
};
|
|
28503
|
+
const canAccessPath = (path) => {
|
|
28504
|
+
if (!user || !role) return false;
|
|
28505
|
+
if (isSuperAdmin) return true;
|
|
28506
|
+
const basePath = getBasePath(path);
|
|
28507
|
+
const allowed = roleAccessMap[role] || [];
|
|
28508
|
+
if (!allowed.includes(basePath)) {
|
|
28509
|
+
return false;
|
|
28510
|
+
}
|
|
28511
|
+
const workspaceId = getIdFromPath(path, "workspace");
|
|
28512
|
+
if (workspaceId && scope?.workspace_ids?.length) {
|
|
28513
|
+
return scope.workspace_ids.includes(workspaceId);
|
|
28514
|
+
}
|
|
28515
|
+
const lineId = getIdFromPath(path, "kpis") || getIdFromPath(path, "line");
|
|
28516
|
+
if (lineId && scope?.line_ids?.length) {
|
|
28517
|
+
return scope.line_ids.includes(lineId);
|
|
28518
|
+
}
|
|
28519
|
+
const factoryId = getIdFromPath(path, "factory");
|
|
28520
|
+
if (factoryId && scope?.factory_ids?.length) {
|
|
28521
|
+
return scope.factory_ids.includes(factoryId);
|
|
28522
|
+
}
|
|
28523
|
+
return true;
|
|
28524
|
+
};
|
|
28525
|
+
const pathToCheck = requiredPath || router.asPath || router.pathname;
|
|
28310
28526
|
if (authLoading) {
|
|
28311
28527
|
return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
28312
28528
|
/* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }),
|
|
@@ -28316,6 +28532,24 @@ function withAccessControl(WrappedComponent2, options = {}) {
|
|
|
28316
28532
|
if (!user) {
|
|
28317
28533
|
return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-4", children: "Please log in to continue" }) }) });
|
|
28318
28534
|
}
|
|
28535
|
+
const hasAccess = canAccessPath(pathToCheck);
|
|
28536
|
+
if (!hasAccess) {
|
|
28537
|
+
if (UnauthorizedComponent) {
|
|
28538
|
+
return /* @__PURE__ */ jsx(UnauthorizedComponent, {});
|
|
28539
|
+
}
|
|
28540
|
+
if (typeof window !== "undefined") {
|
|
28541
|
+
router.replace(redirectTo);
|
|
28542
|
+
}
|
|
28543
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
28544
|
+
/* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Access Denied" }),
|
|
28545
|
+
/* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-4", children: "You don't have permission to access this page." }),
|
|
28546
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
|
|
28547
|
+
"Your role: ",
|
|
28548
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: role || "Unknown" })
|
|
28549
|
+
] }),
|
|
28550
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Redirecting to home page..." })
|
|
28551
|
+
] }) });
|
|
28552
|
+
}
|
|
28319
28553
|
return /* @__PURE__ */ jsx(WrappedComponent2, { ...props });
|
|
28320
28554
|
};
|
|
28321
28555
|
WithAccessControlComponent.displayName = `withAccessControl(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
|
|
@@ -48629,9 +48863,29 @@ var SideNavBar = memo$1(({
|
|
|
48629
48863
|
}) => {
|
|
48630
48864
|
const router = useRouter();
|
|
48631
48865
|
const { navigate } = useNavigation();
|
|
48632
|
-
const { signOut } = useAuth();
|
|
48866
|
+
const { signOut, user } = useAuth();
|
|
48633
48867
|
const entityConfig = useEntityConfig();
|
|
48634
48868
|
const dashboardConfig = useDashboardConfig();
|
|
48869
|
+
const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
|
|
48870
|
+
const role = user?.role_level;
|
|
48871
|
+
const roleAccessMap = {
|
|
48872
|
+
optifye: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48873
|
+
owner: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48874
|
+
it: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48875
|
+
plant_head: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/supervisor-management", "/skus", "/help", "/health", "/profile", "/workspace", "/factory-view", "/team-management", "/tickets", "/improvement-center"],
|
|
48876
|
+
supervisor: ["/", "/leaderboard", "/kpis", "/targets", "/shifts", "/skus", "/help", "/health", "/profile", "/workspace", "/tickets", "/improvement-center"]
|
|
48877
|
+
};
|
|
48878
|
+
const getBasePath = useCallback((path) => {
|
|
48879
|
+
const firstSegment = path.split("?")[0].split("/").filter(Boolean)[0];
|
|
48880
|
+
return firstSegment ? `/${firstSegment}` : "/";
|
|
48881
|
+
}, []);
|
|
48882
|
+
const canAccessPath = useCallback((path) => {
|
|
48883
|
+
if (!role) return false;
|
|
48884
|
+
if (isSuperAdmin) return true;
|
|
48885
|
+
const basePath = getBasePath(path);
|
|
48886
|
+
const allowedPaths = roleAccessMap[role] || [];
|
|
48887
|
+
return allowedPaths.includes(basePath);
|
|
48888
|
+
}, [role, isSuperAdmin, getBasePath]);
|
|
48635
48889
|
const lineId = entityConfig.defaultLineId || LINE_1_UUID;
|
|
48636
48890
|
const skuEnabled = dashboardConfig?.skuConfig?.enabled || false;
|
|
48637
48891
|
dashboardConfig?.supervisorConfig?.enabled || false;
|
|
@@ -48797,7 +49051,7 @@ var SideNavBar = memo$1(({
|
|
|
48797
49051
|
const settingsTriggerRef = useRef(null);
|
|
48798
49052
|
const settingsItems = useMemo(() => {
|
|
48799
49053
|
const items = [
|
|
48800
|
-
{
|
|
49054
|
+
...canAccessPath("/targets") ? [{
|
|
48801
49055
|
key: "targets",
|
|
48802
49056
|
label: "Targets",
|
|
48803
49057
|
icon: AdjustmentsHorizontalIcon,
|
|
@@ -48806,8 +49060,8 @@ var SideNavBar = memo$1(({
|
|
|
48806
49060
|
setIsSettingsOpen(false);
|
|
48807
49061
|
},
|
|
48808
49062
|
isActive: pathname === "/targets" || pathname.startsWith("/targets/")
|
|
48809
|
-
},
|
|
48810
|
-
{
|
|
49063
|
+
}] : [],
|
|
49064
|
+
...canAccessPath("/shifts") ? [{
|
|
48811
49065
|
key: "shifts",
|
|
48812
49066
|
label: "Shifts",
|
|
48813
49067
|
icon: ClockIcon,
|
|
@@ -48816,8 +49070,8 @@ var SideNavBar = memo$1(({
|
|
|
48816
49070
|
setIsSettingsOpen(false);
|
|
48817
49071
|
},
|
|
48818
49072
|
isActive: pathname === "/shifts" || pathname.startsWith("/shifts/")
|
|
48819
|
-
},
|
|
48820
|
-
{
|
|
49073
|
+
}] : [],
|
|
49074
|
+
...canAccessPath("/team-management") ? [{
|
|
48821
49075
|
key: "teams",
|
|
48822
49076
|
label: "Teams",
|
|
48823
49077
|
icon: UsersIcon,
|
|
@@ -48826,8 +49080,8 @@ var SideNavBar = memo$1(({
|
|
|
48826
49080
|
setIsSettingsOpen(false);
|
|
48827
49081
|
},
|
|
48828
49082
|
isActive: pathname === "/team-management" || pathname.startsWith("/team-management/")
|
|
48829
|
-
},
|
|
48830
|
-
{
|
|
49083
|
+
}] : [],
|
|
49084
|
+
...canAccessPath("/profile") ? [{
|
|
48831
49085
|
key: "profile",
|
|
48832
49086
|
label: "Profile",
|
|
48833
49087
|
icon: UserCircleIcon,
|
|
@@ -48836,9 +49090,9 @@ var SideNavBar = memo$1(({
|
|
|
48836
49090
|
setIsSettingsOpen(false);
|
|
48837
49091
|
},
|
|
48838
49092
|
isActive: pathname === "/profile" || pathname.startsWith("/profile/")
|
|
48839
|
-
}
|
|
49093
|
+
}] : []
|
|
48840
49094
|
];
|
|
48841
|
-
if (ticketsEnabled) {
|
|
49095
|
+
if (ticketsEnabled && canAccessPath("/tickets")) {
|
|
48842
49096
|
items.push({
|
|
48843
49097
|
key: "tickets",
|
|
48844
49098
|
label: "Tickets",
|
|
@@ -48850,18 +49104,20 @@ var SideNavBar = memo$1(({
|
|
|
48850
49104
|
isActive: pathname === "/tickets" || pathname.startsWith("/tickets/")
|
|
48851
49105
|
});
|
|
48852
49106
|
}
|
|
48853
|
-
|
|
48854
|
-
|
|
48855
|
-
|
|
48856
|
-
|
|
48857
|
-
|
|
48858
|
-
|
|
48859
|
-
|
|
48860
|
-
|
|
48861
|
-
|
|
48862
|
-
|
|
49107
|
+
if (canAccessPath("/help")) {
|
|
49108
|
+
items.push({
|
|
49109
|
+
key: "help",
|
|
49110
|
+
label: "Help",
|
|
49111
|
+
icon: QuestionMarkCircleIcon,
|
|
49112
|
+
onClick: () => {
|
|
49113
|
+
handleHelpClick();
|
|
49114
|
+
setIsSettingsOpen(false);
|
|
49115
|
+
},
|
|
49116
|
+
isActive: pathname === "/help" || pathname.startsWith("/help/")
|
|
49117
|
+
});
|
|
49118
|
+
}
|
|
48863
49119
|
return items;
|
|
48864
|
-
}, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled]);
|
|
49120
|
+
}, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled, canAccessPath]);
|
|
48865
49121
|
const handleLogout = useCallback(async () => {
|
|
48866
49122
|
setIsSettingsOpen(false);
|
|
48867
49123
|
try {
|
|
@@ -48899,7 +49155,7 @@ var SideNavBar = memo$1(({
|
|
|
48899
49155
|
}
|
|
48900
49156
|
) }),
|
|
48901
49157
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 w-full py-6 px-4 overflow-y-auto", children: [
|
|
48902
|
-
/* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsxs(
|
|
49158
|
+
/* @__PURE__ */ jsx("div", { className: "mb-6", children: canAccessPath("/") && /* @__PURE__ */ jsxs(
|
|
48903
49159
|
"button",
|
|
48904
49160
|
{
|
|
48905
49161
|
onClick: handleHomeClick,
|
|
@@ -48915,7 +49171,7 @@ var SideNavBar = memo$1(({
|
|
|
48915
49171
|
}
|
|
48916
49172
|
) }),
|
|
48917
49173
|
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
48918
|
-
/* @__PURE__ */ jsxs(
|
|
49174
|
+
canAccessPath("/leaderboard") && /* @__PURE__ */ jsxs(
|
|
48919
49175
|
"button",
|
|
48920
49176
|
{
|
|
48921
49177
|
onClick: handleLeaderboardClick,
|
|
@@ -48930,7 +49186,7 @@ var SideNavBar = memo$1(({
|
|
|
48930
49186
|
]
|
|
48931
49187
|
}
|
|
48932
49188
|
),
|
|
48933
|
-
/* @__PURE__ */ jsxs(
|
|
49189
|
+
canAccessPath("/kpis") && /* @__PURE__ */ jsxs(
|
|
48934
49190
|
"button",
|
|
48935
49191
|
{
|
|
48936
49192
|
onClick: handleKPIsClick,
|
|
@@ -48945,7 +49201,7 @@ var SideNavBar = memo$1(({
|
|
|
48945
49201
|
]
|
|
48946
49202
|
}
|
|
48947
49203
|
),
|
|
48948
|
-
/* @__PURE__ */ jsxs(
|
|
49204
|
+
canAccessPath("/improvement-center") && /* @__PURE__ */ jsxs(
|
|
48949
49205
|
"button",
|
|
48950
49206
|
{
|
|
48951
49207
|
onClick: handleImprovementClick,
|
|
@@ -48961,7 +49217,7 @@ var SideNavBar = memo$1(({
|
|
|
48961
49217
|
}
|
|
48962
49218
|
),
|
|
48963
49219
|
showSupervisorManagement,
|
|
48964
|
-
skuEnabled &&
|
|
49220
|
+
skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxs(
|
|
48965
49221
|
"button",
|
|
48966
49222
|
{
|
|
48967
49223
|
onClick: handleSKUsClick,
|
|
@@ -48976,7 +49232,7 @@ var SideNavBar = memo$1(({
|
|
|
48976
49232
|
]
|
|
48977
49233
|
}
|
|
48978
49234
|
),
|
|
48979
|
-
/* @__PURE__ */ jsxs(
|
|
49235
|
+
canAccessPath("/health") && /* @__PURE__ */ jsxs(
|
|
48980
49236
|
"button",
|
|
48981
49237
|
{
|
|
48982
49238
|
onClick: handleHealthClick,
|
|
@@ -48993,7 +49249,7 @@ var SideNavBar = memo$1(({
|
|
|
48993
49249
|
)
|
|
48994
49250
|
] })
|
|
48995
49251
|
] }),
|
|
48996
|
-
/* @__PURE__ */ jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxs(
|
|
49252
|
+
settingsItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "w-full py-5 px-4 border-t border-gray-100 flex-shrink-0", children: /* @__PURE__ */ jsxs(
|
|
48997
49253
|
"button",
|
|
48998
49254
|
{
|
|
48999
49255
|
ref: settingsTriggerRef,
|
|
@@ -49031,7 +49287,7 @@ var SideNavBar = memo$1(({
|
|
|
49031
49287
|
};
|
|
49032
49288
|
};
|
|
49033
49289
|
return /* @__PURE__ */ jsxs("nav", { className: "px-5 py-6", children: [
|
|
49034
|
-
/* @__PURE__ */ jsxs(
|
|
49290
|
+
canAccessPath("/") && /* @__PURE__ */ jsxs(
|
|
49035
49291
|
"button",
|
|
49036
49292
|
{
|
|
49037
49293
|
onClick: handleMobileNavClick(handleHomeClick),
|
|
@@ -49044,7 +49300,7 @@ var SideNavBar = memo$1(({
|
|
|
49044
49300
|
}
|
|
49045
49301
|
),
|
|
49046
49302
|
/* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
|
|
49047
|
-
/* @__PURE__ */ jsxs(
|
|
49303
|
+
canAccessPath("/leaderboard") && /* @__PURE__ */ jsxs(
|
|
49048
49304
|
"button",
|
|
49049
49305
|
{
|
|
49050
49306
|
onClick: handleMobileNavClick(handleLeaderboardClick),
|
|
@@ -49056,7 +49312,7 @@ var SideNavBar = memo$1(({
|
|
|
49056
49312
|
]
|
|
49057
49313
|
}
|
|
49058
49314
|
),
|
|
49059
|
-
/* @__PURE__ */ jsxs(
|
|
49315
|
+
canAccessPath("/kpis") && /* @__PURE__ */ jsxs(
|
|
49060
49316
|
"button",
|
|
49061
49317
|
{
|
|
49062
49318
|
onClick: handleMobileNavClick(handleKPIsClick),
|
|
@@ -49068,7 +49324,7 @@ var SideNavBar = memo$1(({
|
|
|
49068
49324
|
]
|
|
49069
49325
|
}
|
|
49070
49326
|
),
|
|
49071
|
-
/* @__PURE__ */ jsxs(
|
|
49327
|
+
canAccessPath("/improvement-center") && /* @__PURE__ */ jsxs(
|
|
49072
49328
|
"button",
|
|
49073
49329
|
{
|
|
49074
49330
|
onClick: handleMobileNavClick(handleImprovementClick),
|
|
@@ -49081,7 +49337,7 @@ var SideNavBar = memo$1(({
|
|
|
49081
49337
|
}
|
|
49082
49338
|
),
|
|
49083
49339
|
showSupervisorManagement,
|
|
49084
|
-
skuEnabled &&
|
|
49340
|
+
skuEnabled && canAccessPath("/skus") && /* @__PURE__ */ jsxs(
|
|
49085
49341
|
"button",
|
|
49086
49342
|
{
|
|
49087
49343
|
onClick: handleMobileNavClick(handleSKUsClick),
|
|
@@ -49093,7 +49349,7 @@ var SideNavBar = memo$1(({
|
|
|
49093
49349
|
]
|
|
49094
49350
|
}
|
|
49095
49351
|
),
|
|
49096
|
-
/* @__PURE__ */ jsxs(
|
|
49352
|
+
canAccessPath("/health") && /* @__PURE__ */ jsxs(
|
|
49097
49353
|
"button",
|
|
49098
49354
|
{
|
|
49099
49355
|
onClick: handleMobileNavClick(handleHealthClick),
|
|
@@ -49106,10 +49362,10 @@ var SideNavBar = memo$1(({
|
|
|
49106
49362
|
}
|
|
49107
49363
|
)
|
|
49108
49364
|
] }),
|
|
49109
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
|
|
49365
|
+
settingsItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100", children: [
|
|
49110
49366
|
/* @__PURE__ */ jsx("h3", { className: "px-5 mb-3 text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Settings & Support" }),
|
|
49111
49367
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
49112
|
-
/* @__PURE__ */ jsxs(
|
|
49368
|
+
canAccessPath("/targets") && /* @__PURE__ */ jsxs(
|
|
49113
49369
|
"button",
|
|
49114
49370
|
{
|
|
49115
49371
|
onClick: handleMobileNavClick(handleTargetsClick),
|
|
@@ -49121,7 +49377,7 @@ var SideNavBar = memo$1(({
|
|
|
49121
49377
|
]
|
|
49122
49378
|
}
|
|
49123
49379
|
),
|
|
49124
|
-
/* @__PURE__ */ jsxs(
|
|
49380
|
+
canAccessPath("/shifts") && /* @__PURE__ */ jsxs(
|
|
49125
49381
|
"button",
|
|
49126
49382
|
{
|
|
49127
49383
|
onClick: handleMobileNavClick(handleShiftsClick),
|
|
@@ -49133,7 +49389,7 @@ var SideNavBar = memo$1(({
|
|
|
49133
49389
|
]
|
|
49134
49390
|
}
|
|
49135
49391
|
),
|
|
49136
|
-
/* @__PURE__ */ jsxs(
|
|
49392
|
+
canAccessPath("/team-management") && /* @__PURE__ */ jsxs(
|
|
49137
49393
|
"button",
|
|
49138
49394
|
{
|
|
49139
49395
|
onClick: handleMobileNavClick(handleTeamManagementClick),
|
|
@@ -49145,7 +49401,7 @@ var SideNavBar = memo$1(({
|
|
|
49145
49401
|
]
|
|
49146
49402
|
}
|
|
49147
49403
|
),
|
|
49148
|
-
/* @__PURE__ */ jsxs(
|
|
49404
|
+
canAccessPath("/profile") && /* @__PURE__ */ jsxs(
|
|
49149
49405
|
"button",
|
|
49150
49406
|
{
|
|
49151
49407
|
onClick: handleMobileNavClick(handleProfileClick),
|
|
@@ -49157,7 +49413,7 @@ var SideNavBar = memo$1(({
|
|
|
49157
49413
|
]
|
|
49158
49414
|
}
|
|
49159
49415
|
),
|
|
49160
|
-
ticketsEnabled && /* @__PURE__ */ jsxs(
|
|
49416
|
+
ticketsEnabled && canAccessPath("/tickets") && /* @__PURE__ */ jsxs(
|
|
49161
49417
|
"button",
|
|
49162
49418
|
{
|
|
49163
49419
|
onClick: handleMobileNavClick(handleTicketsClick),
|
|
@@ -49169,7 +49425,7 @@ var SideNavBar = memo$1(({
|
|
|
49169
49425
|
]
|
|
49170
49426
|
}
|
|
49171
49427
|
),
|
|
49172
|
-
/* @__PURE__ */ jsxs(
|
|
49428
|
+
canAccessPath("/help") && /* @__PURE__ */ jsxs(
|
|
49173
49429
|
"button",
|
|
49174
49430
|
{
|
|
49175
49431
|
onClick: handleMobileNavClick(handleHelpClick),
|
|
@@ -51425,13 +51681,41 @@ var InviteUserDialog = ({
|
|
|
51425
51681
|
const [selectedRole, setSelectedRole] = useState("supervisor");
|
|
51426
51682
|
const [selectedLines, setSelectedLines] = useState([]);
|
|
51427
51683
|
const [selectedFactories, setSelectedFactories] = useState([]);
|
|
51684
|
+
const [lineSearch, setLineSearch] = useState("");
|
|
51685
|
+
const [factorySearch, setFactorySearch] = useState("");
|
|
51428
51686
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
51429
51687
|
const [error, setError] = useState(null);
|
|
51430
|
-
const
|
|
51431
|
-
const
|
|
51688
|
+
const isSuperAdminOptifye = user?.role_level === "optifye" && (user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin);
|
|
51689
|
+
const canInviteOwner = isSuperAdminOptifye;
|
|
51432
51690
|
const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
|
|
51433
51691
|
const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
|
|
51434
51692
|
const canInviteSupervisor = ["owner", "it", "plant_head", "optifye"].includes(user?.role_level || "");
|
|
51693
|
+
const invitableRoles = useMemo(() => {
|
|
51694
|
+
const roles = [];
|
|
51695
|
+
if (canInviteOwner) roles.push("owner");
|
|
51696
|
+
if (canInviteIT) roles.push("it");
|
|
51697
|
+
if (canInvitePlantHead) roles.push("plant_head");
|
|
51698
|
+
if (canInviteSupervisor) roles.push("supervisor");
|
|
51699
|
+
return roles;
|
|
51700
|
+
}, [canInviteOwner, canInviteIT, canInvitePlantHead, canInviteSupervisor]);
|
|
51701
|
+
const filteredLines = useMemo(() => {
|
|
51702
|
+
const search = lineSearch.trim().toLowerCase();
|
|
51703
|
+
if (!search) return availableLines;
|
|
51704
|
+
return availableLines.filter((line) => line.name.toLowerCase().includes(search));
|
|
51705
|
+
}, [availableLines, lineSearch]);
|
|
51706
|
+
const filteredFactories = useMemo(() => {
|
|
51707
|
+
const search = factorySearch.trim().toLowerCase();
|
|
51708
|
+
if (!search) return availableFactories;
|
|
51709
|
+
return availableFactories.filter((factory) => factory.name.toLowerCase().includes(search));
|
|
51710
|
+
}, [availableFactories, factorySearch]);
|
|
51711
|
+
const selectedLineItems = useMemo(
|
|
51712
|
+
() => availableLines.filter((line) => selectedLines.includes(line.id)),
|
|
51713
|
+
[availableLines, selectedLines]
|
|
51714
|
+
);
|
|
51715
|
+
const selectedFactoryItems = useMemo(
|
|
51716
|
+
() => availableFactories.filter((factory) => selectedFactories.includes(factory.id)),
|
|
51717
|
+
[availableFactories, selectedFactories]
|
|
51718
|
+
);
|
|
51435
51719
|
useEffect(() => {
|
|
51436
51720
|
if (!isOpen) {
|
|
51437
51721
|
setEmail("");
|
|
@@ -51440,11 +51724,19 @@ var InviteUserDialog = ({
|
|
|
51440
51724
|
setSelectedRole("supervisor");
|
|
51441
51725
|
setSelectedLines([]);
|
|
51442
51726
|
setSelectedFactories([]);
|
|
51727
|
+
setLineSearch("");
|
|
51728
|
+
setFactorySearch("");
|
|
51443
51729
|
setError(null);
|
|
51444
|
-
setIsLinesDropdownOpen(false);
|
|
51445
|
-
setIsFactoriesDropdownOpen(false);
|
|
51446
51730
|
}
|
|
51447
51731
|
}, [isOpen]);
|
|
51732
|
+
useEffect(() => {
|
|
51733
|
+
if (!isOpen || invitableRoles.length === 0) {
|
|
51734
|
+
return;
|
|
51735
|
+
}
|
|
51736
|
+
if (!invitableRoles.includes(selectedRole)) {
|
|
51737
|
+
setSelectedRole(invitableRoles[0]);
|
|
51738
|
+
}
|
|
51739
|
+
}, [isOpen, invitableRoles, selectedRole]);
|
|
51448
51740
|
const validateEmail = (email2) => {
|
|
51449
51741
|
const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$/;
|
|
51450
51742
|
return emailRegex.test(email2);
|
|
@@ -51474,6 +51766,14 @@ var InviteUserDialog = ({
|
|
|
51474
51766
|
setError("Please enter a valid email address");
|
|
51475
51767
|
return;
|
|
51476
51768
|
}
|
|
51769
|
+
if (!invitableRoles.includes(selectedRole)) {
|
|
51770
|
+
setError("You do not have permission to assign this role.");
|
|
51771
|
+
return;
|
|
51772
|
+
}
|
|
51773
|
+
if (selectedRole === "owner" && !canInviteOwner) {
|
|
51774
|
+
setError("Only super-admin Optifye users can create owner users.");
|
|
51775
|
+
return;
|
|
51776
|
+
}
|
|
51477
51777
|
const companyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
51478
51778
|
if (!companyId) {
|
|
51479
51779
|
setError("Company ID not found. Please refresh and try again.");
|
|
@@ -51511,7 +51811,6 @@ var InviteUserDialog = ({
|
|
|
51511
51811
|
try {
|
|
51512
51812
|
const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
|
|
51513
51813
|
if (dashboardUrl) {
|
|
51514
|
-
console.log("Sending welcome email to:", email.trim());
|
|
51515
51814
|
const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
|
|
51516
51815
|
body: {
|
|
51517
51816
|
action: "send-welcome-email",
|
|
@@ -51524,13 +51823,8 @@ var InviteUserDialog = ({
|
|
|
51524
51823
|
console.error("Failed to send welcome email:", emailError);
|
|
51525
51824
|
toast.warning("User added successfully, but welcome email could not be sent");
|
|
51526
51825
|
} else if (emailData?.success) {
|
|
51527
|
-
console.log("Welcome email sent successfully:", emailData);
|
|
51528
51826
|
toast.success("User added and welcome email sent!");
|
|
51529
|
-
} else {
|
|
51530
|
-
console.log("Welcome email response:", emailData);
|
|
51531
51827
|
}
|
|
51532
|
-
} else {
|
|
51533
|
-
console.warn("Dashboard URL not available, skipping welcome email");
|
|
51534
51828
|
}
|
|
51535
51829
|
} catch (emailErr) {
|
|
51536
51830
|
console.error("Error sending welcome email:", emailErr);
|
|
@@ -51556,7 +51850,7 @@ var InviteUserDialog = ({
|
|
|
51556
51850
|
children: /* @__PURE__ */ jsxs(
|
|
51557
51851
|
"div",
|
|
51558
51852
|
{
|
|
51559
|
-
className: "bg-white rounded-xl shadow-2xl max-w-
|
|
51853
|
+
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",
|
|
51560
51854
|
onClick: (e) => e.stopPropagation(),
|
|
51561
51855
|
children: [
|
|
51562
51856
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
|
|
@@ -51584,7 +51878,7 @@ var InviteUserDialog = ({
|
|
|
51584
51878
|
/* @__PURE__ */ jsx("span", { children: error })
|
|
51585
51879
|
] }),
|
|
51586
51880
|
/* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
51587
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
51881
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [
|
|
51588
51882
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
51589
51883
|
/* @__PURE__ */ jsxs("label", { htmlFor: "firstName", className: "block text-sm font-medium text-gray-700 mb-2", children: [
|
|
51590
51884
|
"First Name ",
|
|
@@ -51660,6 +51954,33 @@ var InviteUserDialog = ({
|
|
|
51660
51954
|
/* @__PURE__ */ jsx("span", { className: "text-red-500", children: "*" })
|
|
51661
51955
|
] }),
|
|
51662
51956
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
51957
|
+
canInviteOwner && /* @__PURE__ */ jsxs(
|
|
51958
|
+
"label",
|
|
51959
|
+
{
|
|
51960
|
+
className: cn(
|
|
51961
|
+
"flex items-start gap-3 p-4 border-2 rounded-lg cursor-pointer transition-all duration-200",
|
|
51962
|
+
selectedRole === "owner" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"
|
|
51963
|
+
),
|
|
51964
|
+
children: [
|
|
51965
|
+
/* @__PURE__ */ jsx(
|
|
51966
|
+
"input",
|
|
51967
|
+
{
|
|
51968
|
+
type: "radio",
|
|
51969
|
+
name: "role",
|
|
51970
|
+
value: "owner",
|
|
51971
|
+
checked: selectedRole === "owner",
|
|
51972
|
+
onChange: (e) => setSelectedRole(e.target.value),
|
|
51973
|
+
className: "mt-1",
|
|
51974
|
+
disabled: isSubmitting
|
|
51975
|
+
}
|
|
51976
|
+
),
|
|
51977
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
51978
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 mb-1", children: /* @__PURE__ */ jsx(RoleBadge_default, { role: "owner", size: "sm" }) }),
|
|
51979
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "Company owner access. Full management permissions for the selected company dashboard." })
|
|
51980
|
+
] })
|
|
51981
|
+
]
|
|
51982
|
+
}
|
|
51983
|
+
),
|
|
51663
51984
|
canInviteIT && /* @__PURE__ */ jsxs(
|
|
51664
51985
|
"label",
|
|
51665
51986
|
{
|
|
@@ -51743,124 +52064,192 @@ var InviteUserDialog = ({
|
|
|
51743
52064
|
)
|
|
51744
52065
|
] })
|
|
51745
52066
|
] }),
|
|
51746
|
-
selectedRole === "plant_head" &&
|
|
51747
|
-
/* @__PURE__ */ jsxs("
|
|
51748
|
-
/* @__PURE__ */
|
|
51749
|
-
|
|
52067
|
+
selectedRole === "plant_head" && /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
|
|
52068
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
52069
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
52070
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
|
|
52071
|
+
/* @__PURE__ */ jsx(Building2, { className: "w-4 h-4 text-blue-600" }),
|
|
52072
|
+
"Factory Access"
|
|
52073
|
+
] }),
|
|
52074
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the factories this plant head will manage." })
|
|
52075
|
+
] }),
|
|
52076
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
|
|
52077
|
+
selectedFactories.length,
|
|
52078
|
+
" selected"
|
|
52079
|
+
] })
|
|
51750
52080
|
] }),
|
|
51751
|
-
/* @__PURE__ */ jsx("
|
|
51752
|
-
|
|
51753
|
-
|
|
51754
|
-
|
|
52081
|
+
availableFactories.length === 0 ? /* @__PURE__ */ 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__ */ jsxs(Fragment, { children: [
|
|
52082
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
52083
|
+
/* @__PURE__ */ jsx(
|
|
52084
|
+
"button",
|
|
52085
|
+
{
|
|
52086
|
+
type: "button",
|
|
52087
|
+
onClick: () => setSelectedFactories(availableFactories.map((factory) => factory.id)),
|
|
52088
|
+
className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
|
|
52089
|
+
disabled: isSubmitting,
|
|
52090
|
+
children: "Select all"
|
|
52091
|
+
}
|
|
52092
|
+
),
|
|
52093
|
+
/* @__PURE__ */ jsx(
|
|
52094
|
+
"button",
|
|
52095
|
+
{
|
|
52096
|
+
type: "button",
|
|
52097
|
+
onClick: () => setSelectedFactories([]),
|
|
52098
|
+
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",
|
|
52099
|
+
disabled: isSubmitting || selectedFactories.length === 0,
|
|
52100
|
+
children: "Clear"
|
|
52101
|
+
}
|
|
52102
|
+
)
|
|
52103
|
+
] }),
|
|
52104
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
52105
|
+
/* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
|
|
52106
|
+
/* @__PURE__ */ jsx(
|
|
52107
|
+
"input",
|
|
52108
|
+
{
|
|
52109
|
+
type: "text",
|
|
52110
|
+
value: factorySearch,
|
|
52111
|
+
onChange: (e) => setFactorySearch(e.target.value),
|
|
52112
|
+
placeholder: "Search factories",
|
|
52113
|
+
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",
|
|
52114
|
+
disabled: isSubmitting
|
|
52115
|
+
}
|
|
52116
|
+
)
|
|
52117
|
+
] }),
|
|
52118
|
+
/* @__PURE__ */ 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__ */ jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No factories match your search." }) : filteredFactories.map((factory) => /* @__PURE__ */ jsxs(
|
|
52119
|
+
"label",
|
|
51755
52120
|
{
|
|
51756
|
-
|
|
51757
|
-
|
|
51758
|
-
|
|
51759
|
-
|
|
51760
|
-
|
|
51761
|
-
selectedFactories.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Select factories..." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedFactories.map((factoryId) => {
|
|
51762
|
-
const factory = availableFactories.find((f) => f.id === factoryId);
|
|
51763
|
-
return factory ? /* @__PURE__ */ jsxs(
|
|
51764
|
-
"span",
|
|
52121
|
+
className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
|
|
52122
|
+
children: [
|
|
52123
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
52124
|
+
/* @__PURE__ */ jsx(
|
|
52125
|
+
"input",
|
|
51765
52126
|
{
|
|
51766
|
-
|
|
51767
|
-
|
|
51768
|
-
|
|
51769
|
-
|
|
51770
|
-
|
|
51771
|
-
|
|
51772
|
-
|
|
51773
|
-
|
|
51774
|
-
|
|
51775
|
-
|
|
51776
|
-
|
|
51777
|
-
|
|
51778
|
-
|
|
51779
|
-
|
|
51780
|
-
|
|
51781
|
-
|
|
51782
|
-
]
|
|
51783
|
-
},
|
|
51784
|
-
factory.id
|
|
51785
|
-
) : null;
|
|
51786
|
-
}) }),
|
|
51787
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isFactoriesDropdownOpen && "rotate-180") })
|
|
51788
|
-
] })
|
|
51789
|
-
}
|
|
51790
|
-
),
|
|
51791
|
-
isFactoriesDropdownOpen && /* @__PURE__ */ 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__ */ jsxs(
|
|
51792
|
-
"div",
|
|
52127
|
+
type: "checkbox",
|
|
52128
|
+
checked: selectedFactories.includes(factory.id),
|
|
52129
|
+
onChange: () => toggleFactorySelection(factory.id),
|
|
52130
|
+
className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
|
|
52131
|
+
disabled: isSubmitting
|
|
52132
|
+
}
|
|
52133
|
+
),
|
|
52134
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-gray-800 truncate", children: factory.name })
|
|
52135
|
+
] }),
|
|
52136
|
+
selectedFactories.includes(factory.id) && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
|
|
52137
|
+
]
|
|
52138
|
+
},
|
|
52139
|
+
factory.id
|
|
52140
|
+
)) }),
|
|
52141
|
+
selectedFactoryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedFactoryItems.map((factory) => /* @__PURE__ */ jsxs(
|
|
52142
|
+
"span",
|
|
51793
52143
|
{
|
|
51794
|
-
|
|
51795
|
-
className: cn(
|
|
51796
|
-
"px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
|
|
51797
|
-
selectedFactories.includes(factory.id) && "bg-blue-50"
|
|
51798
|
-
),
|
|
52144
|
+
className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
|
|
51799
52145
|
children: [
|
|
51800
|
-
|
|
51801
|
-
|
|
52146
|
+
factory.name,
|
|
52147
|
+
/* @__PURE__ */ jsx(
|
|
52148
|
+
"button",
|
|
52149
|
+
{
|
|
52150
|
+
type: "button",
|
|
52151
|
+
onClick: () => toggleFactorySelection(factory.id),
|
|
52152
|
+
className: "hover:bg-blue-200 rounded-sm transition-colors",
|
|
52153
|
+
disabled: isSubmitting,
|
|
52154
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
|
|
52155
|
+
}
|
|
52156
|
+
)
|
|
51802
52157
|
]
|
|
51803
52158
|
},
|
|
51804
52159
|
factory.id
|
|
51805
52160
|
)) })
|
|
51806
52161
|
] })
|
|
51807
52162
|
] }),
|
|
51808
|
-
selectedRole === "supervisor" &&
|
|
51809
|
-
/* @__PURE__ */ jsxs("
|
|
51810
|
-
/* @__PURE__ */
|
|
51811
|
-
|
|
52163
|
+
selectedRole === "supervisor" && /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
|
|
52164
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
52165
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
52166
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
|
|
52167
|
+
/* @__PURE__ */ jsx(Users, { className: "w-4 h-4 text-blue-600" }),
|
|
52168
|
+
"Line Access"
|
|
52169
|
+
] }),
|
|
52170
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the lines this supervisor will monitor." })
|
|
52171
|
+
] }),
|
|
52172
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
|
|
52173
|
+
selectedLines.length,
|
|
52174
|
+
" selected"
|
|
52175
|
+
] })
|
|
51812
52176
|
] }),
|
|
51813
|
-
/* @__PURE__ */ jsx("
|
|
51814
|
-
|
|
51815
|
-
|
|
51816
|
-
|
|
52177
|
+
availableLines.length === 0 ? /* @__PURE__ */ 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__ */ jsxs(Fragment, { children: [
|
|
52178
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
52179
|
+
/* @__PURE__ */ jsx(
|
|
52180
|
+
"button",
|
|
52181
|
+
{
|
|
52182
|
+
type: "button",
|
|
52183
|
+
onClick: () => setSelectedLines(availableLines.map((line) => line.id)),
|
|
52184
|
+
className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
|
|
52185
|
+
disabled: isSubmitting,
|
|
52186
|
+
children: "Select all"
|
|
52187
|
+
}
|
|
52188
|
+
),
|
|
52189
|
+
/* @__PURE__ */ jsx(
|
|
52190
|
+
"button",
|
|
52191
|
+
{
|
|
52192
|
+
type: "button",
|
|
52193
|
+
onClick: () => setSelectedLines([]),
|
|
52194
|
+
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",
|
|
52195
|
+
disabled: isSubmitting || selectedLines.length === 0,
|
|
52196
|
+
children: "Clear"
|
|
52197
|
+
}
|
|
52198
|
+
)
|
|
52199
|
+
] }),
|
|
52200
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
52201
|
+
/* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
|
|
52202
|
+
/* @__PURE__ */ jsx(
|
|
52203
|
+
"input",
|
|
52204
|
+
{
|
|
52205
|
+
type: "text",
|
|
52206
|
+
value: lineSearch,
|
|
52207
|
+
onChange: (e) => setLineSearch(e.target.value),
|
|
52208
|
+
placeholder: "Search lines",
|
|
52209
|
+
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",
|
|
52210
|
+
disabled: isSubmitting
|
|
52211
|
+
}
|
|
52212
|
+
)
|
|
52213
|
+
] }),
|
|
52214
|
+
/* @__PURE__ */ 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__ */ jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No lines match your search." }) : filteredLines.map((line) => /* @__PURE__ */ jsxs(
|
|
52215
|
+
"label",
|
|
51817
52216
|
{
|
|
51818
|
-
|
|
51819
|
-
|
|
51820
|
-
|
|
51821
|
-
|
|
51822
|
-
|
|
51823
|
-
selectedLines.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Select lines..." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedLines.map((lineId) => {
|
|
51824
|
-
const line = availableLines.find((l) => l.id === lineId);
|
|
51825
|
-
return line ? /* @__PURE__ */ jsxs(
|
|
51826
|
-
"span",
|
|
52217
|
+
className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
|
|
52218
|
+
children: [
|
|
52219
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
52220
|
+
/* @__PURE__ */ jsx(
|
|
52221
|
+
"input",
|
|
51827
52222
|
{
|
|
51828
|
-
|
|
51829
|
-
|
|
51830
|
-
|
|
51831
|
-
|
|
51832
|
-
|
|
51833
|
-
|
|
51834
|
-
|
|
51835
|
-
|
|
51836
|
-
|
|
51837
|
-
|
|
51838
|
-
|
|
51839
|
-
|
|
51840
|
-
|
|
51841
|
-
|
|
51842
|
-
|
|
51843
|
-
|
|
51844
|
-
]
|
|
51845
|
-
},
|
|
51846
|
-
line.id
|
|
51847
|
-
) : null;
|
|
51848
|
-
}) }),
|
|
51849
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isLinesDropdownOpen && "rotate-180") })
|
|
51850
|
-
] })
|
|
51851
|
-
}
|
|
51852
|
-
),
|
|
51853
|
-
isLinesDropdownOpen && /* @__PURE__ */ 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__ */ jsxs(
|
|
51854
|
-
"div",
|
|
52223
|
+
type: "checkbox",
|
|
52224
|
+
checked: selectedLines.includes(line.id),
|
|
52225
|
+
onChange: () => toggleLineSelection(line.id),
|
|
52226
|
+
className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
|
|
52227
|
+
disabled: isSubmitting
|
|
52228
|
+
}
|
|
52229
|
+
),
|
|
52230
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-gray-800 truncate", children: line.name })
|
|
52231
|
+
] }),
|
|
52232
|
+
selectedLines.includes(line.id) && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
|
|
52233
|
+
]
|
|
52234
|
+
},
|
|
52235
|
+
line.id
|
|
52236
|
+
)) }),
|
|
52237
|
+
selectedLineItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedLineItems.map((line) => /* @__PURE__ */ jsxs(
|
|
52238
|
+
"span",
|
|
51855
52239
|
{
|
|
51856
|
-
|
|
51857
|
-
className: cn(
|
|
51858
|
-
"px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
|
|
51859
|
-
selectedLines.includes(line.id) && "bg-blue-50"
|
|
51860
|
-
),
|
|
52240
|
+
className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
|
|
51861
52241
|
children: [
|
|
51862
|
-
|
|
51863
|
-
|
|
52242
|
+
line.name,
|
|
52243
|
+
/* @__PURE__ */ jsx(
|
|
52244
|
+
"button",
|
|
52245
|
+
{
|
|
52246
|
+
type: "button",
|
|
52247
|
+
onClick: () => toggleLineSelection(line.id),
|
|
52248
|
+
className: "hover:bg-blue-200 rounded-sm transition-colors",
|
|
52249
|
+
disabled: isSubmitting,
|
|
52250
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
|
|
52251
|
+
}
|
|
52252
|
+
)
|
|
51864
52253
|
]
|
|
51865
52254
|
},
|
|
51866
52255
|
line.id
|
|
@@ -52395,10 +52784,22 @@ var LineAssignmentDropdown = ({
|
|
|
52395
52784
|
canEdit,
|
|
52396
52785
|
onUpdate
|
|
52397
52786
|
}) => {
|
|
52787
|
+
const VIEWPORT_MARGIN = 8;
|
|
52788
|
+
const DROPDOWN_GAP = 6;
|
|
52789
|
+
const MIN_DROPDOWN_WIDTH = 300;
|
|
52790
|
+
const MAX_DROPDOWN_WIDTH = 420;
|
|
52791
|
+
const PREFERRED_DROPDOWN_HEIGHT = 420;
|
|
52792
|
+
const MIN_DROPDOWN_HEIGHT = 180;
|
|
52398
52793
|
const [isOpen, setIsOpen] = useState(false);
|
|
52399
52794
|
const [selectedIds, setSelectedIds] = useState(currentLineIds);
|
|
52400
52795
|
const [isSaving, setIsSaving] = useState(false);
|
|
52401
|
-
const [position, setPosition] = useState({
|
|
52796
|
+
const [position, setPosition] = useState({
|
|
52797
|
+
top: 0,
|
|
52798
|
+
left: 0,
|
|
52799
|
+
width: MIN_DROPDOWN_WIDTH,
|
|
52800
|
+
maxHeight: PREFERRED_DROPDOWN_HEIGHT,
|
|
52801
|
+
openAbove: false
|
|
52802
|
+
});
|
|
52402
52803
|
const buttonRef = useRef(null);
|
|
52403
52804
|
const dropdownRef = useRef(null);
|
|
52404
52805
|
useEffect(() => {
|
|
@@ -52408,10 +52809,31 @@ var LineAssignmentDropdown = ({
|
|
|
52408
52809
|
const updatePosition = () => {
|
|
52409
52810
|
if (isOpen && buttonRef.current) {
|
|
52410
52811
|
const rect = buttonRef.current.getBoundingClientRect();
|
|
52812
|
+
const viewportWidth = window.innerWidth;
|
|
52813
|
+
const viewportHeight = window.innerHeight;
|
|
52814
|
+
const width = Math.min(
|
|
52815
|
+
Math.max(rect.width, MIN_DROPDOWN_WIDTH),
|
|
52816
|
+
Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
|
|
52817
|
+
);
|
|
52818
|
+
const left = Math.min(
|
|
52819
|
+
Math.max(VIEWPORT_MARGIN, rect.left),
|
|
52820
|
+
viewportWidth - width - VIEWPORT_MARGIN
|
|
52821
|
+
);
|
|
52822
|
+
const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
|
|
52823
|
+
const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
|
|
52824
|
+
const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
|
|
52825
|
+
const availableSpace = openAbove ? spaceAbove : spaceBelow;
|
|
52826
|
+
const maxHeight = Math.max(
|
|
52827
|
+
MIN_DROPDOWN_HEIGHT,
|
|
52828
|
+
Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
|
|
52829
|
+
);
|
|
52830
|
+
const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
|
|
52411
52831
|
setPosition({
|
|
52412
|
-
top
|
|
52413
|
-
left
|
|
52414
|
-
width
|
|
52832
|
+
top,
|
|
52833
|
+
left,
|
|
52834
|
+
width,
|
|
52835
|
+
maxHeight,
|
|
52836
|
+
openAbove
|
|
52415
52837
|
});
|
|
52416
52838
|
}
|
|
52417
52839
|
};
|
|
@@ -52489,7 +52911,7 @@ var LineAssignmentDropdown = ({
|
|
|
52489
52911
|
assignedLines.length - 2
|
|
52490
52912
|
] });
|
|
52491
52913
|
};
|
|
52492
|
-
const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentLineIds.sort());
|
|
52914
|
+
const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentLineIds].sort());
|
|
52493
52915
|
if (!canEdit) {
|
|
52494
52916
|
return /* @__PURE__ */ jsx("div", { className: "text-sm", children: getDisplayText() });
|
|
52495
52917
|
}
|
|
@@ -52519,13 +52941,13 @@ var LineAssignmentDropdown = ({
|
|
|
52519
52941
|
"div",
|
|
52520
52942
|
{
|
|
52521
52943
|
ref: dropdownRef,
|
|
52522
|
-
className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
|
|
52944
|
+
className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
|
|
52523
52945
|
style: {
|
|
52524
|
-
top: `${position.top
|
|
52946
|
+
top: `${position.top}px`,
|
|
52525
52947
|
left: `${position.left}px`,
|
|
52526
|
-
|
|
52527
|
-
|
|
52528
|
-
|
|
52948
|
+
width: `${position.width}px`,
|
|
52949
|
+
maxHeight: `${position.maxHeight}px`,
|
|
52950
|
+
transform: position.openAbove ? "translateY(-100%)" : void 0
|
|
52529
52951
|
},
|
|
52530
52952
|
children: [
|
|
52531
52953
|
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -52542,7 +52964,7 @@ var LineAssignmentDropdown = ({
|
|
|
52542
52964
|
}
|
|
52543
52965
|
)
|
|
52544
52966
|
] }) }),
|
|
52545
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
52967
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxs(
|
|
52546
52968
|
"label",
|
|
52547
52969
|
{
|
|
52548
52970
|
className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
|
|
@@ -52602,10 +53024,22 @@ var FactoryAssignmentDropdown = ({
|
|
|
52602
53024
|
canEdit,
|
|
52603
53025
|
onUpdate
|
|
52604
53026
|
}) => {
|
|
53027
|
+
const VIEWPORT_MARGIN = 8;
|
|
53028
|
+
const DROPDOWN_GAP = 6;
|
|
53029
|
+
const MIN_DROPDOWN_WIDTH = 300;
|
|
53030
|
+
const MAX_DROPDOWN_WIDTH = 420;
|
|
53031
|
+
const PREFERRED_DROPDOWN_HEIGHT = 420;
|
|
53032
|
+
const MIN_DROPDOWN_HEIGHT = 180;
|
|
52605
53033
|
const [isOpen, setIsOpen] = useState(false);
|
|
52606
53034
|
const [selectedIds, setSelectedIds] = useState(currentFactoryIds);
|
|
52607
53035
|
const [isSaving, setIsSaving] = useState(false);
|
|
52608
|
-
const [position, setPosition] = useState({
|
|
53036
|
+
const [position, setPosition] = useState({
|
|
53037
|
+
top: 0,
|
|
53038
|
+
left: 0,
|
|
53039
|
+
width: MIN_DROPDOWN_WIDTH,
|
|
53040
|
+
maxHeight: PREFERRED_DROPDOWN_HEIGHT,
|
|
53041
|
+
openAbove: false
|
|
53042
|
+
});
|
|
52609
53043
|
const buttonRef = useRef(null);
|
|
52610
53044
|
const dropdownRef = useRef(null);
|
|
52611
53045
|
useEffect(() => {
|
|
@@ -52615,10 +53049,31 @@ var FactoryAssignmentDropdown = ({
|
|
|
52615
53049
|
const updatePosition = () => {
|
|
52616
53050
|
if (isOpen && buttonRef.current) {
|
|
52617
53051
|
const rect = buttonRef.current.getBoundingClientRect();
|
|
53052
|
+
const viewportWidth = window.innerWidth;
|
|
53053
|
+
const viewportHeight = window.innerHeight;
|
|
53054
|
+
const width = Math.min(
|
|
53055
|
+
Math.max(rect.width, MIN_DROPDOWN_WIDTH),
|
|
53056
|
+
Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
|
|
53057
|
+
);
|
|
53058
|
+
const left = Math.min(
|
|
53059
|
+
Math.max(VIEWPORT_MARGIN, rect.left),
|
|
53060
|
+
viewportWidth - width - VIEWPORT_MARGIN
|
|
53061
|
+
);
|
|
53062
|
+
const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
|
|
53063
|
+
const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
|
|
53064
|
+
const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
|
|
53065
|
+
const availableSpace = openAbove ? spaceAbove : spaceBelow;
|
|
53066
|
+
const maxHeight = Math.max(
|
|
53067
|
+
MIN_DROPDOWN_HEIGHT,
|
|
53068
|
+
Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
|
|
53069
|
+
);
|
|
53070
|
+
const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
|
|
52618
53071
|
setPosition({
|
|
52619
|
-
top
|
|
52620
|
-
left
|
|
52621
|
-
width
|
|
53072
|
+
top,
|
|
53073
|
+
left,
|
|
53074
|
+
width,
|
|
53075
|
+
maxHeight,
|
|
53076
|
+
openAbove
|
|
52622
53077
|
});
|
|
52623
53078
|
}
|
|
52624
53079
|
};
|
|
@@ -52696,7 +53151,7 @@ var FactoryAssignmentDropdown = ({
|
|
|
52696
53151
|
assignedFactories.length - 2
|
|
52697
53152
|
] });
|
|
52698
53153
|
};
|
|
52699
|
-
const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentFactoryIds.sort());
|
|
53154
|
+
const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentFactoryIds].sort());
|
|
52700
53155
|
if (!canEdit) {
|
|
52701
53156
|
return /* @__PURE__ */ jsx("div", { className: "text-sm", children: getDisplayText() });
|
|
52702
53157
|
}
|
|
@@ -52726,13 +53181,13 @@ var FactoryAssignmentDropdown = ({
|
|
|
52726
53181
|
"div",
|
|
52727
53182
|
{
|
|
52728
53183
|
ref: dropdownRef,
|
|
52729
|
-
className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
|
|
53184
|
+
className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
|
|
52730
53185
|
style: {
|
|
52731
|
-
top: `${position.top
|
|
53186
|
+
top: `${position.top}px`,
|
|
52732
53187
|
left: `${position.left}px`,
|
|
52733
|
-
|
|
52734
|
-
|
|
52735
|
-
|
|
53188
|
+
width: `${position.width}px`,
|
|
53189
|
+
maxHeight: `${position.maxHeight}px`,
|
|
53190
|
+
transform: position.openAbove ? "translateY(-100%)" : void 0
|
|
52736
53191
|
},
|
|
52737
53192
|
children: [
|
|
52738
53193
|
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -52749,7 +53204,7 @@ var FactoryAssignmentDropdown = ({
|
|
|
52749
53204
|
}
|
|
52750
53205
|
)
|
|
52751
53206
|
] }) }),
|
|
52752
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
53207
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxs(
|
|
52753
53208
|
"label",
|
|
52754
53209
|
{
|
|
52755
53210
|
className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
|
|
@@ -53397,11 +53852,32 @@ var UserManagementTable = ({
|
|
|
53397
53852
|
setSortDirection("asc");
|
|
53398
53853
|
}
|
|
53399
53854
|
};
|
|
53855
|
+
const getFactoryIdsFromUser = (user) => {
|
|
53856
|
+
const properties = user.properties || {};
|
|
53857
|
+
const rawFactoryIds = properties.factory_ids ?? properties.factory_id;
|
|
53858
|
+
if (Array.isArray(rawFactoryIds)) {
|
|
53859
|
+
return rawFactoryIds.filter((factoryId) => typeof factoryId === "string" && factoryId.length > 0);
|
|
53860
|
+
}
|
|
53861
|
+
if (typeof rawFactoryIds === "string" && rawFactoryIds.length > 0) {
|
|
53862
|
+
return [rawFactoryIds];
|
|
53863
|
+
}
|
|
53864
|
+
return [];
|
|
53865
|
+
};
|
|
53400
53866
|
const formatAssignments = (user) => {
|
|
53401
53867
|
if (user.role_level === "owner" || user.role_level === "it") return "Company-wide";
|
|
53402
53868
|
if (user.role_level === "optifye") return "All companies";
|
|
53403
|
-
if (user.role_level === "plant_head"
|
|
53404
|
-
|
|
53869
|
+
if (user.role_level === "plant_head") {
|
|
53870
|
+
if (user.assigned_factories && user.assigned_factories.length > 0) {
|
|
53871
|
+
return user.assigned_factories.join(", ");
|
|
53872
|
+
}
|
|
53873
|
+
const assignedFactoryIds = getFactoryIdsFromUser(user);
|
|
53874
|
+
if (assignedFactoryIds.length === 1) {
|
|
53875
|
+
return "1 factory assigned";
|
|
53876
|
+
}
|
|
53877
|
+
if (assignedFactoryIds.length > 1) {
|
|
53878
|
+
return `${assignedFactoryIds.length} factories assigned`;
|
|
53879
|
+
}
|
|
53880
|
+
return "No factories assigned";
|
|
53405
53881
|
}
|
|
53406
53882
|
if (user.role_level === "supervisor" && user.assigned_lines) {
|
|
53407
53883
|
return user.assigned_lines.join(", ") || "No lines assigned";
|
|
@@ -53593,22 +54069,28 @@ var UserManagementTable = ({
|
|
|
53593
54069
|
onUpdate: onLineAssignmentUpdate || (async () => {
|
|
53594
54070
|
})
|
|
53595
54071
|
}
|
|
53596
|
-
) : user.role_level === "plant_head" ?
|
|
53597
|
-
|
|
53598
|
-
{
|
|
53599
|
-
|
|
53600
|
-
currentFactoryIds: user.properties?.factory_ids || [],
|
|
53601
|
-
availableFactories: (
|
|
53602
|
-
// Filter factories to only show those from the target user's company
|
|
53603
|
-
user.properties?.company_id ? availableFactories.filter(
|
|
53604
|
-
(factory) => factory.company_id === user.properties?.company_id
|
|
53605
|
-
) : availableFactories
|
|
53606
|
-
),
|
|
53607
|
-
canEdit: permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate,
|
|
53608
|
-
onUpdate: onFactoryAssignmentUpdate || (async () => {
|
|
53609
|
-
})
|
|
54072
|
+
) : user.role_level === "plant_head" ? (() => {
|
|
54073
|
+
const canEditFactoryAssignments = permissions.canAssignFactories(user) && !!onFactoryAssignmentUpdate;
|
|
54074
|
+
if (!canEditFactoryAssignments) {
|
|
54075
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) });
|
|
53610
54076
|
}
|
|
53611
|
-
|
|
54077
|
+
return /* @__PURE__ */ jsx(
|
|
54078
|
+
FactoryAssignmentDropdown,
|
|
54079
|
+
{
|
|
54080
|
+
userId: user.user_id,
|
|
54081
|
+
currentFactoryIds: getFactoryIdsFromUser(user),
|
|
54082
|
+
availableFactories: (
|
|
54083
|
+
// Filter factories to only show those from the target user's company
|
|
54084
|
+
user.properties?.company_id ? availableFactories.filter(
|
|
54085
|
+
(factory) => factory.company_id === user.properties?.company_id
|
|
54086
|
+
) : availableFactories
|
|
54087
|
+
),
|
|
54088
|
+
canEdit: canEditFactoryAssignments,
|
|
54089
|
+
onUpdate: onFactoryAssignmentUpdate || (async () => {
|
|
54090
|
+
})
|
|
54091
|
+
}
|
|
54092
|
+
);
|
|
54093
|
+
})() : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
|
|
53612
54094
|
showUsageStats && /* @__PURE__ */ jsx("td", { className: "px-6 py-4", children: user.role_level === "plant_head" || user.role_level === "supervisor" ? /* @__PURE__ */ jsx(
|
|
53613
54095
|
"button",
|
|
53614
54096
|
{
|
|
@@ -54773,52 +55255,18 @@ function HomeView({
|
|
|
54773
55255
|
});
|
|
54774
55256
|
return merged;
|
|
54775
55257
|
}, [lineNames, dbLines]);
|
|
54776
|
-
const
|
|
54777
|
-
|
|
55258
|
+
const enabledLineIdSet = useMemo(
|
|
55259
|
+
() => new Set(dbLines.filter((line) => line.enable).map((line) => line.id)),
|
|
55260
|
+
[dbLines]
|
|
55261
|
+
);
|
|
54778
55262
|
const isSupervisor = user?.role_level === "supervisor";
|
|
54779
|
-
const assignedLineIds = useMemo(() => {
|
|
54780
|
-
const rawLineIds = user?.properties?.line_id || user?.properties?.line_ids || [];
|
|
54781
|
-
return Array.isArray(rawLineIds) ? rawLineIds : [];
|
|
54782
|
-
}, [user]);
|
|
54783
|
-
const assignedLineIdsInConfig = useMemo(() => {
|
|
54784
|
-
if (assignedLineIds.length === 0) {
|
|
54785
|
-
return [];
|
|
54786
|
-
}
|
|
54787
|
-
return assignedLineIds.filter((id3) => allLineIds.includes(id3));
|
|
54788
|
-
}, [assignedLineIds, allLineIds]);
|
|
54789
|
-
const assignedFactoryIds = useMemo(() => {
|
|
54790
|
-
const rawFactoryIds = user?.properties?.factory_id || user?.properties?.factory_ids || [];
|
|
54791
|
-
return Array.isArray(rawFactoryIds) ? rawFactoryIds : [];
|
|
54792
|
-
}, [user]);
|
|
54793
|
-
const lineFactoryMap = useMemo(() => {
|
|
54794
|
-
const map = /* @__PURE__ */ new Map();
|
|
54795
|
-
dbLines.forEach((line) => {
|
|
54796
|
-
map.set(line.id, line.factory_id);
|
|
54797
|
-
});
|
|
54798
|
-
return map;
|
|
54799
|
-
}, [dbLines]);
|
|
54800
|
-
const plantHeadLineIds = useMemo(() => {
|
|
54801
|
-
if (!isPlantHead || assignedFactoryIds.length === 0) {
|
|
54802
|
-
return [];
|
|
54803
|
-
}
|
|
54804
|
-
const assignedFactoryIdSet = new Set(assignedFactoryIds);
|
|
54805
|
-
return allLineIds.filter((lineId) => assignedFactoryIdSet.has(lineFactoryMap.get(lineId) || ""));
|
|
54806
|
-
}, [isPlantHead, assignedFactoryIds, allLineIds, lineFactoryMap]);
|
|
54807
55263
|
const visibleLineIds = useMemo(() => {
|
|
54808
|
-
|
|
54809
|
-
|
|
54810
|
-
|
|
54811
|
-
if (isPlantHead) {
|
|
54812
|
-
const combinedLineIds = /* @__PURE__ */ new Set();
|
|
54813
|
-
plantHeadLineIds.forEach((lineId) => combinedLineIds.add(lineId));
|
|
54814
|
-
assignedLineIdsInConfig.forEach((lineId) => combinedLineIds.add(lineId));
|
|
54815
|
-
return Array.from(combinedLineIds);
|
|
55264
|
+
const scoped = Array.from(new Set(allLineIds.filter(Boolean)));
|
|
55265
|
+
if (enabledLineIdSet.size === 0) {
|
|
55266
|
+
return scoped;
|
|
54816
55267
|
}
|
|
54817
|
-
|
|
54818
|
-
|
|
54819
|
-
}
|
|
54820
|
-
return allLineIds;
|
|
54821
|
-
}, [isOwner, isPlantHead, isSupervisor, allLineIds, plantHeadLineIds, assignedLineIdsInConfig]);
|
|
55268
|
+
return scoped.filter((lineId) => enabledLineIdSet.has(lineId));
|
|
55269
|
+
}, [allLineIds, enabledLineIdSet]);
|
|
54822
55270
|
const fallbackLineId = visibleLineIds[0] || defaultLineId;
|
|
54823
55271
|
const defaultHomeLineId = fallbackLineId;
|
|
54824
55272
|
const availableLineIds = useMemo(() => {
|
|
@@ -54845,7 +55293,7 @@ function HomeView({
|
|
|
54845
55293
|
return defaultHomeLineId;
|
|
54846
55294
|
});
|
|
54847
55295
|
useEffect(() => {
|
|
54848
|
-
if (!user ||
|
|
55296
|
+
if (!user || availableLineIds.length === 0) {
|
|
54849
55297
|
return;
|
|
54850
55298
|
}
|
|
54851
55299
|
try {
|
|
@@ -54859,13 +55307,14 @@ function HomeView({
|
|
|
54859
55307
|
} catch (error) {
|
|
54860
55308
|
console.warn("Failed to read line filter from sessionStorage:", error);
|
|
54861
55309
|
}
|
|
55310
|
+
if (availableLineIds.includes(selectedLineId)) {
|
|
55311
|
+
return;
|
|
55312
|
+
}
|
|
54862
55313
|
if (defaultHomeLineId !== selectedLineId) {
|
|
54863
55314
|
setSelectedLineId(defaultHomeLineId);
|
|
54864
55315
|
}
|
|
54865
55316
|
}, [
|
|
54866
55317
|
user,
|
|
54867
|
-
isSupervisor,
|
|
54868
|
-
isPlantHead,
|
|
54869
55318
|
availableLineIds,
|
|
54870
55319
|
defaultHomeLineId,
|
|
54871
55320
|
selectedLineId,
|
|
@@ -54884,14 +55333,14 @@ function HomeView({
|
|
|
54884
55333
|
const [diagnoses, setDiagnoses] = useState([]);
|
|
54885
55334
|
const timezone = useAppTimezone();
|
|
54886
55335
|
const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
|
|
54887
|
-
|
|
55336
|
+
visibleLineIds,
|
|
54888
55337
|
dashboardConfig?.shiftConfig
|
|
54889
55338
|
);
|
|
54890
55339
|
useEffect(() => {
|
|
54891
55340
|
const initDisplayNames = async () => {
|
|
54892
55341
|
try {
|
|
54893
55342
|
if (selectedLineId === factoryViewId) {
|
|
54894
|
-
for (const lineId of
|
|
55343
|
+
for (const lineId of visibleLineIds) {
|
|
54895
55344
|
await preInitializeWorkspaceDisplayNames(lineId);
|
|
54896
55345
|
}
|
|
54897
55346
|
} else {
|
|
@@ -54904,7 +55353,7 @@ function HomeView({
|
|
|
54904
55353
|
}
|
|
54905
55354
|
};
|
|
54906
55355
|
initDisplayNames();
|
|
54907
|
-
}, [selectedLineId, factoryViewId,
|
|
55356
|
+
}, [selectedLineId, factoryViewId, visibleLineIds]);
|
|
54908
55357
|
const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
|
|
54909
55358
|
const {
|
|
54910
55359
|
displayNames: workspaceDisplayNames,
|
|
@@ -58655,8 +59104,32 @@ var KPIsOverviewView = ({
|
|
|
58655
59104
|
const dbTimezone = useAppTimezone();
|
|
58656
59105
|
const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
58657
59106
|
const { startDate: monthStartDate, endDate: monthEndDateKey, monthEndDate } = getMonthDateInfo(configuredTimezone);
|
|
58658
|
-
const
|
|
58659
|
-
const
|
|
59107
|
+
const isSuperAdmin = user?.scope_mode === "SUPER_ADMIN" || !!user?.access_scope?.is_super_admin;
|
|
59108
|
+
const scopedLineIds = React26__default.useMemo(
|
|
59109
|
+
() => Array.isArray(user?.access_scope?.line_ids) ? user.access_scope.line_ids.filter((lineId) => typeof lineId === "string" && lineId.length > 0) : [],
|
|
59110
|
+
[user?.access_scope?.line_ids]
|
|
59111
|
+
);
|
|
59112
|
+
const hasCanonicalScope = !!user?.scope_mode || !!user?.access_scope;
|
|
59113
|
+
const scopeRole = (user?.role_level || user?.role || "").toLowerCase();
|
|
59114
|
+
const isStrictLineScopedRole = scopeRole === "supervisor" || scopeRole === "plant_head";
|
|
59115
|
+
const resolvedAssignedLineIds = React26__default.useMemo(() => {
|
|
59116
|
+
if (isSuperAdmin) return [];
|
|
59117
|
+
if (scopedLineIds.length > 0) return scopedLineIds;
|
|
59118
|
+
if (lineIds && lineIds.length > 0) return lineIds;
|
|
59119
|
+
if (isStrictLineScopedRole && hasCanonicalScope) return [];
|
|
59120
|
+
return [];
|
|
59121
|
+
}, [isSuperAdmin, scopedLineIds, lineIds, isStrictLineScopedRole, hasCanonicalScope]);
|
|
59122
|
+
const assignedLineIdSet = React26__default.useMemo(
|
|
59123
|
+
() => new Set(resolvedAssignedLineIds),
|
|
59124
|
+
[resolvedAssignedLineIds]
|
|
59125
|
+
);
|
|
59126
|
+
const metricsLineIds = React26__default.useMemo(() => {
|
|
59127
|
+
if (isSuperAdmin) {
|
|
59128
|
+
return lineIds ?? [];
|
|
59129
|
+
}
|
|
59130
|
+
return resolvedAssignedLineIds;
|
|
59131
|
+
}, [isSuperAdmin, lineIds, resolvedAssignedLineIds]);
|
|
59132
|
+
const assignedLineIdsForLeaderboard = isSuperAdmin ? void 0 : resolvedAssignedLineIds;
|
|
58660
59133
|
const leaderboardLinesForView = React26__default.useMemo(() => {
|
|
58661
59134
|
const targetMode = viewType === "machine" ? "uptime" : "output";
|
|
58662
59135
|
return leaderboardLines.filter((line) => (line.monitoring_mode ?? "output") === targetMode);
|
|
@@ -58708,7 +59181,7 @@ var KPIsOverviewView = ({
|
|
|
58708
59181
|
error: metricsError
|
|
58709
59182
|
} = useDashboardMetrics({
|
|
58710
59183
|
lineId: factoryViewId,
|
|
58711
|
-
userAccessibleLineIds:
|
|
59184
|
+
userAccessibleLineIds: metricsLineIds
|
|
58712
59185
|
});
|
|
58713
59186
|
const defaultKPIs = React26__default.useMemo(() => createDefaultKPIs(), []);
|
|
58714
59187
|
const kpisByLineId = React26__default.useMemo(() => {
|
|
@@ -58762,16 +59235,16 @@ var KPIsOverviewView = ({
|
|
|
58762
59235
|
console.log("[KPIsOverviewView] Fetching lines with lineIds filter:", lineIds);
|
|
58763
59236
|
const allLines = await dashboardService.getAllLines();
|
|
58764
59237
|
let filteredLines = allLines;
|
|
58765
|
-
if (
|
|
58766
|
-
filteredLines = allLines.filter((line) =>
|
|
58767
|
-
console.log("[KPIsOverviewView]
|
|
59238
|
+
if (!isSuperAdmin) {
|
|
59239
|
+
filteredLines = allLines.filter((line) => assignedLineIdSet.has(line.id));
|
|
59240
|
+
console.log("[KPIsOverviewView] Applied scoped line filter:", {
|
|
58768
59241
|
total: allLines.length,
|
|
58769
59242
|
filtered: filteredLines.length,
|
|
58770
|
-
|
|
59243
|
+
allowedLineIds: resolvedAssignedLineIds,
|
|
58771
59244
|
filteredLineIds: filteredLines.map((l) => l.id)
|
|
58772
59245
|
});
|
|
58773
59246
|
} else {
|
|
58774
|
-
console.log("[KPIsOverviewView]
|
|
59247
|
+
console.log("[KPIsOverviewView] Super admin view, showing all lines:", allLines.length);
|
|
58775
59248
|
}
|
|
58776
59249
|
setLines(filteredLines);
|
|
58777
59250
|
} catch (err) {
|
|
@@ -58782,34 +59255,36 @@ var KPIsOverviewView = ({
|
|
|
58782
59255
|
}
|
|
58783
59256
|
};
|
|
58784
59257
|
fetchLines();
|
|
58785
|
-
}, [supabase, dashboardConfig, lineIds]);
|
|
59258
|
+
}, [supabase, dashboardConfig, lineIds, isSuperAdmin, assignedLineIdSet, resolvedAssignedLineIds]);
|
|
58786
59259
|
useEffect(() => {
|
|
58787
59260
|
let isMounted = true;
|
|
58788
59261
|
const fetchLeaderboardLines = async () => {
|
|
58789
59262
|
if (!supabase || !resolvedCompanyId) {
|
|
58790
|
-
setLeaderboardLines(lines);
|
|
59263
|
+
setLeaderboardLines(lines.filter((line) => line.enable !== false));
|
|
58791
59264
|
return;
|
|
58792
59265
|
}
|
|
58793
59266
|
setLeaderboardLinesLoading(true);
|
|
58794
59267
|
try {
|
|
58795
|
-
const
|
|
58796
|
-
|
|
59268
|
+
const data = await fetchBackendJson(
|
|
59269
|
+
supabase,
|
|
59270
|
+
`/api/dashboard/leaderboard-lines?company_id=${encodeURIComponent(resolvedCompanyId)}`
|
|
59271
|
+
);
|
|
58797
59272
|
if (!isMounted) return;
|
|
58798
|
-
const transformed =
|
|
59273
|
+
const transformed = (data.lines || []).filter((line) => line.enable !== false).map((line) => ({
|
|
58799
59274
|
id: line.id,
|
|
58800
|
-
line_name: line.
|
|
58801
|
-
factory_id: line.
|
|
59275
|
+
line_name: line.line_name,
|
|
59276
|
+
factory_id: line.factory_id || "",
|
|
58802
59277
|
factory_name: "N/A",
|
|
58803
|
-
company_id: line.
|
|
59278
|
+
company_id: line.company_id,
|
|
58804
59279
|
company_name: "",
|
|
58805
|
-
enable: line.
|
|
58806
|
-
monitoring_mode: line.
|
|
59280
|
+
enable: line.enable ?? true,
|
|
59281
|
+
monitoring_mode: line.monitoring_mode ?? "output"
|
|
58807
59282
|
}));
|
|
58808
59283
|
setLeaderboardLines(transformed);
|
|
58809
59284
|
} catch (err) {
|
|
58810
59285
|
console.error("[KPIsOverviewView] Failed to load leaderboard lines:", err);
|
|
58811
59286
|
if (!isMounted) return;
|
|
58812
|
-
setLeaderboardLines(lines);
|
|
59287
|
+
setLeaderboardLines(lines.filter((line) => line.enable !== false));
|
|
58813
59288
|
} finally {
|
|
58814
59289
|
if (isMounted) setLeaderboardLinesLoading(false);
|
|
58815
59290
|
}
|
|
@@ -58890,11 +59365,10 @@ var KPIsOverviewView = ({
|
|
|
58890
59365
|
lineMode: viewType === "machine" ? "uptime" : "output"
|
|
58891
59366
|
});
|
|
58892
59367
|
const nextMap = /* @__PURE__ */ new Map();
|
|
59368
|
+
targetLineIds.forEach((lineId) => nextMap.set(lineId, 0));
|
|
58893
59369
|
entries.forEach((entry) => {
|
|
58894
59370
|
const value = Number(entry.avg_efficiency);
|
|
58895
|
-
|
|
58896
|
-
nextMap.set(entry.line_id, value);
|
|
58897
|
-
}
|
|
59371
|
+
nextMap.set(entry.line_id, Number.isFinite(value) ? value : 0);
|
|
58898
59372
|
});
|
|
58899
59373
|
setTodayEfficiencyByLineId(nextMap);
|
|
58900
59374
|
} catch (err) {
|
|
@@ -58977,6 +59451,9 @@ var KPIsOverviewView = ({
|
|
|
58977
59451
|
};
|
|
58978
59452
|
};
|
|
58979
59453
|
const handleLineClick = (line, kpis) => {
|
|
59454
|
+
if (!isSuperAdmin && !assignedLineIdSet.has(line.id)) {
|
|
59455
|
+
return;
|
|
59456
|
+
}
|
|
58980
59457
|
const trackProps = {
|
|
58981
59458
|
line_id: line.id,
|
|
58982
59459
|
line_name: line.line_name,
|
|
@@ -59489,6 +59966,7 @@ var MobileWorkspaceCard = memo$1(({
|
|
|
59489
59966
|
workspace,
|
|
59490
59967
|
rank,
|
|
59491
59968
|
cardClass,
|
|
59969
|
+
isClickable,
|
|
59492
59970
|
onWorkspaceClick,
|
|
59493
59971
|
getMedalIcon,
|
|
59494
59972
|
efficiencyLabel
|
|
@@ -59501,8 +59979,8 @@ var MobileWorkspaceCard = memo$1(({
|
|
|
59501
59979
|
transition: {
|
|
59502
59980
|
layout: { duration: 0.3, ease: "easeInOut" }
|
|
59503
59981
|
},
|
|
59504
|
-
onClick: () => onWorkspaceClick(workspace, rank),
|
|
59505
|
-
className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] cursor-pointer`,
|
|
59982
|
+
onClick: isClickable ? () => onWorkspaceClick(workspace, rank) : void 0,
|
|
59983
|
+
className: `${cardClass} p-3 rounded-lg border shadow-sm active:scale-[0.98] ${isClickable ? "cursor-pointer" : "cursor-not-allowed opacity-75"}`,
|
|
59506
59984
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
59507
59985
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
59508
59986
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -59524,13 +60002,14 @@ var MobileWorkspaceCard = memo$1(({
|
|
|
59524
60002
|
] })
|
|
59525
60003
|
}
|
|
59526
60004
|
), (prevProps, nextProps) => {
|
|
59527
|
-
return prevProps.efficiencyLabel === nextProps.efficiencyLabel && prevProps.rank === nextProps.rank && prevProps.cardClass === nextProps.cardClass && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.action_count === nextProps.workspace.action_count && prevProps.workspace.action_threshold === nextProps.workspace.action_threshold && prevProps.workspace.avg_cycle_time === nextProps.workspace.avg_cycle_time && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
|
|
60005
|
+
return prevProps.efficiencyLabel === nextProps.efficiencyLabel && prevProps.rank === nextProps.rank && prevProps.cardClass === nextProps.cardClass && prevProps.isClickable === nextProps.isClickable && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.action_count === nextProps.workspace.action_count && prevProps.workspace.action_threshold === nextProps.workspace.action_threshold && prevProps.workspace.avg_cycle_time === nextProps.workspace.avg_cycle_time && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
|
|
59528
60006
|
});
|
|
59529
60007
|
MobileWorkspaceCard.displayName = "MobileWorkspaceCard";
|
|
59530
60008
|
var DesktopWorkspaceRow = memo$1(({
|
|
59531
60009
|
workspace,
|
|
59532
60010
|
index,
|
|
59533
60011
|
rowClass,
|
|
60012
|
+
isClickable,
|
|
59534
60013
|
onWorkspaceClick,
|
|
59535
60014
|
getMedalIcon
|
|
59536
60015
|
}) => /* @__PURE__ */ jsxs(
|
|
@@ -59540,8 +60019,8 @@ var DesktopWorkspaceRow = memo$1(({
|
|
|
59540
60019
|
layoutId: `row-${workspace.workspace_uuid}`,
|
|
59541
60020
|
initial: false,
|
|
59542
60021
|
transition: { layout: { duration: 0.3, ease: "easeInOut" } },
|
|
59543
|
-
onClick: () => onWorkspaceClick(workspace, index + 1),
|
|
59544
|
-
className: `${rowClass} hover:bg-gray-50/90
|
|
60022
|
+
onClick: isClickable ? () => onWorkspaceClick(workspace, index + 1) : void 0,
|
|
60023
|
+
className: `${rowClass} transition-colors duration-150 ${isClickable ? "hover:bg-gray-50/90 cursor-pointer group" : "cursor-not-allowed opacity-75"}`,
|
|
59545
60024
|
children: [
|
|
59546
60025
|
/* @__PURE__ */ jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap group-hover:font-medium", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
59547
60026
|
/* @__PURE__ */ jsx("span", { children: index + 1 }),
|
|
@@ -59553,7 +60032,7 @@ var DesktopWorkspaceRow = memo$1(({
|
|
|
59553
60032
|
]
|
|
59554
60033
|
}
|
|
59555
60034
|
), (prevProps, nextProps) => {
|
|
59556
|
-
return prevProps.index === nextProps.index && prevProps.rowClass === nextProps.rowClass && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
|
|
60035
|
+
return prevProps.index === nextProps.index && prevProps.rowClass === nextProps.rowClass && prevProps.isClickable === nextProps.isClickable && prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.efficiency === nextProps.workspace.efficiency && prevProps.workspace.displayName === nextProps.workspace.displayName && prevProps.workspace.lineName === nextProps.workspace.lineName;
|
|
59557
60036
|
});
|
|
59558
60037
|
DesktopWorkspaceRow.displayName = "DesktopWorkspaceRow";
|
|
59559
60038
|
var LeaderboardDetailView = memo$1(({
|
|
@@ -59713,6 +60192,16 @@ var LeaderboardDetailView = memo$1(({
|
|
|
59713
60192
|
}
|
|
59714
60193
|
return allLineIds;
|
|
59715
60194
|
}, [entityConfig, userAccessibleLineIds]);
|
|
60195
|
+
const accessibleLineIdSet = useMemo(
|
|
60196
|
+
() => new Set((userAccessibleLineIds || []).filter(Boolean)),
|
|
60197
|
+
[userAccessibleLineIds]
|
|
60198
|
+
);
|
|
60199
|
+
const canOpenWorkspace = useCallback((workspaceLineId) => {
|
|
60200
|
+
if (!workspaceLineId) return false;
|
|
60201
|
+
if (!userAccessibleLineIds) return true;
|
|
60202
|
+
if (accessibleLineIdSet.size === 0) return false;
|
|
60203
|
+
return accessibleLineIdSet.has(workspaceLineId);
|
|
60204
|
+
}, [accessibleLineIdSet, userAccessibleLineIds]);
|
|
59716
60205
|
const { hasUptime: lineModeHasUptime, hasOutput: lineModeHasOutput } = useMemo(() => {
|
|
59717
60206
|
if (!lines || lines.length === 0) {
|
|
59718
60207
|
return { hasUptime: false, hasOutput: false };
|
|
@@ -60110,6 +60599,9 @@ var LeaderboardDetailView = memo$1(({
|
|
|
60110
60599
|
return `${startDate.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${endDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
|
|
60111
60600
|
}, [normalizedRange]);
|
|
60112
60601
|
const handleWorkspaceClick = useCallback((workspace, rank) => {
|
|
60602
|
+
if (!canOpenWorkspace(workspace.line_id)) {
|
|
60603
|
+
return;
|
|
60604
|
+
}
|
|
60113
60605
|
trackCoreEvent("Workspace from Leaderboard Clicked", {
|
|
60114
60606
|
workspace_name: workspace.workspace_name,
|
|
60115
60607
|
workspace_id: workspace.workspace_uuid,
|
|
@@ -60142,7 +60634,7 @@ var LeaderboardDetailView = memo$1(({
|
|
|
60142
60634
|
const combinedParams = navParams ? `${navParams}&${contextParamString}` : `?${contextParamString}`;
|
|
60143
60635
|
navigation.navigate(`/workspace/${workspace.workspace_uuid}${combinedParams}`);
|
|
60144
60636
|
}
|
|
60145
|
-
}, [onWorkspaceClick, navigation, date, shiftId]);
|
|
60637
|
+
}, [canOpenWorkspace, onWorkspaceClick, navigation, date, shiftId]);
|
|
60146
60638
|
useEffect(() => {
|
|
60147
60639
|
workspacesLengthRef.current = activeEntries.length || 0;
|
|
60148
60640
|
}, [activeEntries.length]);
|
|
@@ -60433,6 +60925,7 @@ var LeaderboardDetailView = memo$1(({
|
|
|
60433
60925
|
workspace: ws,
|
|
60434
60926
|
rank,
|
|
60435
60927
|
cardClass,
|
|
60928
|
+
isClickable: canOpenWorkspace(ws.line_id),
|
|
60436
60929
|
onWorkspaceClick: stableHandleWorkspaceClick,
|
|
60437
60930
|
getMedalIcon: stableGetMedalIcon,
|
|
60438
60931
|
efficiencyLabel
|
|
@@ -60457,6 +60950,7 @@ var LeaderboardDetailView = memo$1(({
|
|
|
60457
60950
|
workspace: ws,
|
|
60458
60951
|
index,
|
|
60459
60952
|
rowClass,
|
|
60953
|
+
isClickable: canOpenWorkspace(ws.line_id),
|
|
60460
60954
|
onWorkspaceClick: stableHandleWorkspaceClick,
|
|
60461
60955
|
getMedalIcon: stableGetMedalIcon
|
|
60462
60956
|
},
|
|
@@ -60467,7 +60961,7 @@ var LeaderboardDetailView = memo$1(({
|
|
|
60467
60961
|
) })
|
|
60468
60962
|
] });
|
|
60469
60963
|
}, (prevProps, nextProps) => {
|
|
60470
|
-
return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && JSON.stringify(prevProps.lineNames) === JSON.stringify(nextProps.lineNames) && prevProps.className === nextProps.className;
|
|
60964
|
+
return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && JSON.stringify(prevProps.lineNames) === JSON.stringify(nextProps.lineNames) && JSON.stringify(prevProps.userAccessibleLineIds) === JSON.stringify(nextProps.userAccessibleLineIds) && prevProps.className === nextProps.className;
|
|
60471
60965
|
});
|
|
60472
60966
|
LeaderboardDetailView.displayName = "LeaderboardDetailView";
|
|
60473
60967
|
var LeaderboardDetailViewWithDisplayNames = withAllWorkspaceDisplayNames(LeaderboardDetailView);
|
|
@@ -60882,6 +61376,20 @@ var ProfileView = () => {
|
|
|
60882
61376
|
] });
|
|
60883
61377
|
};
|
|
60884
61378
|
var ProfileView_default = ProfileView;
|
|
61379
|
+
|
|
61380
|
+
// src/lib/constants/actions.ts
|
|
61381
|
+
var ACTION_NAMES = {
|
|
61382
|
+
/** Assembly operations */
|
|
61383
|
+
ASSEMBLY: "Assembly",
|
|
61384
|
+
/** Packaging operations */
|
|
61385
|
+
PACKAGING: "Packaging",
|
|
61386
|
+
/** Inspection operations */
|
|
61387
|
+
INSPECTION: "Inspection",
|
|
61388
|
+
/** Testing operations */
|
|
61389
|
+
TESTING: "Testing",
|
|
61390
|
+
/** Quality control operations */
|
|
61391
|
+
QUALITY_CONTROL: "Quality Control"
|
|
61392
|
+
};
|
|
60885
61393
|
var calculateShiftHours = (startTime, endTime, breaks = []) => {
|
|
60886
61394
|
if (!startTime || !endTime) return 8;
|
|
60887
61395
|
const [startHour, startMinute] = startTime.split(":").map(Number);
|
|
@@ -60895,6 +61403,43 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
|
|
|
60895
61403
|
const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
|
|
60896
61404
|
return Number(hoursDiff.toFixed(1));
|
|
60897
61405
|
};
|
|
61406
|
+
var calculateBreakDuration2 = (startTime, endTime) => {
|
|
61407
|
+
const [startHour, startMinute] = startTime.split(":").map(Number);
|
|
61408
|
+
const [endHour, endMinute] = endTime.split(":").map(Number);
|
|
61409
|
+
let startMinutes = startHour * 60 + startMinute;
|
|
61410
|
+
let endMinutes = endHour * 60 + endMinute;
|
|
61411
|
+
if (endMinutes < startMinutes) {
|
|
61412
|
+
endMinutes += 24 * 60;
|
|
61413
|
+
}
|
|
61414
|
+
return endMinutes - startMinutes;
|
|
61415
|
+
};
|
|
61416
|
+
var parseBreaksFromDB = (dbBreaks) => {
|
|
61417
|
+
if (!dbBreaks) return [];
|
|
61418
|
+
if (Array.isArray(dbBreaks)) {
|
|
61419
|
+
return dbBreaks.map((breakItem) => ({
|
|
61420
|
+
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
61421
|
+
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
61422
|
+
duration: calculateBreakDuration2(
|
|
61423
|
+
breakItem.start || breakItem.startTime || "00:00",
|
|
61424
|
+
breakItem.end || breakItem.endTime || "00:00"
|
|
61425
|
+
),
|
|
61426
|
+
remarks: breakItem.remarks || breakItem.name || ""
|
|
61427
|
+
}));
|
|
61428
|
+
} else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
|
|
61429
|
+
return dbBreaks.breaks.map((breakItem) => ({
|
|
61430
|
+
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
61431
|
+
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
61432
|
+
duration: calculateBreakDuration2(
|
|
61433
|
+
breakItem.start || breakItem.startTime || "00:00",
|
|
61434
|
+
breakItem.end || breakItem.endTime || "00:00"
|
|
61435
|
+
),
|
|
61436
|
+
remarks: breakItem.remarks || breakItem.name || ""
|
|
61437
|
+
}));
|
|
61438
|
+
} else {
|
|
61439
|
+
console.warn("Unexpected breaks format:", dbBreaks);
|
|
61440
|
+
return [];
|
|
61441
|
+
}
|
|
61442
|
+
};
|
|
60898
61443
|
var getStoredLineState = (lineId) => {
|
|
60899
61444
|
try {
|
|
60900
61445
|
return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
|
|
@@ -60911,6 +61456,7 @@ var formatBreaks = (breaks) => {
|
|
|
60911
61456
|
}))
|
|
60912
61457
|
};
|
|
60913
61458
|
};
|
|
61459
|
+
var SHIFT_HOURS_EPSILON = 1e-3;
|
|
60914
61460
|
var BreakRow = memo$1(({
|
|
60915
61461
|
break: breakItem,
|
|
60916
61462
|
onUpdate,
|
|
@@ -61206,16 +61752,22 @@ var ShiftsView = ({
|
|
|
61206
61752
|
);
|
|
61207
61753
|
const [loading, setLoading] = useState(true);
|
|
61208
61754
|
const [error, setError] = useState(null);
|
|
61755
|
+
const [saveStatusMessages, setSaveStatusMessages] = useState(
|
|
61756
|
+
() => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
|
|
61757
|
+
);
|
|
61209
61758
|
const showToast = useCallback((type, message) => {
|
|
61210
61759
|
if (onToast) {
|
|
61211
|
-
|
|
61212
|
-
|
|
61213
|
-
|
|
61214
|
-
|
|
61215
|
-
} else {
|
|
61216
|
-
toast.error(message);
|
|
61760
|
+
try {
|
|
61761
|
+
onToast(type, message);
|
|
61762
|
+
} catch (error2) {
|
|
61763
|
+
console.warn("[ShiftsView] onToast callback failed, falling back to sonner toast:", error2);
|
|
61217
61764
|
}
|
|
61218
61765
|
}
|
|
61766
|
+
if (type === "success") {
|
|
61767
|
+
toast.success(message);
|
|
61768
|
+
} else {
|
|
61769
|
+
toast.error(message);
|
|
61770
|
+
}
|
|
61219
61771
|
}, [onToast]);
|
|
61220
61772
|
useEffect(() => {
|
|
61221
61773
|
const fetchShiftConfigs = async () => {
|
|
@@ -61366,15 +61918,196 @@ var ShiftsView = ({
|
|
|
61366
61918
|
return config;
|
|
61367
61919
|
}));
|
|
61368
61920
|
}, []);
|
|
61921
|
+
const getOperationalDateForLine = useCallback((lineConfig) => {
|
|
61922
|
+
const sortedShifts = [...lineConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
|
|
61923
|
+
const dayBoundaryStart = sortedShifts[0]?.startTime || "06:00";
|
|
61924
|
+
return getOperationalDate(lineConfig.timezone || "UTC", /* @__PURE__ */ new Date(), dayBoundaryStart);
|
|
61925
|
+
}, []);
|
|
61926
|
+
const recalculateTargetsForShiftHourChanges = useCallback(
|
|
61927
|
+
async ({
|
|
61928
|
+
lineId,
|
|
61929
|
+
lineConfig,
|
|
61930
|
+
previousShiftHours,
|
|
61931
|
+
updatedBy
|
|
61932
|
+
}) => {
|
|
61933
|
+
const primaryOperationalDate = getOperationalDate(lineConfig.timezone || "UTC");
|
|
61934
|
+
const alternateOperationalDate = getOperationalDateForLine(lineConfig);
|
|
61935
|
+
const candidateOperationalDates = primaryOperationalDate === alternateOperationalDate ? [primaryOperationalDate] : [primaryOperationalDate, alternateOperationalDate];
|
|
61936
|
+
let recalculatedCount = 0;
|
|
61937
|
+
const { data: lineRow, error: lineRowError } = await supabase.from("lines").select("factory_id").eq("id", lineId).maybeSingle();
|
|
61938
|
+
if (lineRowError) {
|
|
61939
|
+
throw new Error(`Failed to resolve line factory for target recalculation: ${lineRowError.message}`);
|
|
61940
|
+
}
|
|
61941
|
+
const lineFactoryId = lineRow?.factory_id || null;
|
|
61942
|
+
for (const shift of lineConfig.shifts || []) {
|
|
61943
|
+
const oldShiftHours = previousShiftHours[shift.shiftId];
|
|
61944
|
+
const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
|
|
61945
|
+
if (oldShiftHours === void 0) continue;
|
|
61946
|
+
if (!Number.isFinite(newShiftHours) || newShiftHours <= 0) {
|
|
61947
|
+
console.warn(
|
|
61948
|
+
`[ShiftsView] Skipping target recalculation for line ${lineId}, shift ${shift.shiftId} due to invalid new shift hours: ${newShiftHours}`
|
|
61949
|
+
);
|
|
61950
|
+
continue;
|
|
61951
|
+
}
|
|
61952
|
+
if (Math.abs(newShiftHours - oldShiftHours) <= SHIFT_HOURS_EPSILON) continue;
|
|
61953
|
+
let thresholdDateForShift = candidateOperationalDates[0];
|
|
61954
|
+
let currentThresholds = [];
|
|
61955
|
+
for (const candidateDate of candidateOperationalDates) {
|
|
61956
|
+
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);
|
|
61957
|
+
if (thresholdsFetchError) {
|
|
61958
|
+
throw new Error(
|
|
61959
|
+
`Failed to fetch action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsFetchError.message}`
|
|
61960
|
+
);
|
|
61961
|
+
}
|
|
61962
|
+
if ((thresholdRows || []).length > 0) {
|
|
61963
|
+
thresholdDateForShift = candidateDate;
|
|
61964
|
+
currentThresholds = thresholdRows || [];
|
|
61965
|
+
break;
|
|
61966
|
+
}
|
|
61967
|
+
}
|
|
61968
|
+
if (currentThresholds.length === 0) {
|
|
61969
|
+
continue;
|
|
61970
|
+
}
|
|
61971
|
+
const actionIds = Array.from(
|
|
61972
|
+
new Set(currentThresholds.map((threshold) => threshold.action_id).filter(Boolean))
|
|
61973
|
+
);
|
|
61974
|
+
const actionNameById = /* @__PURE__ */ new Map();
|
|
61975
|
+
if (actionIds.length > 0) {
|
|
61976
|
+
const { data: actionRows, error: actionsError } = await supabase.from("actions").select("id, action_name").in("id", actionIds);
|
|
61977
|
+
if (actionsError) {
|
|
61978
|
+
console.warn(
|
|
61979
|
+
`[ShiftsView] Failed to resolve action names for line ${lineId}, shift ${shift.shiftId}: ${actionsError.message}`
|
|
61980
|
+
);
|
|
61981
|
+
} else {
|
|
61982
|
+
(actionRows || []).forEach((actionRow) => {
|
|
61983
|
+
if (actionRow.id && actionRow.action_name) {
|
|
61984
|
+
actionNameById.set(actionRow.id, actionRow.action_name);
|
|
61985
|
+
}
|
|
61986
|
+
});
|
|
61987
|
+
}
|
|
61988
|
+
}
|
|
61989
|
+
const expandedHours = newShiftHours > oldShiftHours;
|
|
61990
|
+
const recalculatedThresholds = currentThresholds.map((threshold) => {
|
|
61991
|
+
const existingPPH = Number(threshold.pph_threshold) || 0;
|
|
61992
|
+
const existingDayOutput = Number(threshold.total_day_output) || 0;
|
|
61993
|
+
const baselineDayOutput = existingDayOutput > 0 ? existingDayOutput : Math.round(existingPPH * Math.max(oldShiftHours, 0));
|
|
61994
|
+
let nextPPH = existingPPH;
|
|
61995
|
+
let nextDayOutput = existingDayOutput;
|
|
61996
|
+
if (expandedHours) {
|
|
61997
|
+
const pphToKeep = existingPPH > 0 ? existingPPH : oldShiftHours > 0 ? Math.round(baselineDayOutput / oldShiftHours) : 0;
|
|
61998
|
+
nextPPH = pphToKeep;
|
|
61999
|
+
nextDayOutput = Math.round(pphToKeep * newShiftHours);
|
|
62000
|
+
} else {
|
|
62001
|
+
const dayOutputToKeep = baselineDayOutput;
|
|
62002
|
+
nextDayOutput = dayOutputToKeep;
|
|
62003
|
+
nextPPH = newShiftHours > 0 ? Math.round(dayOutputToKeep / newShiftHours) : 0;
|
|
62004
|
+
}
|
|
62005
|
+
const resolvedActionName = (typeof threshold.action_name === "string" && threshold.action_name.trim().length > 0 ? threshold.action_name : actionNameById.get(threshold.action_id)) || ACTION_NAMES.ASSEMBLY;
|
|
62006
|
+
return {
|
|
62007
|
+
line_id: threshold.line_id || lineId,
|
|
62008
|
+
shift_id: shift.shiftId,
|
|
62009
|
+
action_id: threshold.action_id,
|
|
62010
|
+
workspace_id: threshold.workspace_id,
|
|
62011
|
+
date: threshold.date || thresholdDateForShift,
|
|
62012
|
+
pph_threshold: Math.max(0, Math.round(Number.isFinite(nextPPH) ? nextPPH : 0)),
|
|
62013
|
+
ideal_cycle_time: Number(threshold.ideal_cycle_time) || 0,
|
|
62014
|
+
total_day_output: Math.max(0, Math.round(Number.isFinite(nextDayOutput) ? nextDayOutput : 0)),
|
|
62015
|
+
action_name: resolvedActionName,
|
|
62016
|
+
updated_by: updatedBy,
|
|
62017
|
+
...threshold.sku_id ? { sku_id: threshold.sku_id } : {}
|
|
62018
|
+
};
|
|
62019
|
+
});
|
|
62020
|
+
const { error: thresholdsUpsertError } = await supabase.from("action_thresholds").upsert(recalculatedThresholds);
|
|
62021
|
+
if (thresholdsUpsertError) {
|
|
62022
|
+
throw new Error(
|
|
62023
|
+
`Failed to update action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsUpsertError.message}`
|
|
62024
|
+
);
|
|
62025
|
+
}
|
|
62026
|
+
const packagingActionIds = new Set(
|
|
62027
|
+
Array.from(actionNameById.entries()).filter(([, actionName]) => actionName.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase()).map(([actionId]) => actionId)
|
|
62028
|
+
);
|
|
62029
|
+
const packagingThresholds = recalculatedThresholds.filter((threshold) => {
|
|
62030
|
+
if (packagingActionIds.has(threshold.action_id)) return true;
|
|
62031
|
+
return typeof threshold.action_name === "string" && threshold.action_name.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase();
|
|
62032
|
+
});
|
|
62033
|
+
const thresholdDayOutput = packagingThresholds.reduce(
|
|
62034
|
+
(sum, threshold) => sum + (Number(threshold.total_day_output) || 0),
|
|
62035
|
+
0
|
|
62036
|
+
);
|
|
62037
|
+
const thresholdPPH = packagingThresholds.reduce(
|
|
62038
|
+
(sum, threshold) => sum + (Number(threshold.pph_threshold) || 0),
|
|
62039
|
+
0
|
|
62040
|
+
);
|
|
62041
|
+
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();
|
|
62042
|
+
if (existingLineThresholdError) {
|
|
62043
|
+
console.warn(
|
|
62044
|
+
`[ShiftsView] Failed to read existing line threshold for line ${lineId}, shift ${shift.shiftId}: ${existingLineThresholdError.message}`
|
|
62045
|
+
);
|
|
62046
|
+
}
|
|
62047
|
+
const factoryId = existingLineThreshold?.factory_id || lineFactoryId;
|
|
62048
|
+
if (factoryId) {
|
|
62049
|
+
const lineThresholdPayload = {
|
|
62050
|
+
factory_id: factoryId,
|
|
62051
|
+
line_id: lineId,
|
|
62052
|
+
date: thresholdDateForShift,
|
|
62053
|
+
shift_id: shift.shiftId,
|
|
62054
|
+
product_code: existingLineThreshold?.product_code || "",
|
|
62055
|
+
threshold_day_output: thresholdDayOutput,
|
|
62056
|
+
threshold_pph: thresholdPPH
|
|
62057
|
+
};
|
|
62058
|
+
if (existingLineThreshold?.sku_id) {
|
|
62059
|
+
lineThresholdPayload.sku_id = existingLineThreshold.sku_id;
|
|
62060
|
+
}
|
|
62061
|
+
const { error: lineThresholdUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdPayload, { onConflict: "factory_id,line_id,date,shift_id" });
|
|
62062
|
+
if (lineThresholdUpsertError) {
|
|
62063
|
+
throw new Error(
|
|
62064
|
+
`Failed to update line thresholds for line ${lineId}, shift ${shift.shiftId}: ${lineThresholdUpsertError.message}`
|
|
62065
|
+
);
|
|
62066
|
+
}
|
|
62067
|
+
} else {
|
|
62068
|
+
console.warn(
|
|
62069
|
+
`[ShiftsView] Missing factory_id while updating line thresholds for line ${lineId}, shift ${shift.shiftId}`
|
|
62070
|
+
);
|
|
62071
|
+
}
|
|
62072
|
+
recalculatedCount += recalculatedThresholds.length;
|
|
62073
|
+
}
|
|
62074
|
+
return recalculatedCount;
|
|
62075
|
+
},
|
|
62076
|
+
[getOperationalDateForLine, supabase]
|
|
62077
|
+
);
|
|
61369
62078
|
const handleSaveShifts = useCallback(async (lineId) => {
|
|
62079
|
+
const userId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
|
|
61370
62080
|
setLineConfigs((prev) => prev.map(
|
|
61371
62081
|
(config) => config.id === lineId ? { ...config, isSaving: true, saveSuccess: false } : config
|
|
61372
62082
|
));
|
|
62083
|
+
setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
|
|
61373
62084
|
try {
|
|
61374
62085
|
const lineConfig = lineConfigs.find((config) => config.id === lineId);
|
|
61375
62086
|
if (!lineConfig) {
|
|
61376
62087
|
throw new Error("Line configuration not found");
|
|
61377
62088
|
}
|
|
62089
|
+
const { data: existingRows, error: existingRowsError } = await supabase.from("line_operating_hours").select("shift_id, start_time, end_time, breaks").eq("line_id", lineId);
|
|
62090
|
+
if (existingRowsError) {
|
|
62091
|
+
throw new Error(`Failed to read existing shift timings: ${existingRowsError.message}`);
|
|
62092
|
+
}
|
|
62093
|
+
const previousShiftHours = (existingRows || []).reduce(
|
|
62094
|
+
(acc, row) => {
|
|
62095
|
+
acc[row.shift_id] = calculateShiftHours(
|
|
62096
|
+
row.start_time,
|
|
62097
|
+
row.end_time,
|
|
62098
|
+
parseBreaksFromDB(row.breaks)
|
|
62099
|
+
);
|
|
62100
|
+
return acc;
|
|
62101
|
+
},
|
|
62102
|
+
{}
|
|
62103
|
+
);
|
|
62104
|
+
const changedShiftCount = (lineConfig.shifts || []).reduce((count, shift) => {
|
|
62105
|
+
const oldShiftHours = previousShiftHours[shift.shiftId];
|
|
62106
|
+
if (oldShiftHours === void 0) return count;
|
|
62107
|
+
const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
|
|
62108
|
+
if (!Number.isFinite(newShiftHours)) return count;
|
|
62109
|
+
return Math.abs(newShiftHours - oldShiftHours) > SHIFT_HOURS_EPSILON ? count + 1 : count;
|
|
62110
|
+
}, 0);
|
|
61378
62111
|
const allSavedRows = [];
|
|
61379
62112
|
for (const shift of lineConfig.shifts || []) {
|
|
61380
62113
|
const shiftData = {
|
|
@@ -61396,23 +62129,65 @@ var ShiftsView = ({
|
|
|
61396
62129
|
if (allSavedRows.length > 0) {
|
|
61397
62130
|
shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
|
|
61398
62131
|
}
|
|
62132
|
+
let recalculatedTargetsCount = 0;
|
|
62133
|
+
try {
|
|
62134
|
+
recalculatedTargetsCount = await recalculateTargetsForShiftHourChanges({
|
|
62135
|
+
lineId,
|
|
62136
|
+
lineConfig,
|
|
62137
|
+
previousShiftHours,
|
|
62138
|
+
updatedBy: userId
|
|
62139
|
+
});
|
|
62140
|
+
} catch (recalcError) {
|
|
62141
|
+
console.error("[ShiftsView] Shift timings were saved but target recalculation failed:", recalcError);
|
|
62142
|
+
showToast("error", "Shift timings saved, but target recalculation failed. Please review targets.");
|
|
62143
|
+
setSaveStatusMessages((prev) => ({
|
|
62144
|
+
...prev,
|
|
62145
|
+
[lineId]: {
|
|
62146
|
+
message: "Shift timings saved, but target recalculation failed. Please review targets.",
|
|
62147
|
+
tone: "error"
|
|
62148
|
+
}
|
|
62149
|
+
}));
|
|
62150
|
+
}
|
|
61399
62151
|
setLineConfigs((prev) => prev.map(
|
|
61400
62152
|
(config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
|
|
61401
62153
|
));
|
|
61402
|
-
|
|
62154
|
+
let successMessage = "Shift configurations saved successfully.";
|
|
62155
|
+
if (changedShiftCount > 0 && recalculatedTargetsCount > 0) {
|
|
62156
|
+
successMessage = `Shift configurations saved. Targets updated successfully for ${recalculatedTargetsCount} workspace${recalculatedTargetsCount === 1 ? "" : "s"}.`;
|
|
62157
|
+
} else if (changedShiftCount > 0) {
|
|
62158
|
+
successMessage = "Shift configurations saved. Target recalculation completed successfully.";
|
|
62159
|
+
}
|
|
62160
|
+
showToast("success", successMessage);
|
|
62161
|
+
setSaveStatusMessages((prev) => ({
|
|
62162
|
+
...prev,
|
|
62163
|
+
[lineId]: {
|
|
62164
|
+
message: successMessage,
|
|
62165
|
+
tone: "success"
|
|
62166
|
+
}
|
|
62167
|
+
}));
|
|
61403
62168
|
setTimeout(() => {
|
|
61404
62169
|
setLineConfigs((prev) => prev.map(
|
|
61405
62170
|
(config) => config.id === lineId ? { ...config, saveSuccess: false } : config
|
|
61406
62171
|
));
|
|
61407
62172
|
}, 3e3);
|
|
62173
|
+
setTimeout(() => {
|
|
62174
|
+
setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
|
|
62175
|
+
}, 7e3);
|
|
61408
62176
|
} catch (error2) {
|
|
61409
62177
|
console.error("Error saving shift configurations:", error2);
|
|
61410
62178
|
showToast("error", "Failed to save shift configurations");
|
|
62179
|
+
setSaveStatusMessages((prev) => ({
|
|
62180
|
+
...prev,
|
|
62181
|
+
[lineId]: {
|
|
62182
|
+
message: "Failed to save shift configurations",
|
|
62183
|
+
tone: "error"
|
|
62184
|
+
}
|
|
62185
|
+
}));
|
|
61411
62186
|
setLineConfigs((prev) => prev.map(
|
|
61412
62187
|
(config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: false } : config
|
|
61413
62188
|
));
|
|
61414
62189
|
}
|
|
61415
|
-
}, [lineConfigs, supabase, showToast]);
|
|
62190
|
+
}, [lineConfigs, recalculateTargetsForShiftHourChanges, supabase, showToast]);
|
|
61416
62191
|
return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
|
|
61417
62192
|
/* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "px-3 sm:px-4 md:px-6 lg:px-8 py-3 sm:py-4", children: [
|
|
61418
62193
|
/* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
|
|
@@ -61467,7 +62242,14 @@ var ShiftsView = ({
|
|
|
61467
62242
|
/* @__PURE__ */ 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: [
|
|
61468
62243
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
61469
62244
|
config.isSaving && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-blue-600", children: "Saving..." }),
|
|
61470
|
-
config.saveSuccess && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" })
|
|
62245
|
+
config.saveSuccess && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" }),
|
|
62246
|
+
saveStatusMessages[config.id]?.message && /* @__PURE__ */ jsx(
|
|
62247
|
+
"span",
|
|
62248
|
+
{
|
|
62249
|
+
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"}`,
|
|
62250
|
+
children: saveStatusMessages[config.id]?.message
|
|
62251
|
+
}
|
|
62252
|
+
)
|
|
61471
62253
|
] }),
|
|
61472
62254
|
/* @__PURE__ */ jsxs(
|
|
61473
62255
|
"button",
|
|
@@ -61524,20 +62306,6 @@ var ShiftsView = ({
|
|
|
61524
62306
|
var AuthenticatedShiftsView = withAuth(React26__default.memo(ShiftsView));
|
|
61525
62307
|
var ShiftsView_default = ShiftsView;
|
|
61526
62308
|
|
|
61527
|
-
// src/lib/constants/actions.ts
|
|
61528
|
-
var ACTION_NAMES = {
|
|
61529
|
-
/** Assembly operations */
|
|
61530
|
-
ASSEMBLY: "Assembly",
|
|
61531
|
-
/** Packaging operations */
|
|
61532
|
-
PACKAGING: "Packaging",
|
|
61533
|
-
/** Inspection operations */
|
|
61534
|
-
INSPECTION: "Inspection",
|
|
61535
|
-
/** Testing operations */
|
|
61536
|
-
TESTING: "Testing",
|
|
61537
|
-
/** Quality control operations */
|
|
61538
|
-
QUALITY_CONTROL: "Quality Control"
|
|
61539
|
-
};
|
|
61540
|
-
|
|
61541
62309
|
// src/views/TargetsView.utils.ts
|
|
61542
62310
|
var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
|
|
61543
62311
|
if (cycleTime === "" || cycleTime === 0) return "";
|
|
@@ -65954,9 +66722,38 @@ var TeamManagementView = ({
|
|
|
65954
66722
|
optifye: 0
|
|
65955
66723
|
});
|
|
65956
66724
|
const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
|
|
65957
|
-
const
|
|
66725
|
+
const normalizeIds = useCallback((value) => {
|
|
66726
|
+
if (Array.isArray(value)) {
|
|
66727
|
+
return value.filter((id3) => typeof id3 === "string" && id3.length > 0);
|
|
66728
|
+
}
|
|
66729
|
+
if (typeof value === "string" && value.length > 0) {
|
|
66730
|
+
return [value];
|
|
66731
|
+
}
|
|
66732
|
+
return [];
|
|
66733
|
+
}, []);
|
|
66734
|
+
const plantHeadFactoryIds = useMemo(() => {
|
|
66735
|
+
if (user?.role_level !== "plant_head") return [];
|
|
66736
|
+
const scopedFactoryIds = normalizeIds(user?.access_scope?.factory_ids);
|
|
66737
|
+
if (scopedFactoryIds.length > 0) {
|
|
66738
|
+
return Array.from(new Set(scopedFactoryIds));
|
|
66739
|
+
}
|
|
66740
|
+
const propertyFactoryIds = normalizeIds(
|
|
66741
|
+
user?.properties?.factory_ids ?? user?.properties?.factory_id
|
|
66742
|
+
);
|
|
66743
|
+
if (propertyFactoryIds.length > 0) {
|
|
66744
|
+
return Array.from(new Set(propertyFactoryIds));
|
|
66745
|
+
}
|
|
66746
|
+
return entityConfig?.factoryId ? [entityConfig.factoryId] : [];
|
|
66747
|
+
}, [user, entityConfig?.factoryId, normalizeIds]);
|
|
66748
|
+
const notifyScopeRefresh = useCallback(() => {
|
|
66749
|
+
if (typeof window !== "undefined") {
|
|
66750
|
+
window.dispatchEvent(new Event("rbac:refresh-scope"));
|
|
66751
|
+
}
|
|
66752
|
+
}, []);
|
|
66753
|
+
const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "plant_head" || user?.role_level === "optifye";
|
|
65958
66754
|
const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
|
|
65959
|
-
const
|
|
66755
|
+
const pageCompanyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
66756
|
+
const companyIdForUsage = pageCompanyId;
|
|
65960
66757
|
const usageDateRange = useMemo(() => {
|
|
65961
66758
|
const today = /* @__PURE__ */ new Date();
|
|
65962
66759
|
const dayOfWeek = today.getDay();
|
|
@@ -65987,8 +66784,8 @@ var TeamManagementView = ({
|
|
|
65987
66784
|
return acc;
|
|
65988
66785
|
}, {});
|
|
65989
66786
|
}, [usageData, usageDateRange.daysElapsed]);
|
|
65990
|
-
const pageTitle =
|
|
65991
|
-
const pageDescription = user?.role_level === "
|
|
66787
|
+
const pageTitle = "Team Management";
|
|
66788
|
+
const pageDescription = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye" ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
|
|
65992
66789
|
const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "it", "plant_head"].includes(user.role_level) : false;
|
|
65993
66790
|
const loadData = useCallback(async () => {
|
|
65994
66791
|
if (!supabase) {
|
|
@@ -65996,20 +66793,16 @@ var TeamManagementView = ({
|
|
|
65996
66793
|
setIsLoading(false);
|
|
65997
66794
|
return;
|
|
65998
66795
|
}
|
|
65999
|
-
const
|
|
66000
|
-
if (!
|
|
66001
|
-
|
|
66002
|
-
|
|
66003
|
-
|
|
66004
|
-
setIsLoading(false);
|
|
66005
|
-
return;
|
|
66006
|
-
}
|
|
66796
|
+
const companyId = pageCompanyId;
|
|
66797
|
+
if (!companyId) {
|
|
66798
|
+
setError("Company not found. Please contact your administrator.");
|
|
66799
|
+
setIsLoading(false);
|
|
66800
|
+
return;
|
|
66007
66801
|
}
|
|
66008
|
-
const companyId = entityConfig?.companyId || user?.properties?.company_id;
|
|
66009
66802
|
console.log("[TeamManagementView] Loading data:", {
|
|
66010
66803
|
role: user?.role_level,
|
|
66011
|
-
isOptifye:
|
|
66012
|
-
companyId
|
|
66804
|
+
isOptifye: user?.role_level === "optifye",
|
|
66805
|
+
companyId
|
|
66013
66806
|
});
|
|
66014
66807
|
setIsLoading(true);
|
|
66015
66808
|
setError(void 0);
|
|
@@ -66024,23 +66817,7 @@ var TeamManagementView = ({
|
|
|
66024
66817
|
if (!backendUrl) {
|
|
66025
66818
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
66026
66819
|
}
|
|
66027
|
-
if (
|
|
66028
|
-
const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").order("factory_name");
|
|
66029
|
-
const linesResponse = await fetch(`${backendUrl}/api/lines`, {
|
|
66030
|
-
headers: {
|
|
66031
|
-
"Authorization": `Bearer ${token}`,
|
|
66032
|
-
"Content-Type": "application/json"
|
|
66033
|
-
}
|
|
66034
|
-
});
|
|
66035
|
-
if (!linesResponse.ok) {
|
|
66036
|
-
throw new Error(`Failed to fetch lines: ${linesResponse.statusText}`);
|
|
66037
|
-
}
|
|
66038
|
-
const linesData = await linesResponse.json();
|
|
66039
|
-
const lines = linesData.lines || [];
|
|
66040
|
-
setAvailableFactories(factories || []);
|
|
66041
|
-
setAvailableLines(lines);
|
|
66042
|
-
console.log("[TeamManagementView] Optifye - Loaded factories:", factories?.length, "lines:", lines?.length, "Sample lines:", lines?.slice(0, 3));
|
|
66043
|
-
} else if ((user?.role_level === "owner" || user?.role_level === "it") && companyId) {
|
|
66820
|
+
if ((user?.role_level === "optifye" || user?.role_level === "owner" || user?.role_level === "it") && companyId) {
|
|
66044
66821
|
const { data: factories } = await supabase.from("factories").select("id, factory_name, company_id").eq("company_id", companyId).order("factory_name");
|
|
66045
66822
|
const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
|
|
66046
66823
|
headers: {
|
|
@@ -66055,9 +66832,8 @@ var TeamManagementView = ({
|
|
|
66055
66832
|
const lines = linesData.lines || [];
|
|
66056
66833
|
setAvailableFactories(factories || []);
|
|
66057
66834
|
setAvailableLines(lines);
|
|
66058
|
-
console.log("[TeamManagementView]
|
|
66835
|
+
console.log("[TeamManagementView] Company-scoped team view - Company:", companyId, "Loaded factories:", factories?.length, "lines:", lines?.length);
|
|
66059
66836
|
} else if (user?.role_level === "plant_head") {
|
|
66060
|
-
const plantHeadFactoryIds = user?.properties?.factory_ids || [];
|
|
66061
66837
|
if (plantHeadFactoryIds.length > 0) {
|
|
66062
66838
|
if (companyId) {
|
|
66063
66839
|
const linesResponse = await fetch(`${backendUrl}/api/lines?company_id=${companyId}`, {
|
|
@@ -66110,35 +66886,46 @@ var TeamManagementView = ({
|
|
|
66110
66886
|
setAvailableFactories([]);
|
|
66111
66887
|
console.log("[TeamManagementView] Fallback - Company:", companyId, "Loaded lines:", lines?.length);
|
|
66112
66888
|
}
|
|
66113
|
-
|
|
66114
|
-
|
|
66115
|
-
|
|
66116
|
-
|
|
66117
|
-
|
|
66118
|
-
|
|
66119
|
-
|
|
66120
|
-
|
|
66121
|
-
|
|
66122
|
-
|
|
66123
|
-
|
|
66124
|
-
|
|
66125
|
-
|
|
66126
|
-
|
|
66127
|
-
|
|
66128
|
-
|
|
66129
|
-
|
|
66130
|
-
|
|
66131
|
-
|
|
66132
|
-
|
|
66133
|
-
|
|
66134
|
-
|
|
66135
|
-
|
|
66136
|
-
|
|
66137
|
-
|
|
66138
|
-
|
|
66139
|
-
|
|
66140
|
-
|
|
66141
|
-
|
|
66889
|
+
const usersPromise = user?.role_level === "plant_head" ? (async () => {
|
|
66890
|
+
if (plantHeadFactoryIds.length === 0) {
|
|
66891
|
+
return [];
|
|
66892
|
+
}
|
|
66893
|
+
const results = await Promise.allSettled(
|
|
66894
|
+
plantHeadFactoryIds.map((factoryId) => userManagementService.getFactoryUsers(factoryId))
|
|
66895
|
+
);
|
|
66896
|
+
const successful = results.filter(
|
|
66897
|
+
(result) => result.status === "fulfilled"
|
|
66898
|
+
).flatMap((result) => result.value || []);
|
|
66899
|
+
if (successful.length > 0) {
|
|
66900
|
+
const byUserId = /* @__PURE__ */ new Map();
|
|
66901
|
+
successful.forEach((u) => {
|
|
66902
|
+
if (u?.user_id) {
|
|
66903
|
+
byUserId.set(u.user_id, u);
|
|
66904
|
+
}
|
|
66905
|
+
});
|
|
66906
|
+
return Array.from(byUserId.values());
|
|
66907
|
+
}
|
|
66908
|
+
const firstRejected = results.find(
|
|
66909
|
+
(result) => result.status === "rejected"
|
|
66910
|
+
);
|
|
66911
|
+
if (firstRejected) {
|
|
66912
|
+
throw firstRejected.reason;
|
|
66913
|
+
}
|
|
66914
|
+
return [];
|
|
66915
|
+
})() : userManagementService.getCompanyUsers(companyId);
|
|
66916
|
+
const [usersData, userStats] = await Promise.all([
|
|
66917
|
+
usersPromise,
|
|
66918
|
+
userManagementService.getUserStats(companyId)
|
|
66919
|
+
]);
|
|
66920
|
+
setUsers(usersData);
|
|
66921
|
+
setStats({
|
|
66922
|
+
totalUsers: userStats.total,
|
|
66923
|
+
owners: userStats.owners,
|
|
66924
|
+
it: userStats.it,
|
|
66925
|
+
plantHeads: userStats.plant_heads,
|
|
66926
|
+
supervisors: userStats.supervisors,
|
|
66927
|
+
optifye: 0
|
|
66928
|
+
});
|
|
66142
66929
|
} catch (err) {
|
|
66143
66930
|
console.error("Error loading team management data:", err);
|
|
66144
66931
|
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
@@ -66146,16 +66933,16 @@ var TeamManagementView = ({
|
|
|
66146
66933
|
} finally {
|
|
66147
66934
|
setIsLoading(false);
|
|
66148
66935
|
}
|
|
66149
|
-
}, [supabase, user, entityConfig]);
|
|
66936
|
+
}, [supabase, user, pageCompanyId, entityConfig?.factoryId, plantHeadFactoryIds]);
|
|
66150
66937
|
useEffect(() => {
|
|
66151
|
-
const companyId =
|
|
66152
|
-
const canLoad = hasAccess && user &&
|
|
66938
|
+
const companyId = pageCompanyId;
|
|
66939
|
+
const canLoad = hasAccess && user && !!companyId;
|
|
66153
66940
|
if (canLoad) {
|
|
66154
66941
|
loadData();
|
|
66155
66942
|
} else if (!user) {
|
|
66156
66943
|
setIsLoading(true);
|
|
66157
66944
|
}
|
|
66158
|
-
}, [hasAccess, loadData, user,
|
|
66945
|
+
}, [hasAccess, loadData, user, pageCompanyId]);
|
|
66159
66946
|
const handleUserAdded = useCallback(() => {
|
|
66160
66947
|
loadData();
|
|
66161
66948
|
}, [loadData]);
|
|
@@ -66169,12 +66956,13 @@ var TeamManagementView = ({
|
|
|
66169
66956
|
updated_by: user.id
|
|
66170
66957
|
});
|
|
66171
66958
|
toast.success("User role updated successfully");
|
|
66959
|
+
notifyScopeRefresh();
|
|
66172
66960
|
loadData();
|
|
66173
66961
|
} catch (err) {
|
|
66174
66962
|
console.error("Error updating user role:", err);
|
|
66175
66963
|
toast.error("Failed to update user role");
|
|
66176
66964
|
}
|
|
66177
|
-
}, [supabase, user, loadData]);
|
|
66965
|
+
}, [supabase, user, loadData, notifyScopeRefresh]);
|
|
66178
66966
|
const handleRemoveUser = useCallback(async (userId) => {
|
|
66179
66967
|
if (!supabase || !user) return;
|
|
66180
66968
|
try {
|
|
@@ -66197,12 +66985,13 @@ var TeamManagementView = ({
|
|
|
66197
66985
|
assigned_by: user.id
|
|
66198
66986
|
});
|
|
66199
66987
|
toast.success("Line assignments updated successfully");
|
|
66988
|
+
notifyScopeRefresh();
|
|
66200
66989
|
loadData();
|
|
66201
66990
|
} catch (err) {
|
|
66202
66991
|
console.error("Error updating line assignments:", err);
|
|
66203
66992
|
toast.error("Failed to update line assignments");
|
|
66204
66993
|
}
|
|
66205
|
-
}, [supabase, user, loadData]);
|
|
66994
|
+
}, [supabase, user, loadData, notifyScopeRefresh]);
|
|
66206
66995
|
const handleFactoryAssignmentUpdate = useCallback(async (userId, factoryIds) => {
|
|
66207
66996
|
if (!supabase || !user) return;
|
|
66208
66997
|
try {
|
|
@@ -66213,12 +67002,13 @@ var TeamManagementView = ({
|
|
|
66213
67002
|
assigned_by: user.id
|
|
66214
67003
|
});
|
|
66215
67004
|
toast.success("Factory assignments updated successfully");
|
|
67005
|
+
notifyScopeRefresh();
|
|
66216
67006
|
loadData();
|
|
66217
67007
|
} catch (err) {
|
|
66218
67008
|
console.error("Error updating factory assignments:", err);
|
|
66219
67009
|
toast.error("Failed to update factory assignments");
|
|
66220
67010
|
}
|
|
66221
|
-
}, [supabase, user, loadData]);
|
|
67011
|
+
}, [supabase, user, loadData, notifyScopeRefresh]);
|
|
66222
67012
|
const handleProfileUpdate = useCallback(async (userId, firstName, lastName, profilePhotoUrl) => {
|
|
66223
67013
|
if (!supabase || !user) return;
|
|
66224
67014
|
try {
|