Fix UI freeze by removing cyclical useEffect and stabilizing executive candidate memo

This commit is contained in:
2026-10-25 12:13:15 +02:00
parent 07018c3ce4
commit 78d147b002
@@ -292,69 +292,64 @@ export const GovernancePage: React.FC = () => {
}, [tenantUsersList]); }, [tenantUsersList]);
const modalExecutiveUsers = React.useMemo(() => { const modalExecutiveUsers = React.useMemo(() => {
let list: User[] = []; const list: User[] = [];
const seenIds = new Set<string>();
if (isAdmin) { const addCandidate = (u?: User | null) => {
list = usersDirectory.filter((u) => String(u.role || '').toLowerCase() === 'executive'); if (!u || !u.id) return;
} else { const key = String(u.id).toLowerCase();
list = tenantUsersList if (seenIds.has(key)) return;
.map((tu) => tu.user || ({ id: tu.userId, email: getUserEmail(tu.userId), role: 'Executive' } as User)) seenIds.add(key);
.filter((u) => String(u.role || '').toLowerCase() === 'executive'); list.push(u);
} };
const currentLinkedUserId = // 1. Always include the organisation's linked user so the select option exists and selects immediately
const linkedUserId =
editingOrg?.userId || editingOrg?.userId ||
editingOrg?.UserId || editingOrg?.UserId ||
editingOrg?.organisationUser || editingOrg?.organisationUser ||
editingOrg?.OrganisationUser || editingOrg?.OrganisationUser ||
''; '';
if (currentLinkedUserId) { if (linkedUserId) {
const alreadyIncluded = list.some( const resolvedEmail = getUserEmail(linkedUserId);
(u) => u.id && String(u.id).toLowerCase() === String(currentLinkedUserId).toLowerCase() const matched = usersDirectory.find(
(u) => u.id && String(u.id).toLowerCase() === String(linkedUserId).toLowerCase()
); );
if (!alreadyIncluded) { addCandidate({
const resolvedEmail = getUserEmail(currentLinkedUserId); id: linkedUserId,
const matchedInDir = usersDirectory.find( email: matched?.email || (resolvedEmail !== '—' && resolvedEmail ? resolvedEmail : linkedUserId),
(u) => u.id && String(u.id).toLowerCase() === String(currentLinkedUserId).toLowerCase() role: matched?.role || 'Executive',
); status: ActivityStatus.Active,
list = [ lastLoginAt: null,
{ });
id: currentLinkedUserId, }
email: matchedInDir?.email || (resolvedEmail !== '—' && resolvedEmail ? resolvedEmail : currentLinkedUserId),
// 2. Load Executive role users: Admin sees all executives across tenants; normal tenant user focuses on tenantusers
if (isAdmin) {
usersDirectory
.filter((u) => String(u.role || '').toLowerCase() === 'executive')
.forEach(addCandidate);
} else {
tenantUsersList
.filter((tu) => String(tu.user?.role || '').toLowerCase() === 'executive')
.forEach((tu) => {
if (tu.user) {
addCandidate(tu.user);
} else if (tu.userId) {
addCandidate({
id: tu.userId,
email: getUserEmail(tu.userId),
role: 'Executive', role: 'Executive',
status: ActivityStatus.Active, status: ActivityStatus.Active,
lastLoginAt: null, lastLoginAt: null,
}, });
...list,
];
} }
});
} }
return list; return list;
}, [isAdmin, usersDirectory, tenantUsersList, editingOrg]); }, [isAdmin, usersDirectory, tenantUsersList, editingOrg?.id, editingOrg?.userId]);
useEffect(() => {
if (isOrgModalOpen && editingOrg) {
const targetUserId =
editingOrg.userId ||
editingOrg.UserId ||
editingOrg.organisationUser ||
editingOrg.OrganisationUser ||
'';
if (targetUserId) {
const targetEmail = getUserEmail(targetUserId);
const matched = modalExecutiveUsers.find(
(u) =>
(u.id && String(u.id).toLowerCase() === String(targetUserId).toLowerCase()) ||
(u.email && targetEmail && targetEmail !== '—' && u.email.toLowerCase() === targetEmail.toLowerCase())
);
const resolvedId = matched?.id || targetUserId;
orgForm.setValue('organisationUser', resolvedId, { shouldValidate: true, shouldDirty: false });
}
}
}, [isOrgModalOpen, editingOrg, modalExecutiveUsers]);
// Quick Add Executive User Subform state // Quick Add Executive User Subform state
const [isQuickAddUserOpen, setIsQuickAddUserOpen] = useState(false); const [isQuickAddUserOpen, setIsQuickAddUserOpen] = useState(false);