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
@@ -45,6 +45,7 @@ import { useToast } from '../components/ui/Toast';
import { ExportButton } from '../components/ui/ExportButton';
import { ExportColumn } from '../utils/exportUtils';
import { useTableSort, SortableTh } from '../utils/tableSort';
import { useRbac } from '../auth/rbac';
import {
Award,
Budget,
@@ -98,6 +99,7 @@ type DisbursementFormValues = z.infer<typeof disbursementSchema>;
export const GrantsPage: React.FC = () => {
const queryClient = useQueryClient();
const { success, error: toastError } = useToast();
const { canAdd, canApprove, canExport } = useRbac();
const [activeTab, setActiveTab] = useState<'awards' | 'budgets' | 'disbursements'>('awards');
@@ -666,7 +668,7 @@ export const GrantsPage: React.FC = () => {
</div>
<div className="flex items-center gap-2 relative z-10 self-start md:self-center">
{activeTab === 'awards' && (
{activeTab === 'awards' && canAdd('grant_financials') && (
<button
onClick={openCreateAward}
className="inline-flex items-center gap-1.5 px-4 py-2.5 text-xs font-semibold text-white bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 rounded-xl shadow-lg shadow-emerald-600/25 transition cursor-pointer"
@@ -675,7 +677,7 @@ export const GrantsPage: React.FC = () => {
<span>Register Approved Award</span>
</button>
)}
{activeTab === 'budgets' && (
{activeTab === 'budgets' && canAdd('grant_financials') && (
<button
onClick={() => setIsBudgetModalOpen(true)}
className="inline-flex items-center gap-1.5 px-4 py-2.5 text-xs font-semibold text-white bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 rounded-xl shadow-lg shadow-emerald-600/25 transition cursor-pointer"
@@ -684,7 +686,7 @@ export const GrantsPage: React.FC = () => {
<span>Add Budget Line</span>
</button>
)}
{activeTab === 'disbursements' && (
{activeTab === 'disbursements' && canAdd('grant_financials') && (
<button
onClick={openCreateDisbursement}
className="inline-flex items-center gap-1.5 px-4 py-2.5 text-xs font-semibold text-white bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 rounded-xl shadow-lg shadow-emerald-600/25 transition cursor-pointer"
@@ -802,6 +804,7 @@ export const GrantsPage: React.FC = () => {
<span>Refresh Awards</span>
</button>
<ExportButton
claim="grant_financials"
title="Grant Awards Registry"
subtitle="Official directory of approved beneficiary funding allocations"
filename={`pfm-awards-${new Date().toISOString().split('T')[0]}`}
@@ -915,6 +918,7 @@ export const GrantsPage: React.FC = () => {
<span>Refresh Budgets</span>
</button>
<ExportButton
claim="grant_financials"
title="Project Budgets Registry"
subtitle="Detailed statutory project budget allocations and category lines"
filename={`pfm-budgets-${new Date().toISOString().split('T')[0]}`}
@@ -998,7 +1002,7 @@ export const GrantsPage: React.FC = () => {
<Edit2 className="w-3 h-3" />
<span>Update</span>
</button>
{b.status !== ApprovalStatus.Approved && (
{b.status !== ApprovalStatus.Approved && canApprove('grant_financials') && (
<button
type="button"
disabled={updateBudgetMutation.isPending}
@@ -1057,6 +1061,7 @@ export const GrantsPage: React.FC = () => {
<span>Refresh Disbursements</span>
</button>
<ExportButton
claim="grant_financials"
title="Disbursement Tranches Ledger"
subtitle="Milestone-linked funding releases and payment authorizations"
filename={`pfm-disbursements-${new Date().toISOString().split('T')[0]}`}
@@ -1154,14 +1159,16 @@ export const GrantsPage: React.FC = () => {
>
<Edit2 className="w-3.5 h-3.5" />
</button>
<button
onClick={() => approveMutation.mutate(d.id)}
disabled={approveMutation.isPending}
className="inline-flex items-center gap-1.5 px-3 py-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 rounded-lg shadow-md shadow-blue-500/20 transition cursor-pointer"
>
<CheckCircle className="w-3.5 h-3.5" />
Approve Payment
</button>
{canApprove('grant_financials') && (
<button
onClick={() => approveMutation.mutate(d.id)}
disabled={approveMutation.isPending}
className="inline-flex items-center gap-1.5 px-3 py-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 rounded-lg shadow-md shadow-blue-500/20 transition cursor-pointer"
>
<CheckCircle className="w-3.5 h-3.5" />
Approve Payment
</button>
)}
</>
)}
</div>
@@ -1555,7 +1562,9 @@ export const GrantsPage: React.FC = () => {
onChange={(e) => setSelectedBudgetStatus(Number(e.target.value) as ApprovalStatus)}
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-medium [&>option]:bg-slate-900 [&>option]:text-white"
>
<option value={ApprovalStatus.Approved}>Approved</option>
<option value={ApprovalStatus.Approved} disabled={!canApprove('grant_financials')}>
Approved {!canApprove('grant_financials') ? '(Requires Update Permission)' : ''}
</option>
<option value={ApprovalStatus.Pending}>Pending Review</option>
<option value={ApprovalStatus.Rejected}>Rejected / Blocked</option>
</select>