Fixed idNumber missed assignment when updating organisation
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
|||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Loader2,
|
Loader2,
|
||||||
Lock,
|
Lock,
|
||||||
|
AlertTriangle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
getOrganisations,
|
getOrganisations,
|
||||||
@@ -113,6 +114,7 @@ const organisationSchema = z.object({
|
|||||||
registrationNo: z.string().min(4, 'Registration number is mandatory'),
|
registrationNo: z.string().min(4, 'Registration number is mandatory'),
|
||||||
email: z.string().email('Enter a valid email address'),
|
email: z.string().email('Enter a valid email address'),
|
||||||
organisationUser: z.string().min(1, 'Please select a linked executive or manager user'),
|
organisationUser: z.string().min(1, 'Please select a linked executive or manager user'),
|
||||||
|
idNumber: z.string().optional(),
|
||||||
type: z.coerce.number().min(1).max(27),
|
type: z.coerce.number().min(1).max(27),
|
||||||
status: z.coerce.number().optional(),
|
status: z.coerce.number().optional(),
|
||||||
});
|
});
|
||||||
@@ -593,11 +595,23 @@ export const GovernancePage: React.FC = () => {
|
|||||||
registrationNo: '',
|
registrationNo: '',
|
||||||
email: '',
|
email: '',
|
||||||
organisationUser: '',
|
organisationUser: '',
|
||||||
|
idNumber: '',
|
||||||
type: OrganisationType.Enterprise,
|
type: OrganisationType.Enterprise,
|
||||||
status: ActivityStatus.Active,
|
status: ActivityStatus.Active,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const watchedOrgUser = orgForm.watch('organisationUser');
|
||||||
|
const selectedExecutiveUser = React.useMemo(() => {
|
||||||
|
if (!watchedOrgUser) return null;
|
||||||
|
const str = String(watchedOrgUser).toLowerCase();
|
||||||
|
return (
|
||||||
|
usersDirectory.find((u) => u.id && String(u.id).toLowerCase() === str) ||
|
||||||
|
modalExecutiveUsers.find((u) => u.id && String(u.id).toLowerCase() === str) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}, [watchedOrgUser, usersDirectory, modalExecutiveUsers]);
|
||||||
|
|
||||||
const portfolioForm = useForm<PortfolioFormValues>({
|
const portfolioForm = useForm<PortfolioFormValues>({
|
||||||
resolver: zodResolver(portfolioSchema),
|
resolver: zodResolver(portfolioSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -622,11 +636,20 @@ export const GovernancePage: React.FC = () => {
|
|||||||
const orgMutation = useMutation({
|
const orgMutation = useMutation({
|
||||||
mutationFn: (data: OrganisationFormValues) => {
|
mutationFn: (data: OrganisationFormValues) => {
|
||||||
if (editingOrg) {
|
if (editingOrg) {
|
||||||
|
if (!data.idNumber || !data.idNumber.trim()) {
|
||||||
|
orgForm.setError('idNumber', {
|
||||||
|
type: 'manual',
|
||||||
|
message: 'Government ID number is required to assign this executive user.',
|
||||||
|
});
|
||||||
|
throw new Error('Executive user ID number is required when updating an organisation.');
|
||||||
|
}
|
||||||
|
|
||||||
return updateOrganisation({
|
return updateOrganisation({
|
||||||
organisationId: editingOrg.id,
|
organisationId: editingOrg.id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
registrationNo: data.registrationNo,
|
registrationNo: data.registrationNo,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
|
idNumber: data.idNumber.trim(),
|
||||||
organisationUser: data.organisationUser,
|
organisationUser: data.organisationUser,
|
||||||
type: Number(data.type),
|
type: Number(data.type),
|
||||||
status: data.status !== undefined ? Number(data.status) : ActivityStatus.Active,
|
status: data.status !== undefined ? Number(data.status) : ActivityStatus.Active,
|
||||||
@@ -636,6 +659,7 @@ export const GovernancePage: React.FC = () => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
registrationNo: data.registrationNo,
|
registrationNo: data.registrationNo,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
|
idNumber: data.idNumber?.trim() || null,
|
||||||
organisationUser: data.organisationUser,
|
organisationUser: data.organisationUser,
|
||||||
type: Number(data.type),
|
type: Number(data.type),
|
||||||
});
|
});
|
||||||
@@ -753,6 +777,7 @@ export const GovernancePage: React.FC = () => {
|
|||||||
registrationNo: '',
|
registrationNo: '',
|
||||||
email: '',
|
email: '',
|
||||||
organisationUser: defaultUser?.id || '',
|
organisationUser: defaultUser?.id || '',
|
||||||
|
idNumber: defaultUser?.idNumber || '',
|
||||||
type: OrganisationType.Enterprise,
|
type: OrganisationType.Enterprise,
|
||||||
status: ActivityStatus.Active,
|
status: ActivityStatus.Active,
|
||||||
});
|
});
|
||||||
@@ -770,9 +795,10 @@ export const GovernancePage: React.FC = () => {
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
let selectedUserId = '';
|
let selectedUserId = '';
|
||||||
|
let matchedUser: User | undefined;
|
||||||
if (targetUserId) {
|
if (targetUserId) {
|
||||||
const targetStr = String(targetUserId).toLowerCase();
|
const targetStr = String(targetUserId).toLowerCase();
|
||||||
const matchedUser = usersDirectory.find(
|
matchedUser = usersDirectory.find(
|
||||||
(u) =>
|
(u) =>
|
||||||
(u.id && String(u.id).toLowerCase() === targetStr) ||
|
(u.id && String(u.id).toLowerCase() === targetStr) ||
|
||||||
(u.email && u.email.toLowerCase() === targetStr)
|
(u.email && u.email.toLowerCase() === targetStr)
|
||||||
@@ -792,15 +818,25 @@ export const GovernancePage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedIdNumber = matchedUser?.idNumber || org.idNumber || '';
|
||||||
|
|
||||||
orgForm.reset({
|
orgForm.reset({
|
||||||
name: org.name || '',
|
name: org.name || '',
|
||||||
registrationNo: org.registrationNo || '',
|
registrationNo: org.registrationNo || '',
|
||||||
email: org.email || '',
|
email: org.email || '',
|
||||||
organisationUser: selectedUserId,
|
organisationUser: selectedUserId,
|
||||||
|
idNumber: resolvedIdNumber,
|
||||||
type: org.type || OrganisationType.Enterprise,
|
type: org.type || OrganisationType.Enterprise,
|
||||||
status: org.status ?? ActivityStatus.Active,
|
status: org.status ?? ActivityStatus.Active,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (selectedUserId && !resolvedIdNumber) {
|
||||||
|
orgForm.setError('idNumber', {
|
||||||
|
type: 'manual',
|
||||||
|
message: 'This executive user has no ID number on file. An ID number is required to update the organisation.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setIsQuickAddUserOpen(false);
|
setIsQuickAddUserOpen(false);
|
||||||
setQuickUserError(null);
|
setQuickUserError(null);
|
||||||
setIsOrgModalOpen(true);
|
setIsOrgModalOpen(true);
|
||||||
@@ -1760,7 +1796,32 @@ export const GovernancePage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
{...orgForm.register('organisationUser')}
|
{...orgForm.register('organisationUser', {
|
||||||
|
onChange: (e) => {
|
||||||
|
const selectedId = e.target.value;
|
||||||
|
if (!selectedId) {
|
||||||
|
orgForm.setValue('idNumber', '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const str = String(selectedId).toLowerCase();
|
||||||
|
const matched =
|
||||||
|
usersDirectory.find((u) => u.id && String(u.id).toLowerCase() === str) ||
|
||||||
|
modalExecutiveUsers.find((u) => u.id && String(u.id).toLowerCase() === str);
|
||||||
|
|
||||||
|
if (matched?.idNumber && matched.idNumber.trim()) {
|
||||||
|
orgForm.setValue('idNumber', matched.idNumber.trim(), { shouldValidate: true });
|
||||||
|
orgForm.clearErrors('idNumber');
|
||||||
|
} else {
|
||||||
|
orgForm.setValue('idNumber', '', { shouldValidate: true });
|
||||||
|
if (editingOrg) {
|
||||||
|
orgForm.setError('idNumber', {
|
||||||
|
type: 'manual',
|
||||||
|
message: 'Selected user has no ID number on file. Please enter their ID number to enable executive assignment.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="">-- Select linked executive or manager account --</option>
|
<option value="">-- Select linked executive or manager account --</option>
|
||||||
@@ -1778,9 +1839,61 @@ export const GovernancePage: React.FC = () => {
|
|||||||
No Executive or Manager user found. Create one below to continue.
|
No Executive or Manager user found. Create one below to continue.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{watchedOrgUser && selectedExecutiveUser && selectedExecutiveUser.idNumber && selectedExecutiveUser.idNumber.trim() && (
|
||||||
|
<div className="flex items-center gap-1.5 text-[11px] text-emerald-400 mt-1">
|
||||||
|
<CheckCircle className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
|
||||||
|
<span>ID on file: <strong className="font-mono text-emerald-300">{selectedExecutiveUser.idNumber}</strong></span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Missing ID Number Alert */}
|
||||||
|
{watchedOrgUser && selectedExecutiveUser && (!selectedExecutiveUser.idNumber || !selectedExecutiveUser.idNumber.trim()) && (
|
||||||
|
<div className="p-3 rounded-xl border border-amber-500/30 bg-amber-500/10 text-amber-300 text-xs flex items-start gap-2.5">
|
||||||
|
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-semibold text-amber-200">
|
||||||
|
Missing Executive ID Number
|
||||||
|
</p>
|
||||||
|
<p className="text-[11px] text-amber-300/90 leading-relaxed">
|
||||||
|
The selected executive (<strong className="font-mono text-white">{selectedExecutiveUser.email || watchedOrgUser}</strong>) does not have a Government ID Number on file. An ID number is required to assign this executive user; otherwise the update will be ignored. Please enter their ID number below.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Executive User ID Number Input */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<label className="block text-xs font-semibold text-slate-300">
|
||||||
|
Executive ID Number {editingOrg ? '*' : '(Optional)'}
|
||||||
|
</label>
|
||||||
|
<HelpTooltip
|
||||||
|
title="Executive ID Requirement"
|
||||||
|
content="Government Issued ID or Passport of the executive. Required when updating to persist the executive user link; otherwise the update will be ignored."
|
||||||
|
size="sm"
|
||||||
|
placement="top"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{selectedExecutiveUser?.idNumber && (
|
||||||
|
<span className="text-[10px] text-emerald-400 font-mono">Sourced from profile</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...orgForm.register('idNumber')}
|
||||||
|
className={`w-full px-3 py-2 text-xs border rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16] font-mono ${
|
||||||
|
orgForm.formState.errors.idNumber ? 'border-rose-500 ring-1 ring-rose-500' : 'border-slate-700/80'
|
||||||
|
}`}
|
||||||
|
placeholder="Enter 13-digit ID or Passport Number"
|
||||||
|
/>
|
||||||
|
{orgForm.formState.errors.idNumber && (
|
||||||
|
<p className="text-[10px] text-rose-500 mt-1">{orgForm.formState.errors.idNumber.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Inline Collapsible Subform to Create Executive User */}
|
{/* Inline Collapsible Subform to Create Executive User */}
|
||||||
{isQuickAddUserOpen && !editingOrg && (
|
{isQuickAddUserOpen && !editingOrg && (
|
||||||
<div className="p-3.5 rounded-xl border border-blue-500/30 bg-blue-950/20 space-y-3">
|
<div className="p-3.5 rounded-xl border border-blue-500/30 bg-blue-950/20 space-y-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user