Finishline #134
@@ -67,6 +67,7 @@ import {
|
|||||||
Instrument,
|
Instrument,
|
||||||
Instruction,
|
Instruction,
|
||||||
Rule,
|
Rule,
|
||||||
|
User,
|
||||||
OrganisationType,
|
OrganisationType,
|
||||||
ActivityStatus,
|
ActivityStatus,
|
||||||
ApprovalStatus,
|
ApprovalStatus,
|
||||||
@@ -564,20 +565,70 @@ export const GovernancePage: React.FC = () => {
|
|||||||
setIsOrgModalOpen(true);
|
setIsOrgModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openEditOrg = (org: Organisation) => {
|
const openEditOrg = async (org: Organisation) => {
|
||||||
setEditingOrg(org);
|
setEditingOrg(org);
|
||||||
const linkedUserId = org.userId || org.createdBy || usersDirectory[0]?.id || currentUserId;
|
|
||||||
const matchedUser = usersDirectory.find((u) => u.id === linkedUserId);
|
|
||||||
orgForm.reset({
|
|
||||||
name: org.name || '',
|
|
||||||
registrationNo: org.registrationNo || '',
|
|
||||||
email: org.email || '',
|
|
||||||
idNumber: matchedUser?.idNumber || (org as any).idNumber || '',
|
|
||||||
organisationUser: linkedUserId,
|
|
||||||
type: org.type || OrganisationType.Enterprise,
|
|
||||||
status: org.status ?? ActivityStatus.Active,
|
|
||||||
});
|
|
||||||
setIsOrgModalOpen(true);
|
setIsOrgModalOpen(true);
|
||||||
|
|
||||||
|
// 1. Fetch fresh organisation details from API
|
||||||
|
let currentOrg = org;
|
||||||
|
try {
|
||||||
|
const fresh = await getOrganisation(org.id);
|
||||||
|
if (fresh) {
|
||||||
|
currentOrg = fresh;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// fallback to passed org
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Resolve linked user ID from organisation model
|
||||||
|
const targetUserId =
|
||||||
|
currentOrg.userId ||
|
||||||
|
(currentOrg as any).UserId ||
|
||||||
|
(currentOrg as any).organisationUser ||
|
||||||
|
(currentOrg as any).OrganisationUser ||
|
||||||
|
currentOrg.createdBy ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
// 3. Find user in directory or fetch user record to get cached user profile and idNumber
|
||||||
|
let matchedUser = usersDirectory.find(
|
||||||
|
(u) => u.id && String(u.id).toLowerCase() === String(targetUserId).toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!matchedUser && targetUserId) {
|
||||||
|
try {
|
||||||
|
const fetchedUser = await getUser(targetUserId);
|
||||||
|
if (fetchedUser) {
|
||||||
|
matchedUser = fetchedUser;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// user fetch failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Ensure matched user is present in usersDirectory cache for dropdown rendering
|
||||||
|
if (matchedUser && !usersDirectory.some((u) => u.id === matchedUser!.id)) {
|
||||||
|
queryClient.setQueryData<User[]>(['users-directory'], (old = []) => [matchedUser!, ...old]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Resolve idNumber from matched user profile or organisation
|
||||||
|
const resolvedIdNumber =
|
||||||
|
matchedUser?.idNumber ||
|
||||||
|
currentOrg.idNumber ||
|
||||||
|
(currentOrg as any).IdNumber ||
|
||||||
|
(org as any).idNumber ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
const selectedUserId = matchedUser?.id || targetUserId || usersDirectory[0]?.id || currentUserId;
|
||||||
|
|
||||||
|
orgForm.reset({
|
||||||
|
name: currentOrg.name || '',
|
||||||
|
registrationNo: currentOrg.registrationNo || '',
|
||||||
|
email: currentOrg.email || '',
|
||||||
|
idNumber: resolvedIdNumber,
|
||||||
|
organisationUser: selectedUserId,
|
||||||
|
type: currentOrg.type || OrganisationType.Enterprise,
|
||||||
|
status: currentOrg.status ?? ActivityStatus.Active,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const openCreatePortfolio = () => {
|
const openCreatePortfolio = () => {
|
||||||
@@ -1448,10 +1499,18 @@ export const GovernancePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
{...orgForm.register('organisationUser', {
|
{...orgForm.register('organisationUser', {
|
||||||
onChange: (e) => {
|
onChange: async (e) => {
|
||||||
const selected = usersDirectory.find((u) => u.id === e.target.value);
|
const selectedId = e.target.value;
|
||||||
if (selected?.idNumber && !orgForm.getValues('idNumber')) {
|
let selected = usersDirectory.find((u) => u.id === selectedId);
|
||||||
orgForm.setValue('idNumber', selected.idNumber);
|
if (!selected && selectedId) {
|
||||||
|
try {
|
||||||
|
selected = await getUser(selectedId);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selected?.idNumber) {
|
||||||
|
orgForm.setValue('idNumber', selected.idNumber, { shouldValidate: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
updateTenant,
|
updateTenant,
|
||||||
getTenantOrganisations,
|
getTenantOrganisations,
|
||||||
getOrganisations,
|
getOrganisations,
|
||||||
|
getUsers,
|
||||||
} from '../services/api';
|
} from '../services/api';
|
||||||
import { Modal } from '../components/ui/Modal';
|
import { Modal } from '../components/ui/Modal';
|
||||||
import { useToast } from '../components/ui/Toast';
|
import { useToast } from '../components/ui/Toast';
|
||||||
@@ -35,8 +36,8 @@ import { ExportButton } from '../components/ui/ExportButton';
|
|||||||
import { ExportColumn } from '../utils/exportUtils';
|
import { ExportColumn } from '../utils/exportUtils';
|
||||||
import { useTableSort, SortableTh } from '../utils/tableSort';
|
import { useTableSort, SortableTh } from '../utils/tableSort';
|
||||||
import { ActivityStatusBadge } from '../components/ui/StatusBadge';
|
import { ActivityStatusBadge } from '../components/ui/StatusBadge';
|
||||||
import { Tenant, Organisation, TenantOrganisation, ActivityStatus, CreateTenantRequest, UpdateTenantRequest } from '../types';
|
import { Tenant, Organisation, TenantOrganisation, User, ActivityStatus, CreateTenantRequest, UpdateTenantRequest } from '../types';
|
||||||
import { formatDate, getOrganisationTypeLabel, getUserEmail } from '../lib/formatters';
|
import { formatDate, getOrganisationTypeLabel, getUserEmail, registerUserEmails } from '../lib/formatters';
|
||||||
import { useAuth } from '../auth/AuthProvider';
|
import { useAuth } from '../auth/AuthProvider';
|
||||||
|
|
||||||
const createTenantSchema = z.object({
|
const createTenantSchema = z.object({
|
||||||
@@ -74,6 +75,19 @@ const TenantOrganisationsSubList: React.FC<{ tenantId: number | string; tenantNa
|
|||||||
staleTime: 60000,
|
staleTime: 60000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch user directory to resolve linked user email per organisation
|
||||||
|
const { data: usersDirectory = [] } = useQuery<User[]>({
|
||||||
|
queryKey: ['users-directory'],
|
||||||
|
queryFn: () => getUsers(1, 200),
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (usersDirectory && usersDirectory.length > 0) {
|
||||||
|
registerUserEmails(usersDirectory);
|
||||||
|
}
|
||||||
|
}, [usersDirectory]);
|
||||||
|
|
||||||
const isLoading = loadingTenantOrgs || (loadingAllOrgs && tenantOrganisations.length > 0 && allOrganisations.length === 0);
|
const isLoading = loadingTenantOrgs || (loadingAllOrgs && tenantOrganisations.length > 0 && allOrganisations.length === 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -164,12 +178,23 @@ const TenantOrganisationsSubList: React.FC<{ tenantId: number | string; tenantNa
|
|||||||
const regNo = org?.registrationNo || tOrg.organisation?.registrationNo || '—';
|
const regNo = org?.registrationNo || tOrg.organisation?.registrationNo || '—';
|
||||||
const orgType = org?.type ?? tOrg.organisation?.type;
|
const orgType = org?.type ?? tOrg.organisation?.type;
|
||||||
const orgEmail = org?.email || tOrg.organisation?.email || '—';
|
const orgEmail = org?.email || tOrg.organisation?.email || '—';
|
||||||
const userEmailDisplay =
|
const linkedUserId =
|
||||||
tOrg.creator?.email ||
|
|
||||||
getUserEmail(org?.userId) ||
|
|
||||||
getUserEmail(tOrg.createdBy) ||
|
|
||||||
getUserEmail(org?.createdBy) ||
|
|
||||||
org?.userId ||
|
org?.userId ||
|
||||||
|
(org as any)?.UserId ||
|
||||||
|
(org as any)?.organisationUser ||
|
||||||
|
(org as any)?.OrganisationUser ||
|
||||||
|
(tOrg.organisation as any)?.userId ||
|
||||||
|
(tOrg as any)?.userId;
|
||||||
|
|
||||||
|
const matchedUser = linkedUserId
|
||||||
|
? usersDirectory.find((u) => u.id && String(u.id).toLowerCase() === String(linkedUserId).toLowerCase())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const userEmailDisplay =
|
||||||
|
matchedUser?.email ||
|
||||||
|
(linkedUserId ? getUserEmail(linkedUserId) : null) ||
|
||||||
|
(linkedUserId && String(linkedUserId).includes('@') ? String(linkedUserId) : null) ||
|
||||||
|
org?.email ||
|
||||||
'—';
|
'—';
|
||||||
const status = org?.status ?? tOrg.organisation?.status ?? tOrg.status;
|
const status = org?.status ?? tOrg.organisation?.status ?? tOrg.status;
|
||||||
const createdAt = org?.createdAt || tOrg.createdAt || tOrg.organisation?.createdAt;
|
const createdAt = org?.createdAt || tOrg.createdAt || tOrg.organisation?.createdAt;
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ export const normalizeOrganisation = (raw: any): Organisation => {
|
|||||||
name: raw.name ?? raw.Name ?? null,
|
name: raw.name ?? raw.Name ?? null,
|
||||||
registrationNo: raw.registrationNo ?? raw.RegistrationNo ?? raw.regNo ?? raw.RegNo ?? null,
|
registrationNo: raw.registrationNo ?? raw.RegistrationNo ?? raw.regNo ?? raw.RegNo ?? null,
|
||||||
email: raw.email ?? raw.Email ?? null,
|
email: raw.email ?? raw.Email ?? null,
|
||||||
userId: raw.userId ?? raw.UserId ?? raw.organisationUser ?? raw.OrganisationUser ?? null,
|
userId: raw.userId ?? raw.UserId ?? raw.organisationUser ?? raw.OrganisationUser ?? raw.user_id ?? raw.linkedUser ?? raw.LinkedUser ?? null,
|
||||||
|
idNumber: raw.idNumber ?? raw.IdNumber ?? raw.id_number ?? null,
|
||||||
type: raw.type ?? raw.Type ?? raw.organisationType ?? raw.OrganisationType ?? 5,
|
type: raw.type ?? raw.Type ?? raw.organisationType ?? raw.OrganisationType ?? 5,
|
||||||
status,
|
status,
|
||||||
createdAt: raw.createdAt ?? raw.CreatedAt ?? new Date().toISOString(),
|
createdAt: raw.createdAt ?? raw.CreatedAt ?? new Date().toISOString(),
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ export interface Organisation {
|
|||||||
registrationNo: string | null;
|
registrationNo: string | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
userId?: string | null;
|
userId?: string | null;
|
||||||
|
idNumber?: string | null;
|
||||||
type: OrganisationType;
|
type: OrganisationType;
|
||||||
status: ActivityStatus;
|
status: ActivityStatus;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user