feat: Sprint 4 — scenario planner, report builder, comments, dashboard widgets
What-If Scenario Planner (G5): - New /projects/[id]/scenario page with side-by-side baseline vs scenario - simulate mutation: pure cost/hours/headcount/utilization computation - apply mutation: creates real PROPOSED assignments from scenario - Impact cards: cost delta, hours delta, headcount, skill coverage % - Per-resource utilization impact table with over-allocation warnings - "What-If" button added to project detail page Custom Report Builder (G7): - New /reports/builder page with full config panel - Entity selector (resource/project/assignment), column picker, filter builder - Dynamic Prisma query with eq/neq/gt/lt/contains/in operators - Sortable results table with pagination (50/page) - CSV export via exportReport mutation - Sidebar nav link under Analytics Collaboration Layer (G8): - Comment model in Prisma (entityType/entityId, replies, @mentions, resolved) - comment router: list, count, create, resolve, delete - @mention parsing with notification creation + SSE delivery - CommentInput with @mention autocomplete (arrow nav, Enter/Tab confirm) - CommentThread with avatar, timestamp, reply, resolve, delete - Integrated as "Comments" tab in estimate workspace with count badge Dashboard Widgets: - BudgetForecastWidget: progress bars per project, burn rate, exhaustion date - SkillGapWidget: supply vs demand per skill, shortage/surplus indicators - ProjectHealthWidget: 3-dimension health circles + composite score - 3 new application use-cases + dashboard router queries - All registered in widget-registry with lazy imports Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -66,6 +66,11 @@ const ExportsTab = dynamic(
|
||||
{ loading: TabSkeleton },
|
||||
);
|
||||
|
||||
const CommentThread = dynamic(
|
||||
() => import("~/components/comments/CommentThread.js").then((mod) => ({ default: mod.CommentThread })),
|
||||
{ loading: TabSkeleton },
|
||||
);
|
||||
|
||||
const TABS: Array<{ id: WorkspaceTab; label: string }> = [
|
||||
{ id: "overview", label: "Overview" },
|
||||
{ id: "assumptions", label: "Assumptions" },
|
||||
@@ -75,6 +80,7 @@ const TABS: Array<{ id: WorkspaceTab; label: string }> = [
|
||||
{ id: "phasing", label: "Phasing" },
|
||||
{ id: "versions", label: "Versions" },
|
||||
{ id: "exports", label: "Exports" },
|
||||
{ id: "comments", label: "Comments" },
|
||||
];
|
||||
|
||||
function EmptyState({ children }: { children: React.ReactNode }) {
|
||||
@@ -127,6 +133,12 @@ export function EstimateWorkspaceClient({ estimateId }: { estimateId: string })
|
||||
const createExportMutation = trpc.estimate.createExport.useMutation();
|
||||
const createPlanningHandoffMutation = trpc.estimate.createPlanningHandoff.useMutation();
|
||||
|
||||
const commentCountQuery = trpc.comment.count.useQuery(
|
||||
{ entityType: "estimate", entityId: estimateId },
|
||||
{ staleTime: 30_000 },
|
||||
);
|
||||
const commentCount = commentCountQuery.data ?? 0;
|
||||
|
||||
const estimate = (detailQuery.data as EstimateWorkspaceView | undefined) ?? null;
|
||||
const hasWorkingVersion = estimate?.versions.some((version) => version.status === "WORKING") ?? false;
|
||||
const editableTab = tab === "overview" || tab === "assumptions" || tab === "scope" || tab === "staffing";
|
||||
@@ -296,6 +308,11 @@ export function EstimateWorkspaceClient({ estimateId }: { estimateId: string })
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
{item.id === "comments" && commentCount > 0 && (
|
||||
<span className="ml-1.5 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-brand-100 dark:bg-sky-800 px-1.5 text-xs font-semibold text-brand-700 dark:text-sky-200">
|
||||
{commentCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -342,6 +359,14 @@ export function EstimateWorkspaceClient({ estimateId }: { estimateId: string })
|
||||
isCreatingExport={createExportMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
{tab === "comments" && (
|
||||
<div className="rounded-3xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-6">
|
||||
<h2 className="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-50">
|
||||
Comments
|
||||
</h2>
|
||||
<CommentThread entityType="estimate" entityId={estimate.id} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user