Refactored project view into modal
Elevated toast message priority
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
SlidersHorizontal,
|
||||
Eye,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
getProjects,
|
||||
@@ -57,7 +58,7 @@ import {
|
||||
APPROVAL_STATUS_OPTIONS,
|
||||
getUserEmail,
|
||||
} from '../lib/formatters';
|
||||
import { ProjectExpandedRow } from '../components/projects/ProjectExpandedRow';
|
||||
import { ProjectViewModal } from '../components/projects/ProjectViewModal';
|
||||
import { ProjectFormModal } from '../components/projects/ProjectFormModal';
|
||||
import { ProjectMilestonesModal } from '../components/projects/ProjectMilestonesModal';
|
||||
import { ProjectActivitiesModal } from '../components/projects/ProjectActivitiesModal';
|
||||
@@ -158,8 +159,9 @@ export const ProjectsPage: React.FC = () => {
|
||||
const [selectedSectorFilter, setSelectedSectorFilter] = useState<string | number | 'all'>('all');
|
||||
const [selectedStatusFilter, setSelectedStatusFilter] = useState<string | number | 'all'>('all');
|
||||
|
||||
// Expanded Rows State (store project IDs)
|
||||
const [expandedRowIds, setExpandedRowIds] = useState<Set<string | number>>(new Set());
|
||||
// Active Project View Modal State
|
||||
const [viewingProject, setViewingProject] = useState<Project | null>(null);
|
||||
const [isProjectViewModalOpen, setIsProjectViewModalOpen] = useState(false);
|
||||
|
||||
// Modals Management State
|
||||
const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
|
||||
@@ -369,26 +371,11 @@ export const ProjectsPage: React.FC = () => {
|
||||
customExtractors
|
||||
);
|
||||
|
||||
// Toggle Single Row Expansion
|
||||
const toggleRow = (id: string | number) => {
|
||||
setExpandedRowIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
// Expand / Collapse All Rows
|
||||
const toggleAllRows = () => {
|
||||
if (expandedRowIds.size === sortedProjects.length && sortedProjects.length > 0) {
|
||||
setExpandedRowIds(new Set());
|
||||
} else {
|
||||
setExpandedRowIds(new Set(sortedProjects.map((p) => p.id)));
|
||||
}
|
||||
// Handler to open comprehensive Project View Modal
|
||||
const handleOpenProjectView = (p: Project, e?: React.MouseEvent) => {
|
||||
if (e) e.stopPropagation();
|
||||
setViewingProject(p);
|
||||
setIsProjectViewModalOpen(true);
|
||||
};
|
||||
|
||||
// Handlers to open resource modals
|
||||
@@ -447,7 +434,7 @@ export const ProjectsPage: React.FC = () => {
|
||||
}, [projects, milestonesByProject]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 max-w-7xl mx-auto pb-12">
|
||||
<div className="space-y-6 w-full max-w-7xl 2xl:max-w-[1600px] mx-auto pb-12">
|
||||
{/* Header Banner */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-gradient-to-r from-[#0d1322] via-[#0f172a] to-[#0d1322] p-6 sm:p-8 rounded-3xl border border-slate-800/80 shadow-2xl relative overflow-hidden">
|
||||
<div className="absolute -right-10 -bottom-10 w-60 h-60 bg-blue-600/10 rounded-full blur-3xl pointer-events-none" />
|
||||
@@ -703,43 +690,30 @@ export const ProjectsPage: React.FC = () => {
|
||||
<h3 className="text-sm font-bold text-white uppercase tracking-wider">
|
||||
Funded Projects Directory ({sortedProjects.length})
|
||||
</h3>
|
||||
<span className="text-xs text-slate-500 hidden sm:inline">
|
||||
• Click any row to expand the Gantt timeline & resource details
|
||||
<span className="text-xs text-slate-400 hidden sm:inline">
|
||||
• Click "View Project" or project row to open the Project View modal & Gantt timeline
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleAllRows}
|
||||
className="text-xs font-semibold text-slate-300 hover:text-white bg-slate-800/80 hover:bg-slate-700/80 border border-slate-700/80 px-3 py-1.5 rounded-xl transition cursor-pointer"
|
||||
>
|
||||
{expandedRowIds.size === sortedProjects.length && sortedProjects.length > 0
|
||||
? 'Collapse All'
|
||||
: 'Expand All'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Projects Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-xs text-slate-300 border-collapse min-w-[960px]">
|
||||
<div className="overflow-x-auto scrollbar-thin">
|
||||
<table className="w-full text-left text-xs text-slate-300 border-collapse min-w-full">
|
||||
<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 min-w-[180px]">
|
||||
<SortableTh column="name" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-3 min-w-[160px] max-w-[240px]">
|
||||
Project Name & ID
|
||||
</SortableTh>
|
||||
<SortableTh column="programme" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[140px]">
|
||||
<SortableTh column="programme" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-3 w-36">
|
||||
Parent Programme
|
||||
</SortableTh>
|
||||
<SortableTh column="duration" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[140px] whitespace-nowrap">
|
||||
<SortableTh column="duration" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-3 w-36 whitespace-nowrap">
|
||||
Schedule (Timeline)
|
||||
</SortableTh>
|
||||
<SortableTh column="progress" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 w-28 whitespace-nowrap">
|
||||
<SortableTh column="progress" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-3 w-28 whitespace-nowrap">
|
||||
WBS Progress
|
||||
</SortableTh>
|
||||
<SortableTh column="risk" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 min-w-[125px] whitespace-nowrap">
|
||||
<SortableTh column="risk" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-3 w-32 whitespace-nowrap">
|
||||
<div className="flex items-center gap-1">
|
||||
<span>Schedule Risk</span>
|
||||
<HelpTooltip
|
||||
@@ -750,16 +724,16 @@ export const ProjectsPage: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
</SortableTh>
|
||||
<SortableTh column="status" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-4 w-32 whitespace-nowrap">
|
||||
<SortableTh column="status" currentColumn={sortColumn} direction={sortDirection} onSort={handleSort} className="py-3 px-3 w-32 whitespace-nowrap">
|
||||
Status
|
||||
</SortableTh>
|
||||
<th className="py-3 px-4 text-right font-semibold whitespace-nowrap min-w-[170px]">Actions</th>
|
||||
<th className="py-3 px-3 text-right font-semibold whitespace-nowrap w-44">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/60 font-medium">
|
||||
{loadingProjects ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="p-12 text-center text-slate-400 text-xs">
|
||||
<td colSpan={7} className="p-12 text-center text-slate-400 text-xs">
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
<div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||
<span>Loading project directory...</span>
|
||||
@@ -768,7 +742,7 @@ export const ProjectsPage: React.FC = () => {
|
||||
</tr>
|
||||
) : sortedProjects.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="p-12 text-center text-slate-400">
|
||||
<td colSpan={7} className="p-12 text-center text-slate-400">
|
||||
<div className="flex flex-col items-center justify-center space-y-3">
|
||||
<FolderKanban className="w-10 h-10 text-slate-600" />
|
||||
<p className="text-sm font-semibold text-slate-300">No matching projects found</p>
|
||||
@@ -789,7 +763,6 @@ export const ProjectsPage: React.FC = () => {
|
||||
</tr>
|
||||
) : (
|
||||
sortedProjects.map((p) => {
|
||||
const isExpanded = expandedRowIds.has(p.id);
|
||||
const prog = programmeMap.get(p.programmeId);
|
||||
const milestoneStats = milestonesByProject.get(p.id) || { total: 0, verified: 0 };
|
||||
const progressPct =
|
||||
@@ -799,58 +772,50 @@ export const ProjectsPage: React.FC = () => {
|
||||
const scheduleRisk = evaluateProjectScheduleRisk(p, milestoneStats);
|
||||
|
||||
return (
|
||||
<React.Fragment key={p.id}>
|
||||
{/* Main Project Row */}
|
||||
<tr
|
||||
onClick={() => toggleRow(p.id)}
|
||||
className={`transition-colors cursor-pointer group ${
|
||||
isExpanded
|
||||
? 'bg-[#0f172a]/90 text-white'
|
||||
: 'hover:bg-slate-900/60 text-slate-300'
|
||||
}`}
|
||||
>
|
||||
{/* Expand/Collapse Chevron */}
|
||||
<td className="py-4 px-3 text-center">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand project row"
|
||||
className="p-1 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 transition"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-blue-400" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 group-hover:text-white" />
|
||||
)}
|
||||
</button>
|
||||
</td>
|
||||
|
||||
{/* Project Name & Meta */}
|
||||
<td className="py-4 px-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-bold text-sm text-white group-hover:text-blue-300 transition">
|
||||
{p.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap text-[11px]">
|
||||
<span className="text-[10px] font-mono text-blue-400 bg-blue-500/10 px-1.5 py-0.2 rounded border border-blue-500/25">
|
||||
#{p.id}
|
||||
</span>
|
||||
<span className="bg-slate-900/90 border border-slate-700/80 px-2 py-0.5 rounded text-slate-300 text-[10px]">
|
||||
{getIndustrySectorLabel(p.sector)}
|
||||
</span>
|
||||
<span className="text-slate-500">•</span>
|
||||
<span className="text-slate-400 text-[10px]">
|
||||
{getProjectTypeLabel(p.projectType)}
|
||||
</span>
|
||||
</div>
|
||||
<tr
|
||||
key={p.id}
|
||||
onClick={() => handleOpenProjectView(p)}
|
||||
className="transition-colors cursor-pointer hover:bg-slate-900/60 text-slate-300 group"
|
||||
>
|
||||
{/* Project Name & Meta */}
|
||||
<td className="py-3.5 px-3 max-w-[240px]">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="font-bold text-sm text-white group-hover:text-blue-300 transition line-clamp-1"
|
||||
title={p.name || `Project #${p.id}`}
|
||||
>
|
||||
{p.name || `Project #${p.id}`}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<div className="flex items-center gap-1.5 flex-wrap text-[11px]">
|
||||
<span className="text-[10px] font-mono text-blue-400 bg-blue-500/10 px-1.5 py-0.2 rounded border border-blue-500/25 shrink-0">
|
||||
#{p.id}
|
||||
</span>
|
||||
<span
|
||||
className="bg-slate-900/90 border border-slate-700/80 px-2 py-0.5 rounded text-slate-300 text-[10px] truncate max-w-[120px]"
|
||||
title={getIndustrySectorLabel(p.sector)}
|
||||
>
|
||||
{getIndustrySectorLabel(p.sector)}
|
||||
</span>
|
||||
<span className="text-slate-500">•</span>
|
||||
<span
|
||||
className="text-slate-400 text-[10px] truncate max-w-[120px]"
|
||||
title={getProjectTypeLabel(p.projectType)}
|
||||
>
|
||||
{getProjectTypeLabel(p.projectType)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Parent Programme */}
|
||||
<td className="py-4 px-4">
|
||||
<td className="py-3.5 px-3 w-36">
|
||||
<div className="space-y-0.5">
|
||||
<span className="text-xs font-semibold text-slate-200 block truncate max-w-[160px]">
|
||||
<span
|
||||
className="text-xs font-semibold text-slate-200 block truncate max-w-[130px]"
|
||||
title={prog?.name || `Programme #${p.programmeId}`}
|
||||
>
|
||||
{prog?.name || `Prog #${p.programmeId}`}
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-500 font-mono">
|
||||
@@ -860,8 +825,8 @@ export const ProjectsPage: React.FC = () => {
|
||||
</td>
|
||||
|
||||
{/* Schedule (Dates) */}
|
||||
<td className="py-4 px-4">
|
||||
<div className="space-y-0.5 font-mono text-[11px] text-slate-400">
|
||||
<td className="py-3.5 px-3 w-36">
|
||||
<div className="space-y-0.5 font-mono text-[11px] text-slate-400 whitespace-nowrap">
|
||||
<div className="flex items-center gap-1.5 text-slate-300">
|
||||
<Calendar className="w-3.5 h-3.5 text-slate-500 shrink-0" />
|
||||
<span>{formatDate(p.startedAt)} → {formatDate(p.endedAt)}</span>
|
||||
@@ -882,8 +847,8 @@ export const ProjectsPage: React.FC = () => {
|
||||
</td>
|
||||
|
||||
{/* Milestone Progress */}
|
||||
<td className="py-4 px-4">
|
||||
<div className="space-y-1 w-32">
|
||||
<td className="py-3.5 px-3 w-28">
|
||||
<div className="space-y-1 w-24">
|
||||
<div className="flex justify-between text-[11px] font-semibold">
|
||||
<span className={progressPct >= 100 ? 'text-emerald-400' : 'text-blue-400'}>
|
||||
{progressPct}%
|
||||
@@ -906,7 +871,7 @@ export const ProjectsPage: React.FC = () => {
|
||||
</td>
|
||||
|
||||
{/* Schedule Risk Column with Icon & Tooltip */}
|
||||
<td className="py-4 px-4" onClick={(e) => e.stopPropagation()}>
|
||||
<td className="py-3.5 px-3 w-32" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{scheduleRisk.isOverdue ? (
|
||||
<span
|
||||
@@ -952,8 +917,8 @@ export const ProjectsPage: React.FC = () => {
|
||||
</td>
|
||||
|
||||
{/* Governance Status */}
|
||||
<td className="py-4 px-4 whitespace-nowrap" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-32">
|
||||
<td className="py-3.5 px-3 whitespace-nowrap w-32" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-28">
|
||||
{(() => {
|
||||
const canApproveThis = canApproveRecord('projects_milestones', p.createdBy);
|
||||
const isSelf = isMaker(p.createdBy);
|
||||
@@ -969,7 +934,7 @@ export const ProjectsPage: React.FC = () => {
|
||||
status: Number(e.target.value) as ApprovalStatus,
|
||||
})
|
||||
}
|
||||
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"
|
||||
className="w-full text-xs font-semibold bg-[#090d16] border border-slate-700/80 rounded-xl px-2 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;
|
||||
@@ -992,51 +957,62 @@ export const ProjectsPage: React.FC = () => {
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Row Level Buttons: Milestones, Activities, Indicators, Risks, Edit */}
|
||||
<td className="py-4 px-4 text-right whitespace-nowrap" onClick={(e) => e.stopPropagation()}>
|
||||
{/* Row Level Buttons: View Project, Milestones, Activities, Indicators, Risks, Edit */}
|
||||
<td className="py-3.5 px-3 text-right whitespace-nowrap w-44" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-end gap-1.5 flex-nowrap">
|
||||
{/* Primary View Project Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenProjectView(p, e)}
|
||||
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-xl transition cursor-pointer shadow-sm shadow-blue-600/25 shrink-0"
|
||||
title="Open Project View & Interactive Gantt Timeline"
|
||||
>
|
||||
<Eye className="w-3.5 h-3.5" />
|
||||
<span>View Project</span>
|
||||
</button>
|
||||
|
||||
{/* Milestones Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenMilestones(p, e)}
|
||||
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"
|
||||
className="inline-flex items-center gap-1 p-1.5 2xl:px-2.5 2xl: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.5 h-3.5 text-blue-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Milestones</span>
|
||||
<span className="hidden 2xl:inline">Milestones</span>
|
||||
</button>
|
||||
|
||||
{/* Activities Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenActivities(p, e)}
|
||||
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"
|
||||
className="inline-flex items-center gap-1 p-1.5 2xl:px-2.5 2xl: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.5 h-3.5 text-cyan-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Activities</span>
|
||||
<span className="hidden 2xl:inline">Activities</span>
|
||||
</button>
|
||||
|
||||
{/* Indicators Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenIndicators(p, e)}
|
||||
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"
|
||||
className="inline-flex items-center gap-1 p-1.5 2xl:px-2.5 2xl: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.5 h-3.5 text-indigo-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Indicators</span>
|
||||
<span className="hidden 2xl:inline">Indicators</span>
|
||||
</button>
|
||||
|
||||
{/* Risks Modal Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenRisks(p, e)}
|
||||
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"
|
||||
className="inline-flex items-center gap-1 p-1.5 2xl:px-2.5 2xl: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.5 h-3.5 text-rose-400 shrink-0" />
|
||||
<span className="hidden xl:inline">Risks</span>
|
||||
<span className="hidden 2xl:inline">Risks</span>
|
||||
</button>
|
||||
|
||||
{/* Edit Project Button */}
|
||||
@@ -1051,24 +1027,7 @@ export const ProjectsPage: React.FC = () => {
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Expanded View Content */}
|
||||
{isExpanded && (
|
||||
<tr>
|
||||
<td colSpan={8} className="p-0 border-b border-blue-500/20">
|
||||
<ProjectExpandedRow
|
||||
project={p}
|
||||
onOpenMilestonesModal={() => handleOpenMilestones(p)}
|
||||
onOpenActivitiesModal={() => handleOpenActivities(p)}
|
||||
onOpenIndicatorsModal={() => handleOpenIndicators(p)}
|
||||
onOpenRisksModal={() => handleOpenRisks(p)}
|
||||
onOpenEditProject={() => handleOpenEditProject(p)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
@@ -1076,6 +1035,47 @@ export const ProjectsPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Project View Modal (Comprehensive Console, Navigable Gantt Timeline & WBS) */}
|
||||
<ProjectViewModal
|
||||
isOpen={isProjectViewModalOpen}
|
||||
onClose={() => {
|
||||
setIsProjectViewModalOpen(false);
|
||||
setViewingProject(null);
|
||||
}}
|
||||
project={viewingProject}
|
||||
programmeMap={programmeMap}
|
||||
onOpenMilestonesModal={() => {
|
||||
if (viewingProject) {
|
||||
setActiveModalProject(viewingProject);
|
||||
setIsMilestonesModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onOpenActivitiesModal={() => {
|
||||
if (viewingProject) {
|
||||
setActiveModalProject(viewingProject);
|
||||
setIsActivitiesModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onOpenIndicatorsModal={() => {
|
||||
if (viewingProject) {
|
||||
setActiveModalProject(viewingProject);
|
||||
setIsIndicatorsModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onOpenRisksModal={() => {
|
||||
if (viewingProject) {
|
||||
setActiveModalProject(viewingProject);
|
||||
setIsRisksModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onOpenEditProject={() => {
|
||||
if (viewingProject) {
|
||||
setEditingProject(viewingProject);
|
||||
setIsProjectModalOpen(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Project Form Modal (Create or Edit) */}
|
||||
<ProjectFormModal
|
||||
isOpen={isProjectModalOpen}
|
||||
|
||||
Reference in New Issue
Block a user