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
@@ -39,6 +39,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, PriorityBadge } from '../components/ui/StatusBadge';
import { Issue, ChangeRequest, ApprovalStatus, ActivityStatus, Priority } from '../types';
import {
@@ -109,6 +110,7 @@ type ResolveChangeFormValues = z.infer<typeof resolveChangeSchema>;
export const ChangeManagementPage: 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<'issues' | 'changes'>('issues');
@@ -722,23 +724,26 @@ export const ChangeManagementPage: React.FC = () => {
</button>
<ExportButton
claim="change_management"
data={sortedIssues}
columns={issueExportCols}
filename={`project_issues_${new Date().toISOString().split('T')[0]}`}
title="Project Issues Register"
/>
<button
onClick={() => {
setEditingIssue(null);
issueForm.reset({ title: '', description: '', priority: Priority.Medium, assignedTo: '' });
setIsIssueModalOpen(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-amber-600 to-orange-600 text-white hover:from-amber-500 hover:to-orange-500 shadow-md shadow-amber-600/25 transition cursor-pointer"
>
<Plus className="w-3.5 h-3.5" />
<span>Log Issue</span>
</button>
{canAdd('change_management') && (
<button
onClick={() => {
setEditingIssue(null);
issueForm.reset({ title: '', description: '', priority: Priority.Medium, assignedTo: '' });
setIsIssueModalOpen(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-amber-600 to-orange-600 text-white hover:from-amber-500 hover:to-orange-500 shadow-md shadow-amber-600/25 transition cursor-pointer"
>
<Plus className="w-3.5 h-3.5" />
<span>Log Issue</span>
</button>
)}
</div>
</div>
@@ -920,28 +925,31 @@ export const ChangeManagementPage: React.FC = () => {
</button>
<ExportButton
claim="change_management"
data={sortedChanges}
columns={changeExportCols}
filename={`change_requests_${new Date().toISOString().split('T')[0]}`}
title="Change Requests Register"
/>
<button
onClick={() => {
setEditingChange(null);
changeForm.reset({
title: '',
justification: '',
revisedBudget: 0,
revisionDate: new Date().toISOString().split('T')[0],
});
setIsChangeModalOpen(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"
>
<GitPullRequest className="w-3.5 h-3.5 text-blue-400" />
<span>Request Change</span>
</button>
{canAdd('change_management') && (
<button
onClick={() => {
setEditingChange(null);
changeForm.reset({
title: '',
justification: '',
revisedBudget: 0,
revisionDate: new Date().toISOString().split('T')[0],
});
setIsChangeModalOpen(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"
>
<GitPullRequest className="w-3.5 h-3.5 text-blue-400" />
<span>Request Change</span>
</button>
)}
</div>
</div>
@@ -1014,18 +1022,22 @@ export const ChangeManagementPage: React.FC = () => {
</span>
) : (
<>
<button
onClick={() => openResolveChange(cr, ApprovalStatus.Approved)}
className="px-2.5 py-1 text-xs font-semibold text-emerald-300 bg-emerald-950/50 hover:bg-emerald-900/60 border border-emerald-700/50 rounded-lg transition cursor-pointer flex items-center gap-1"
>
<Check className="w-3 h-3" /> Approve
</button>
<button
onClick={() => openResolveChange(cr, ApprovalStatus.Rejected)}
className="px-2.5 py-1 text-xs font-semibold text-rose-300 bg-rose-950/50 hover:bg-rose-900/60 border border-rose-700/50 rounded-lg transition cursor-pointer flex items-center gap-1"
>
<XCircle className="w-3 h-3" /> Reject
</button>
{canApprove('change_management') && (
<button
onClick={() => openResolveChange(cr, ApprovalStatus.Approved)}
className="px-2.5 py-1 text-xs font-semibold text-emerald-300 bg-emerald-950/50 hover:bg-emerald-900/60 border border-emerald-700/50 rounded-lg transition cursor-pointer flex items-center gap-1"
>
<Check className="w-3 h-3" /> Approve
</button>
)}
{canAdd('change_management') && (
<button
onClick={() => openResolveChange(cr, ApprovalStatus.Rejected)}
className="px-2.5 py-1 text-xs font-semibold text-rose-300 bg-rose-950/50 hover:bg-rose-900/60 border border-rose-700/50 rounded-lg transition cursor-pointer flex items-center gap-1"
>
<XCircle className="w-3 h-3" /> Reject
</button>
)}
</>
)}
</div>
@@ -1510,7 +1522,9 @@ export const ChangeManagementPage: React.FC = () => {
>
<option value={ApprovalStatus.Pending}>Pending Review</option>
<option value={ApprovalStatus.Review}>Under Active Review</option>
<option value={ApprovalStatus.Approved}>Approved & Enacted</option>
<option value={ApprovalStatus.Approved} disabled={!canApprove('change_management')}>
Approved &amp; Enacted {!canApprove('change_management') ? '(Requires Update Permission)' : ''}
</option>
<option value={ApprovalStatus.Rejected}>Rejected</option>
</select>
</div>