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
@@ -40,6 +40,7 @@ import { HelpTooltip } from '../components/ui/HelpTooltip';
import { ExportButton } from '../components/ui/ExportButton';
import { ExportColumn } from '../utils/exportUtils';
import { useTableSort, SortableTh } from '../utils/tableSort';
import { useRbac } from '../auth/rbac';
import { ApprovalStatusBadge } from '../components/ui/StatusBadge';
import { Beneficiary, ComplianceItem, Outcome, ApprovalStatus } from '../types';
import { getApprovalStatusBadge, formatDate, APPROVAL_STATUS_OPTIONS } from '../lib/formatters';
@@ -70,6 +71,7 @@ type OutcomeFormValues = z.infer<typeof outcomeSchema>;
export const BeneficiariesPage: React.FC = () => {
const queryClient = useQueryClient();
const { success, error: toastError } = useToast();
const { canAdd, canApprove, canExport } = useRbac();
const [selectedProjectId, setSelectedProjectId] = useState<string>('all');
const [activeTab, setActiveTab] = useState<'beneficiaries' | 'compliance'>('beneficiaries');
@@ -574,23 +576,26 @@ export const BeneficiariesPage: React.FC = () => {
</button>
<ExportButton
claim="beneficiaries"
data={sortedBens}
columns={benExportColumns}
filename={`project_beneficiaries_${new Date().toISOString().split('T')[0]}`}
title="Beneficiaries Register"
/>
<button
onClick={() => {
setEditingBen(null);
benForm.reset({ name: '', category: 'Youth & Skills', location: 'National', targetCount: 100, reachedCount: 0 });
setIsBenModalOpen(true);
}}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-semibold bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-500 hover:to-indigo-500 shadow-md shadow-blue-600/25 transition cursor-pointer"
>
<Plus className="w-3.5 h-3.5" />
<span>Register Beneficiary</span>
</button>
{canAdd('beneficiaries') && (
<button
onClick={() => {
setEditingBen(null);
benForm.reset({ name: '', category: 'Youth & Skills', location: 'National', targetCount: 100, reachedCount: 0 });
setIsBenModalOpen(true);
}}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-semibold bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-500 hover:to-indigo-500 shadow-md shadow-blue-600/25 transition cursor-pointer"
>
<Plus className="w-3.5 h-3.5" />
<span>Register Beneficiary</span>
</button>
)}
</div>
</div>
<div className="bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 shadow-lg backdrop-blur-sm overflow-hidden">
@@ -759,24 +764,27 @@ export const BeneficiariesPage: React.FC = () => {
</button>
<ExportButton
claim="beneficiaries"
data={sortedComps}
columns={compExportColumns}
filename={`compliance_requirements_${new Date().toISOString().split('T')[0]}`}
title="Compliance Requirements"
/>
<button
onClick={() => {
setEditingComp(null);
setCompTargetProjectId(selectedProjectId !== 'all' ? selectedProjectId : projects[0]?.id ? String(projects[0].id) : '');
compForm.reset({ title: '', requirements: '', status: ApprovalStatus.Pending });
setIsCompModalOpen(true);
}}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-semibold bg-slate-800 text-white hover:bg-slate-700 border border-slate-700 shadow-md transition cursor-pointer"
>
<CheckSquare className="w-3.5 h-3.5 text-emerald-400" />
<span>Add Compliance Item</span>
</button>
{canAdd('beneficiaries') && (
<button
onClick={() => {
setEditingComp(null);
setCompTargetProjectId(selectedProjectId !== 'all' ? selectedProjectId : projects[0]?.id ? String(projects[0].id) : '');
compForm.reset({ title: '', requirements: '', status: ApprovalStatus.Pending });
setIsCompModalOpen(true);
}}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-semibold bg-slate-800 text-white hover:bg-slate-700 border border-slate-700 shadow-md transition cursor-pointer"
>
<CheckSquare className="w-3.5 h-3.5 text-emerald-400" />
<span>Add Compliance Item</span>
</button>
)}
</div>
</div>
@@ -1017,8 +1025,13 @@ export const BeneficiariesPage: React.FC = () => {
className="w-full bg-slate-900 border border-slate-800 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-blue-500"
>
{APPROVAL_STATUS_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
<option
key={opt.value}
value={opt.value}
disabled={opt.value === ApprovalStatus.Approved && !canApprove('beneficiaries')}
>
{opt.label}
{opt.value === ApprovalStatus.Approved && !canApprove('beneficiaries') ? ' (Requires Update Permission)' : ''}
</option>
))}
</select>