Enforced approval rights

This commit is contained in:
2026-10-22 11:25:55 +02:00
parent 0261a6093e
commit 92891438f6
32 changed files with 1891 additions and 870 deletions
@@ -26,6 +26,7 @@ import {
ChevronUp,
CheckCircle2,
Loader2,
Lock,
} from 'lucide-react';
import {
getOrganisations,
@@ -339,8 +340,9 @@ export const GovernancePage: React.FC = () => {
setQuickUserPhone('');
setQuickUserIdNumber('');
setIsQuickAddUserOpen(false);
} catch (err: any) {
const errorDetail = err?.response?.data?.detail || err?.message || 'Failed to create user.';
} catch (err: unknown) {
const errObj = err as { response?: { data?: { detail?: string } }; message?: string };
const errorDetail = errObj?.response?.data?.detail || errObj?.message || 'Failed to create user.';
setQuickUserError(errorDetail);
toastError('Failed to create user', err);
} finally {
@@ -538,7 +540,7 @@ export const GovernancePage: React.FC = () => {
// Forms
const orgForm = useForm<OrganisationFormValues>({
resolver: zodResolver(organisationSchema) as any,
resolver: zodResolver(organisationSchema),
defaultValues: {
name: '',
registrationNo: '',
@@ -550,7 +552,7 @@ export const GovernancePage: React.FC = () => {
});
const portfolioForm = useForm<PortfolioFormValues>({
resolver: zodResolver(portfolioSchema) as any,
resolver: zodResolver(portfolioSchema),
defaultValues: {
name: '',
description: '',
@@ -560,7 +562,7 @@ export const GovernancePage: React.FC = () => {
});
const programmeForm = useForm<ProgrammeFormValues>({
resolver: zodResolver(programmeSchema) as any,
resolver: zodResolver(programmeSchema),
defaultValues: {
name: '',
portfolioId: portfolios[0]?.id ? Number(portfolios[0].id) : 201,
@@ -685,9 +687,9 @@ export const GovernancePage: React.FC = () => {
const targetUserId =
org.userId ||
(org as any).UserId ||
(org as any).organisationUser ||
(org as any).OrganisationUser ||
org.UserId ||
org.organisationUser ||
org.OrganisationUser ||
org.createdBy ||
'';
@@ -1988,33 +1990,43 @@ export const GovernancePage: React.FC = () => {
placement="top"
/>
</div>
{(() => {
const canApproveProgramme = canApproveRecord('governance', selectedProgrammeForDetail?.createdBy);
const isSelf = isMaker(selectedProgrammeForDetail?.createdBy);
{selectedProgrammeForDetail && Number(selectedProgrammeForDetail.status) === ApprovalStatus.Approved ? (
<div
className="w-full px-3 py-2 text-xs border border-emerald-500/30 bg-emerald-500/10 rounded-xl text-emerald-300 font-semibold flex items-center gap-1.5 cursor-default"
title="Approved programmes are immutable."
>
<Lock className="w-3.5 h-3.5 text-emerald-400" />
<span>Approved (Immutable - Cannot Be Changed)</span>
</div>
) : (
(() => {
const canApproveProgramme = canApproveRecord('governance', selectedProgrammeForDetail?.createdBy);
const isSelf = isMaker(selectedProgrammeForDetail?.createdBy);
return (
<select
{...programmeForm.register('status')}
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"
>
{APPROVAL_STATUS_OPTIONS.map((opt) => {
const isApprovedOpt = opt.value === ApprovalStatus.Approved;
const isOptDisabled = isApprovedOpt && !canApproveProgramme;
let reason = '';
if (isApprovedOpt && !canApprove('governance')) {
reason = ' (Requires Update Permission)';
} else if (isApprovedOpt && isSelf) {
reason = ' (Maker-Checker: Cannot approve own record)';
}
return (
<option key={opt.value} value={opt.value} disabled={isOptDisabled}>
{opt.label}{reason}
</option>
);
})}
</select>
);
})()}
return (
<select
{...programmeForm.register('status')}
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"
>
{APPROVAL_STATUS_OPTIONS.map((opt) => {
const isApprovedOpt = opt.value === ApprovalStatus.Approved;
const isOptDisabled = isApprovedOpt && !canApproveProgramme;
let reason = '';
if (isApprovedOpt && !canApprove('governance')) {
reason = ' (Requires Update Permission)';
} else if (isApprovedOpt && isSelf) {
reason = ' (Maker-Checker: Cannot approve own record)';
}
return (
<option key={opt.value} value={opt.value} disabled={isOptDisabled}>
{opt.label}{reason}
</option>
);
})}
</select>
);
})()
)}
</div>
<div>