diff --git a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx index 9ba3a9d..f3a2c7e 100644 --- a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx @@ -62,6 +62,7 @@ import { assignUserRole, getSystemRoles, getCurrentUserTenantId, + getTenantUsers, notifyNewUserCreated, } from '../services/api'; import { Modal } from '../components/ui/Modal'; @@ -77,6 +78,8 @@ import { FundingInstrumentsSection, InstructionsSection } from '../components/go import { RulesSection } from '../components/governance/RulesSection'; import { Organisation, + TenantOrganisation, + TenantUser, Portfolio, Programme, Instrument, @@ -137,8 +140,8 @@ type ProgrammeFormValues = z.infer; export const GovernancePage: React.FC = () => { const queryClient = useQueryClient(); - const { sub, user, claims } = useAuth(); - const { canAdd, canApprove, canExport, canApproveRecord, isMaker } = useRbac(); + const { sub, user, claims, roles, tenantId } = useAuth(); + const { canAdd, canApprove, canExport, canApproveRecord, isMaker, isUnrestricted } = useRbac(); const currentUserId = sub || user?.id || ''; const { success, error: toastError } = useToast(); @@ -265,6 +268,94 @@ export const GovernancePage: React.FC = () => { }); }, [usersDirectory]); + const isAdmin = + Boolean(isUnrestricted) || + (Array.isArray(roles) && roles.some((r) => r.toLowerCase().trim() === 'admin')) || + (claims?.role && String(claims.role).toLowerCase().includes('admin')); + + const orgTenantId = editingOrg?.tenantId || tenantId || getCurrentUserTenantId() || 1; + + const { data: tenantUsersList = [] } = useQuery({ + queryKey: ['tenant-users', orgTenantId], + queryFn: () => getTenantUsers(orgTenantId), + enabled: isOrgModalOpen && !!editingOrg && !isAdmin, + }); + + useEffect(() => { + if (tenantUsersList.length > 0) { + tenantUsersList.forEach((tu) => { + if (tu.userId && tu.user?.email) { + registerUserEmail(tu.userId, tu.user.email); + } + }); + } + }, [tenantUsersList]); + + const modalExecutiveUsers = React.useMemo(() => { + let list: User[] = []; + + if (isAdmin) { + list = usersDirectory.filter((u) => String(u.role || '').toLowerCase() === 'executive'); + } else { + list = tenantUsersList + .map((tu) => tu.user || ({ id: tu.userId, email: getUserEmail(tu.userId), role: 'Executive' } as User)) + .filter((u) => String(u.role || '').toLowerCase() === 'executive'); + } + + const currentLinkedUserId = + editingOrg?.userId || + editingOrg?.UserId || + editingOrg?.organisationUser || + editingOrg?.OrganisationUser || + ''; + + if (currentLinkedUserId) { + const alreadyIncluded = list.some( + (u) => u.id && String(u.id).toLowerCase() === String(currentLinkedUserId).toLowerCase() + ); + if (!alreadyIncluded) { + const resolvedEmail = getUserEmail(currentLinkedUserId); + const matchedInDir = usersDirectory.find( + (u) => u.id && String(u.id).toLowerCase() === String(currentLinkedUserId).toLowerCase() + ); + list = [ + { + id: currentLinkedUserId, + email: matchedInDir?.email || (resolvedEmail !== '—' && resolvedEmail ? resolvedEmail : currentLinkedUserId), + role: 'Executive', + status: ActivityStatus.Active, + lastLoginAt: null, + }, + ...list, + ]; + } + } + + return list; + }, [isAdmin, usersDirectory, tenantUsersList, editingOrg]); + + 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 const [isQuickAddUserOpen, setIsQuickAddUserOpen] = useState(false); const [quickUserEmail, setQuickUserEmail] = useState(''); @@ -597,6 +688,7 @@ export const GovernancePage: React.FC = () => { registrationNo: data.registrationNo, email: data.email, organisationUser: data.organisationUser, + userId: data.organisationUser, type: Number(data.type), status: data.status !== undefined ? Number(data.status) : ActivityStatus.Active, }); @@ -741,8 +833,19 @@ export const GovernancePage: React.FC = () => { org.createdBy || ''; + if (targetUserId) { + const email = getUserEmail(targetUserId); + if (email && email !== '—') { + registerUserEmail(targetUserId, email); + } + } + const matchedUser = targetUserId - ? usersDirectory.find((u) => u.id && String(u.id).toLowerCase() === String(targetUserId).toLowerCase()) + ? usersDirectory.find( + (u) => + (u.id && String(u.id).toLowerCase() === String(targetUserId).toLowerCase()) || + (u.email && getUserEmail(targetUserId) && u.email.toLowerCase() === getUserEmail(targetUserId).toLowerCase()) + ) : null; const selectedUserId = matchedUser?.id || targetUserId || ''; @@ -1714,7 +1817,7 @@ export const GovernancePage: React.FC = () => { className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium" > - {executiveAndManagerUsers.map((u) => ( + {(editingOrg ? modalExecutiveUsers : executiveAndManagerUsers).map((u) => ( diff --git a/PostFundManagement.ConsoleUI/src/services/api.ts b/PostFundManagement.ConsoleUI/src/services/api.ts index e27adbf..908c458 100644 --- a/PostFundManagement.ConsoleUI/src/services/api.ts +++ b/PostFundManagement.ConsoleUI/src/services/api.ts @@ -247,6 +247,7 @@ export const createOrganisation = async (payload: CreateOrganisationRequest): Pr email: payload.email, idNumber: payload.idNumber || null, organisationUser: payload.organisationUser, + userId: payload.userId || payload.organisationUser, type: payload.type !== undefined ? Number(payload.type) : 5, }); return res.data; @@ -261,6 +262,7 @@ export const updateOrganisation = async (payload: UpdateOrganisationRequest): Pr email: payload.email, idNumber: payload.idNumber, organisationUser: payload.organisationUser, + userId: payload.userId || payload.organisationUser, type: Number(payload.type), status: orgStatus, }); diff --git a/PostFundManagement.ConsoleUI/src/types.ts b/PostFundManagement.ConsoleUI/src/types.ts index eaae2f9..16cffce 100644 --- a/PostFundManagement.ConsoleUI/src/types.ts +++ b/PostFundManagement.ConsoleUI/src/types.ts @@ -388,6 +388,7 @@ export interface CreateOrganisationRequest { email: string; idNumber?: string | null; organisationUser: string; + userId?: string | null; type: OrganisationType; } @@ -398,6 +399,7 @@ export interface UpdateOrganisationRequest { email: string; idNumber?: string | null; organisationUser: string; + userId?: string | null; type: OrganisationType; status?: ActivityStatus; }