projects milestones

This commit is contained in:
2026-03-17 07:34:08 -05:00
parent c3f0adc676
commit c1f6386e7d
13 changed files with 510 additions and 46 deletions

View File

@@ -8,6 +8,7 @@ import { Link, useParams } from "react-router-dom";
import { FileAttachmentsPanel } from "../../components/FileAttachmentsPanel";
import { useAuth } from "../../auth/AuthProvider";
import { api, ApiError } from "../../lib/api";
import { projectMilestoneStatusPalette } from "./config";
import { ProjectPriorityBadge } from "./ProjectPriorityBadge";
import { ProjectStatusBadge } from "./ProjectStatusBadge";
@@ -73,6 +74,27 @@ export function ProjectDetailPage() {
<article className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Due Date</p><div className="mt-2 text-base font-bold text-text">{project.dueDate ? new Date(project.dueDate).toLocaleDateString() : "Not set"}</div></article>
<article className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Created</p><div className="mt-2 text-base font-bold text-text">{new Date(project.createdAt).toLocaleDateString()}</div></article>
</section>
<section className="grid gap-3 xl:grid-cols-4">
<article className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Milestones</p>
<div className="mt-2 text-base font-bold text-text">{project.rollups.completedMilestoneCount}/{project.rollups.milestoneCount}</div>
<div className="mt-1 text-xs text-muted">{project.rollups.openMilestoneCount} open</div>
</article>
<article className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Overdue Milestones</p>
<div className="mt-2 text-base font-bold text-text">{project.rollups.overdueMilestoneCount}</div>
</article>
<article className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Linked Work Orders</p>
<div className="mt-2 text-base font-bold text-text">{project.rollups.workOrderCount}</div>
<div className="mt-1 text-xs text-muted">{project.rollups.activeWorkOrderCount} active</div>
</article>
<article className="rounded-[18px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Overdue Work Orders</p>
<div className="mt-2 text-base font-bold text-text">{project.rollups.overdueWorkOrderCount}</div>
<div className="mt-1 text-xs text-muted">{project.rollups.completedWorkOrderCount} complete</div>
</article>
</section>
<div className="grid gap-3 xl:grid-cols-[minmax(0,1.05fr)_minmax(320px,0.95fr)]">
<article className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Customer Linkage</p>
@@ -104,6 +126,48 @@ export function ProjectDetailPage() {
</div>
</div>
</section>
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Milestones</p>
<p className="mt-2 text-sm text-muted">Track project checkpoints, blockers, and completion progress.</p>
</div>
{canManage ? (
<Link to={`/projects/${project.id}/edit`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Edit milestones
</Link>
) : null}
</div>
{project.milestones.length === 0 ? (
<div className="mt-6 rounded-[18px] border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
No milestones are defined for this project yet.
</div>
) : (
<div className="mt-6 space-y-3">
{project.milestones.map((milestone) => (
<div key={milestone.id} className="rounded-[18px] border border-line/70 bg-page/60 p-3">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<div className="font-semibold text-text">{milestone.title}</div>
<div className="mt-2 flex flex-wrap items-center gap-2">
<span className={`inline-flex rounded-full px-2 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${projectMilestoneStatusPalette[milestone.status]}`}>
{milestone.status.replace("_", " ")}
</span>
<span className="text-xs text-muted">
Due {milestone.dueDate ? new Date(milestone.dueDate).toLocaleDateString() : "not scheduled"}
</span>
{milestone.completedAt ? (
<span className="text-xs text-muted">Completed {new Date(milestone.completedAt).toLocaleDateString()}</span>
) : null}
</div>
{milestone.notes ? <div className="mt-3 whitespace-pre-line text-sm text-text">{milestone.notes}</div> : null}
</div>
</div>
</div>
))}
</div>
)}
</section>
{planning ? (
<section className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Material Readiness</p>