Implemented rbac

This commit is contained in:
2026-10-11 11:16:08 +02:00
parent 45a6c0aca0
commit 1142cfd94e
24 changed files with 3033 additions and 432 deletions
@@ -57,6 +57,7 @@ import { useToast } from '../ui/Toast';
import { HelpTooltip } from '../ui/HelpTooltip';
import { ExportButton } from '../ui/ExportButton';
import { ExportColumn } from '../../utils/exportUtils';
import { useRbac } from '../../auth/rbac';
export interface EvidenceReviewQueueProps {
initialProjectId?: string | number | 'all';
@@ -86,6 +87,7 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
}) => {
const queryClient = useQueryClient();
const { success, error: toastError } = useToast();
const { canAdd, canApprove, canExport } = useRbac();
// Filter States: default to initialProjectId if given, otherwise when projects load default to first project
const [selectedProjectId, setSelectedProjectId] = useState<string | number | 'all'>(initialProjectId);
@@ -454,6 +456,7 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
</button>
<ExportButton
claim="evidence_risks"
title="Deliverable Evidence & Verification Ledger"
subtitle="Audited records of submitted milestone deliverables and verification decisions"
filename={`pfm-evidence-review-${new Date().toISOString().split('T')[0]}`}
@@ -461,15 +464,17 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
data={filteredEvidence}
/>
<button
type="button"
onClick={() => setIsManualIngestModalOpen(true)}
className="inline-flex items-center gap-1.5 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 border border-transparent px-3.5 py-2 rounded-xl transition shadow-md shadow-blue-500/20 cursor-pointer"
title="Upload document on behalf of awardee"
>
<Upload className="w-3.5 h-3.5" />
<span>Ingest Evidence</span>
</button>
{canAdd('evidence_risks') && (
<button
type="button"
onClick={() => setIsManualIngestModalOpen(true)}
className="inline-flex items-center gap-1.5 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 border border-transparent px-3.5 py-2 rounded-xl transition shadow-md shadow-blue-500/20 cursor-pointer"
title="Upload document on behalf of awardee"
>
<Upload className="w-3.5 h-3.5" />
<span>Ingest Evidence</span>
</button>
)}
</div>
</div>
@@ -795,49 +800,53 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
<span>Inspect</span>
</button>
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: ev.id,
status: ApprovalStatus.Rejected,
version: ev.version,
evidenceType: ev.type,
})
}
disabled={reviewMutation.isPending && String(reviewMutation.variables?.id) === String(ev.id)}
className={`inline-flex items-center gap-1 text-xs font-semibold px-2.5 py-1.5 rounded-xl border transition cursor-pointer ${
ev.status === ApprovalStatus.Rejected
? 'bg-rose-500/20 text-rose-300 border-rose-500/30 opacity-75'
: 'text-rose-400 hover:text-white bg-rose-500/10 hover:bg-rose-600 border border-rose-500/25'
}`}
title="Reject evidence submission"
>
<XCircle className="w-3.5 h-3.5" />
<span>{ev.status === ApprovalStatus.Rejected ? 'Rejected' : 'Reject'}</span>
</button>
{canAdd('evidence_risks') && (
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: ev.id,
status: ApprovalStatus.Rejected,
version: ev.version,
evidenceType: ev.type,
})
}
disabled={reviewMutation.isPending && String(reviewMutation.variables?.id) === String(ev.id)}
className={`inline-flex items-center gap-1 text-xs font-semibold px-2.5 py-1.5 rounded-xl border transition cursor-pointer ${
ev.status === ApprovalStatus.Rejected
? 'bg-rose-500/20 text-rose-300 border-rose-500/30 opacity-75'
: 'text-rose-400 hover:text-white bg-rose-500/10 hover:bg-rose-600 border border-rose-500/25'
}`}
title="Reject evidence submission"
>
<XCircle className="w-3.5 h-3.5" />
<span>{ev.status === ApprovalStatus.Rejected ? 'Rejected' : 'Reject'}</span>
</button>
)}
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: ev.id,
status: ApprovalStatus.Approved,
version: ev.version,
evidenceType: ev.type,
})
}
disabled={reviewMutation.isPending && String(reviewMutation.variables?.id) === String(ev.id)}
className={`inline-flex items-center gap-1 text-xs font-semibold px-2.5 py-1.5 rounded-xl border transition cursor-pointer ${
ev.status === ApprovalStatus.Approved
? 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30 opacity-75'
: 'text-emerald-400 hover:text-white bg-emerald-500/10 hover:bg-emerald-600 border border-emerald-500/25'
}`}
title="Approve evidence submission"
>
<CheckCircle2 className="w-3.5 h-3.5" />
<span>{ev.status === ApprovalStatus.Approved ? 'Approved' : 'Approve'}</span>
</button>
{canApprove('evidence_risks') && (
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: ev.id,
status: ApprovalStatus.Approved,
version: ev.version,
evidenceType: ev.type,
})
}
disabled={reviewMutation.isPending && String(reviewMutation.variables?.id) === String(ev.id)}
className={`inline-flex items-center gap-1 text-xs font-semibold px-2.5 py-1.5 rounded-xl border transition cursor-pointer ${
ev.status === ApprovalStatus.Approved
? 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30 opacity-75'
: 'text-emerald-400 hover:text-white bg-emerald-500/10 hover:bg-emerald-600 border border-emerald-500/25'
}`}
title="Approve evidence submission"
>
<CheckCircle2 className="w-3.5 h-3.5" />
<span>{ev.status === ApprovalStatus.Approved ? 'Approved' : 'Approve'}</span>
</button>
)}
<button
type="button"
@@ -989,49 +998,53 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
</div>
<div className="flex items-center gap-2.5 shrink-0">
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: inspectEvidence.id,
status: ApprovalStatus.Rejected,
version: inspectEvidence.version,
evidenceType: inspectEvidence.type,
})
}
disabled={reviewMutation.isPending}
className={`px-4 py-2 text-xs font-semibold rounded-xl transition cursor-pointer flex items-center gap-1.5 ${
inspectEvidence.status === ApprovalStatus.Rejected
? 'bg-rose-600/30 text-rose-300 border border-rose-500/40'
: 'text-white bg-rose-600 hover:bg-rose-500 shadow-md shadow-rose-600/20'
}`}
title="Reject evidence submission via API"
>
<XCircle className="w-3.5 h-3.5" />
<span>{reviewMutation.isPending && reviewMutation.variables?.status === ApprovalStatus.Rejected ? 'Rejecting...' : 'Reject Evidence'}</span>
</button>
{canAdd('evidence_risks') && (
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: inspectEvidence.id,
status: ApprovalStatus.Rejected,
version: inspectEvidence.version,
evidenceType: inspectEvidence.type,
})
}
disabled={reviewMutation.isPending}
className={`px-4 py-2 text-xs font-semibold rounded-xl transition cursor-pointer flex items-center gap-1.5 ${
inspectEvidence.status === ApprovalStatus.Rejected
? 'bg-rose-600/30 text-rose-300 border border-rose-500/40'
: 'text-white bg-rose-600 hover:bg-rose-500 shadow-md shadow-rose-600/20'
}`}
title="Reject evidence submission via API"
>
<XCircle className="w-3.5 h-3.5" />
<span>{reviewMutation.isPending && reviewMutation.variables?.status === ApprovalStatus.Rejected ? 'Rejecting...' : 'Reject Evidence'}</span>
</button>
)}
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: inspectEvidence.id,
status: ApprovalStatus.Approved,
version: inspectEvidence.version,
evidenceType: inspectEvidence.type,
})
}
disabled={reviewMutation.isPending}
className={`px-4 py-2 text-xs font-semibold rounded-xl transition cursor-pointer flex items-center gap-1.5 ${
inspectEvidence.status === ApprovalStatus.Approved
? 'bg-emerald-600/30 text-emerald-300 border border-emerald-500/40'
: 'text-white bg-emerald-600 hover:bg-emerald-500 shadow-md shadow-emerald-600/20'
}`}
title="Approve evidence submission via API"
>
<CheckCircle2 className="w-3.5 h-3.5" />
<span>{reviewMutation.isPending && reviewMutation.variables?.status === ApprovalStatus.Approved ? 'Approving...' : 'Approve Evidence'}</span>
</button>
{canApprove('evidence_risks') && (
<button
type="button"
onClick={() =>
reviewMutation.mutate({
id: inspectEvidence.id,
status: ApprovalStatus.Approved,
version: inspectEvidence.version,
evidenceType: inspectEvidence.type,
})
}
disabled={reviewMutation.isPending}
className={`px-4 py-2 text-xs font-semibold rounded-xl transition cursor-pointer flex items-center gap-1.5 ${
inspectEvidence.status === ApprovalStatus.Approved
? 'bg-emerald-600/30 text-emerald-300 border border-emerald-500/40'
: 'text-white bg-emerald-600 hover:bg-emerald-500 shadow-md shadow-emerald-600/20'
}`}
title="Approve evidence submission via API"
>
<CheckCircle2 className="w-3.5 h-3.5" />
<span>{reviewMutation.isPending && reviewMutation.variables?.status === ApprovalStatus.Approved ? 'Approving...' : 'Approve Evidence'}</span>
</button>
)}
</div>
</div>
</div>