projects
This commit is contained in:
@@ -95,6 +95,8 @@ export function ProjectDetailPage() {
|
||||
const materialExceptionItems = planning
|
||||
? planning.items.filter((item) => item.uncoveredQuantity > 0 || item.recommendedBuildQuantity > 0 || item.recommendedPurchaseQuantity > 0).slice(0, 5)
|
||||
: [];
|
||||
const topBuildRecommendation = planning?.items.find((item) => item.recommendedBuildQuantity > 0) ?? null;
|
||||
const topPurchaseRecommendation = planning?.items.find((item) => item.recommendedPurchaseQuantity > 0) ?? null;
|
||||
const completionPercent = project.rollups.milestoneCount > 0
|
||||
? Math.round((project.rollups.completedMilestoneCount / project.rollups.milestoneCount) * 100)
|
||||
: 0;
|
||||
@@ -180,6 +182,62 @@ export function ProjectDetailPage() {
|
||||
</div>
|
||||
</section>
|
||||
<section className="grid gap-3 xl:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)]">
|
||||
<article 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">Actionable Cockpit</p>
|
||||
<p className="mt-2 text-sm text-muted">Turn current exceptions into purchasing, manufacturing, and planning follow-through.</p>
|
||||
</div>
|
||||
<Link to="/planning/gantt" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Open gantt
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-5 grid gap-3 xl:grid-cols-2">
|
||||
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Build Follow-Through</p>
|
||||
<div className="mt-2 text-base font-bold text-text">{topBuildRecommendation ? topBuildRecommendation.itemSku : "No build recommendation"}</div>
|
||||
<div className="mt-1 text-xs text-muted">
|
||||
{topBuildRecommendation ? `Recommended build qty ${topBuildRecommendation.recommendedBuildQuantity}` : "Planning does not currently recommend a new build."}
|
||||
</div>
|
||||
{topBuildRecommendation && project.salesOrderId ? (
|
||||
<Link
|
||||
to={`/manufacturing/work-orders/new?projectId=${project.id}&itemId=${topBuildRecommendation.itemId}&salesOrderId=${project.salesOrderId}&quantity=${topBuildRecommendation.recommendedBuildQuantity}¬es=${encodeURIComponent(`Project cockpit launch from ${project.projectNumber}`)}`}
|
||||
className="mt-4 inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white"
|
||||
>
|
||||
Launch work order
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="rounded-[18px] border border-line/70 bg-page/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Buy Follow-Through</p>
|
||||
<div className="mt-2 text-base font-bold text-text">{topPurchaseRecommendation ? topPurchaseRecommendation.itemSku : "No buy recommendation"}</div>
|
||||
<div className="mt-1 text-xs text-muted">
|
||||
{topPurchaseRecommendation ? `Recommended buy qty ${topPurchaseRecommendation.recommendedPurchaseQuantity}` : "Planning does not currently recommend a new purchase."}
|
||||
</div>
|
||||
{topPurchaseRecommendation && project.salesOrderId ? (
|
||||
<Link
|
||||
to={`/purchasing/orders/new?planningOrderId=${project.salesOrderId}&itemId=${topPurchaseRecommendation.itemId}`}
|
||||
className="mt-4 inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white"
|
||||
>
|
||||
Launch purchase order
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-3">
|
||||
<Link to={`/manufacturing/work-orders/new?projectId=${project.id}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
New project work order
|
||||
</Link>
|
||||
{project.salesOrderId ? (
|
||||
<Link to={`/sales/orders/${project.salesOrderId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Open sales order
|
||||
</Link>
|
||||
) : null}
|
||||
<Link to="/purchasing/orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Review purchasing
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
<article 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">Linked Purchasing</p><p className="mt-2 text-sm text-muted">Purchase orders and receipts tied back to the project sales order.</p></div>{project.salesOrderId ? <Link to="/purchasing/orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open purchasing</Link> : null}</div>
|
||||
{project.cockpit.purchasing.purchaseOrders.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 linked purchase orders are tied to this project yet.</div> : <div className="mt-6 space-y-3">{project.cockpit.purchasing.purchaseOrders.slice(0, 5).map((purchaseOrder) => (<Link key={purchaseOrder.id} to={`/purchasing/orders/${purchaseOrder.id}`} className="block rounded-[18px] border border-line/70 bg-page/60 p-3 transition hover:bg-page/80"><div className="flex flex-wrap items-start justify-between gap-3"><div><div className="font-semibold text-text">{purchaseOrder.documentNumber}</div><div className="mt-1 text-xs text-muted">{purchaseOrder.vendorName} - {purchaseOrder.status.replaceAll("_", " ")}</div></div><div className="text-right text-xs text-muted"><div>${purchaseOrder.linkedLineValue.toFixed(2)} linked value</div><div>{purchaseOrder.totalReceivedQuantity}/{purchaseOrder.totalOrderedQuantity} received</div></div></div></Link>))}</div>}
|
||||
@@ -256,6 +314,34 @@ export function ProjectDetailPage() {
|
||||
</div>
|
||||
{workOrders.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 work orders are linked to this project yet.</div> : <div className="mt-6 space-y-3">{workOrders.map((workOrder) => (<Link key={workOrder.id} to={`/manufacturing/work-orders/${workOrder.id}`} className="block rounded-[18px] border border-line/70 bg-page/60 p-3 transition hover:bg-page/80"><div className="flex flex-wrap items-center justify-between gap-3"><div><div className="font-semibold text-text">{workOrder.workOrderNumber}</div><div className="mt-1 text-xs text-muted">{workOrder.itemSku} - {workOrder.completedQuantity}/{workOrder.quantity} complete</div></div><div className="text-sm font-semibold text-text">{workOrder.status.replace("_", " ")}</div></div></Link>))}</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">Activity Timeline</p><p className="mt-2 text-sm text-muted">Chronological project, milestone, purchasing, manufacturing, sales, and shipping history.</p></div>
|
||||
</div>
|
||||
{project.timeline.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 timeline activity is available for this project yet.</div>
|
||||
) : (
|
||||
<div className="mt-6 space-y-3">
|
||||
{project.timeline.map((entry) => (
|
||||
<div key={entry.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="text-xs font-semibold uppercase tracking-[0.16em] text-muted">{entry.sourceType}</div>
|
||||
<div className="mt-1 font-semibold text-text">
|
||||
{entry.href ? <Link to={entry.href} className="hover:text-brand">{entry.title}</Link> : entry.title}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-muted">{entry.detail}</div>
|
||||
</div>
|
||||
<div className="text-right text-xs text-muted">
|
||||
<div>{new Date(entry.createdAt).toLocaleString()}</div>
|
||||
<div>{entry.actorName || "System"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<FileAttachmentsPanel ownerType="PROJECT" ownerId={project.id} eyebrow="Project Documents" title="Program file hub" description="Store drawings, revision references, correspondence, and support files directly on the project record." emptyMessage="No project files have been uploaded yet." />
|
||||
<div className="rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user