RBAC Improvements

Caching improvements
Maker-Checker Implementation
This commit is contained in:
2026-10-21 07:27:47 +02:00
parent dee9098379
commit 5d7e2f74ce
25 changed files with 1269 additions and 309 deletions
@@ -44,6 +44,7 @@ import { HelpTooltip } from '../ui/HelpTooltip';
import { ExportButton } from '../ui/ExportButton';
import { ExportColumn } from '../../utils/exportUtils';
import { useTableSort, SortableTh } from '../../utils/tableSort';
import { useRbac } from '../../auth/rbac';
import {
Contract,
Award,
@@ -73,6 +74,7 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
}) => {
const queryClient = useQueryClient();
const { success, error: toastError, info } = useToast();
const { canAdd, canApprove, canApproveRecord, isMaker } = useRbac();
// Component state
const [internalCreateModalOpen, setInternalCreateModalOpen] = useState(false);
@@ -602,6 +604,7 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
</button>
<ExportButton
claim="grant_financials"
title="Contract Registry & Agreements"
subtitle="Authoritative repository of bilateral legal agreements and contract files"
filename={`pfm-contracts-${new Date().toISOString().split('T')[0]}`}
@@ -609,17 +612,19 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
data={filteredContracts}
/>
<button
type="button"
onClick={() => {
resetCreateForm();
setCreateModalOpen(true);
}}
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 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-xl shadow-md shadow-blue-500/20 transition cursor-pointer"
>
<Plus className="w-4 h-4" />
Create Contract
</button>
{canAdd('grant_financials') && (
<button
type="button"
onClick={() => {
resetCreateForm();
setCreateModalOpen(true);
}}
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 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-xl shadow-md shadow-blue-500/20 transition cursor-pointer"
>
<Plus className="w-4 h-4" />
Create Contract
</button>
)}
</div>
</div>
@@ -1224,17 +1229,34 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
<label className="block text-xs font-medium text-slate-200 mb-1">
Contract Lifecycle Status <span className="text-rose-400">*</span>
</label>
<select
value={newStatus}
onChange={(e) => setNewStatus(Number(e.target.value))}
className="w-full bg-[#151b2c] border border-slate-700 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-blue-500"
>
{CONTRACT_STATUS_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
{(() => {
const canApproveContract = canApproveRecord('grant_financials', contractToUpdate?.createdBy);
const isSelf = isMaker(contractToUpdate?.createdBy);
return (
<select
value={newStatus}
onChange={(e) => setNewStatus(Number(e.target.value))}
className="w-full bg-[#151b2c] border border-slate-700 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-blue-500 font-medium"
>
{CONTRACT_STATUS_OPTIONS.map((opt) => {
const isActiveOpt = opt.value === ContractStatus.Active;
const isOptDisabled = isActiveOpt && !canApproveContract;
let reasonText = '';
if (isActiveOpt && !canApprove('grant_financials')) {
reasonText = ' (Requires Update Permission)';
} else if (isActiveOpt && isSelf) {
reasonText = ' (Maker-Checker: Cannot approve own record)';
}
return (
<option key={opt.value} value={opt.value} disabled={isOptDisabled}>
{opt.label}{reasonText}
</option>
);
})}
</select>
);
})()}
</div>
<div className="flex items-center justify-end gap-2 pt-3 border-t border-slate-800">