Refactored projects page, fixed security user modal
This commit is contained in:
@@ -723,23 +723,23 @@ export const ProjectsPage: React.FC = () => {
|
||||
|
||||
{/* Projects Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-xs text-slate-300 border-collapse">
|
||||
<table className="w-full text-left text-xs text-slate-300 border-collapse min-w-[960px]">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800/80 bg-[#090d16] text-[11px] font-semibold text-slate-400 select-none">
|
||||
<th className="py-3 px-3 w-10 text-center"></th>
|
||||
<SortableTh column="name" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4">
|
||||
<SortableTh column="name" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[180px]">
|
||||
Project Name & ID
|
||||
</SortableTh>
|
||||
<SortableTh column="programme" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4">
|
||||
<SortableTh column="programme" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[140px]">
|
||||
Parent Programme
|
||||
</SortableTh>
|
||||
<SortableTh column="duration" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4">
|
||||
<SortableTh column="duration" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[140px] whitespace-nowrap">
|
||||
Schedule (Timeline)
|
||||
</SortableTh>
|
||||
<SortableTh column="progress" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4">
|
||||
<SortableTh column="progress" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 w-28 whitespace-nowrap">
|
||||
WBS Progress
|
||||
</SortableTh>
|
||||
<SortableTh column="risk" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4">
|
||||
<SortableTh column="risk" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[125px] whitespace-nowrap">
|
||||
<div className="flex items-center gap-1">
|
||||
<span>Schedule Risk</span>
|
||||
<HelpTooltip
|
||||
@@ -750,10 +750,10 @@ export const ProjectsPage: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
</SortableTh>
|
||||
<SortableTh column="status" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4">
|
||||
<SortableTh column="status" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 w-32 whitespace-nowrap">
|
||||
Status
|
||||
</SortableTh>
|
||||
<th className="py-3 px-4 text-right font-semibold">Resource Management Hub</th>
|
||||
<th className="py-3 px-4 text-right font-semibold whitespace-nowrap min-w-[170px]">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/60 font-medium">
|
||||
@@ -952,8 +952,8 @@ export const ProjectsPage: React.FC = () => {
|
||||
</td>
|
||||
|
||||
{/* Governance Status */}
|
||||
<td className="py-4 px-4" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="space-y-1">
|
||||
<td className="py-4 px-4 whitespace-nowrap" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-32">
|
||||
{(() => {
|
||||
const canApproveThis = canApproveRecord('projects_milestones', p.createdBy);
|
||||
const isSelf = isMaker(p.createdBy);
|
||||
@@ -969,16 +969,16 @@ export const ProjectsPage: React.FC = () => {
|
||||
status: Number(e.target.value) as ApprovalStatus,
|
||||
})
|
||||
}
|
||||
className="text-xs font-semibold bg-[#090d16] border border-slate-700/80 rounded-xl px-2.5 py-1 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
className="w-full text-xs font-semibold bg-[#090d16] border border-slate-700/80 rounded-xl px-2.5 py-1 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed truncate"
|
||||
>
|
||||
{APPROVAL_STATUS_OPTIONS.map((opt) => {
|
||||
const isApprovedOpt = opt.value === ApprovalStatus.Approved;
|
||||
const isOptDisabled = isApprovedOpt && !canApproveThis;
|
||||
let reasonText = '';
|
||||
if (isApprovedOpt && !canApprove('projects_milestones')) {
|
||||
reasonText = ' (Requires Update Permission)';
|
||||
reasonText = ' (Requires Update)';
|
||||
} else if (isApprovedOpt && isSelf) {
|
||||
reasonText = ' (Maker-Checker: Cannot approve own record)';
|
||||
reasonText = ' (Maker-Checker)';
|
||||
}
|
||||
return (
|
||||
<option key={opt.value} value={opt.value} disabled={isOptDisabled}>
|
||||
@@ -993,57 +993,57 @@ export const ProjectsPage: React.FC = () => {
|
||||
</td>
|
||||
|
||||
{/* Row Level Buttons: Milestones, Activities, Indicators, Risks, Edit */}
|
||||
<td className="py-4 px-4 text-right" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-end gap-1.5 flex-wrap">
|
||||
<td className="py-4 px-4 text-right whitespace-nowrap" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-end gap-1.5 flex-nowrap">
|
||||
{/* Milestones Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenMilestones(p, e)}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-semibold text-blue-300 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-lg transition cursor-pointer shadow-sm"
|
||||
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-semibold text-blue-300 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-xl transition cursor-pointer shadow-xs shrink-0"
|
||||
title="Manage Contractual Milestones"
|
||||
>
|
||||
<Flag className="w-3 h-3 text-blue-400" />
|
||||
<span>Milestones</span>
|
||||
<Flag className="w-3.5 h-3.5 text-blue-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Milestones</span>
|
||||
</button>
|
||||
|
||||
{/* Activities Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenActivities(p, e)}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-semibold text-cyan-300 hover:text-white bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 rounded-lg transition cursor-pointer shadow-sm"
|
||||
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-semibold text-cyan-300 hover:text-white bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 rounded-xl transition cursor-pointer shadow-xs shrink-0"
|
||||
title="Manage Work Breakdown Activities"
|
||||
>
|
||||
<Briefcase className="w-3 h-3 text-cyan-400" />
|
||||
<span>Activities</span>
|
||||
<Briefcase className="w-3.5 h-3.5 text-cyan-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Activities</span>
|
||||
</button>
|
||||
|
||||
{/* Indicators Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenIndicators(p, e)}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-semibold text-indigo-300 hover:text-white bg-indigo-500/10 hover:bg-indigo-500/20 border border-indigo-500/30 rounded-lg transition cursor-pointer shadow-sm"
|
||||
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-semibold text-indigo-300 hover:text-white bg-indigo-500/10 hover:bg-indigo-500/20 border border-indigo-500/30 rounded-xl transition cursor-pointer shadow-xs shrink-0"
|
||||
title="Manage M&E Performance Indicators"
|
||||
>
|
||||
<Target className="w-3 h-3 text-indigo-400" />
|
||||
<span>Indicators</span>
|
||||
<Target className="w-3.5 h-3.5 text-indigo-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Indicators</span>
|
||||
</button>
|
||||
|
||||
{/* Risks Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenRisks(p, e)}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-semibold text-rose-300 hover:text-white bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/30 rounded-lg transition cursor-pointer shadow-sm"
|
||||
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-semibold text-rose-300 hover:text-white bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/30 rounded-xl transition cursor-pointer shadow-xs shrink-0"
|
||||
title="Manage Risk Register (ISO 31000)"
|
||||
>
|
||||
<ShieldAlert className="w-3 h-3 text-rose-400" />
|
||||
<span>Risks</span>
|
||||
<ShieldAlert className="w-3.5 h-3.5 text-rose-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Risks</span>
|
||||
</button>
|
||||
|
||||
{/* Edit Project Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenEditProject(p, e)}
|
||||
className="p-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 transition cursor-pointer"
|
||||
className="p-1.5 rounded-xl text-slate-400 hover:text-white hover:bg-slate-800 border border-transparent hover:border-slate-700/60 transition cursor-pointer shrink-0"
|
||||
title="Edit Project Scope & Schedule"
|
||||
>
|
||||
<Edit2 className="w-3.5 h-3.5" />
|
||||
|
||||
Reference in New Issue
Block a user