feat: dashboard overhaul, chargeability reports, dispo import enhancements, UI polish

Dashboard: expanded chargeability widget, resource/project table widgets
with sorting and filters, stat cards with formatMoney integration.

Chargeability: new report client with filtering, chargeability-bookings
use case, updated dashboard overview logic.

Dispo import: TBD project handling, parse-dispo-matrix improvements,
stage-dispo-projects resource value scores, new tests.

Estimates: CommercialTermsEditor component, commercial-terms engine
module, expanded estimate schemas and types.

UI: AppShell navigation updates, timeline filter/toolbar enhancements,
role management improvements, signin page redesign, Tailwind/globals
polish, SystemSettings SMTP section, anonymization support.

Tests: new router tests (anonymization, chargeability, effort-rule,
entitlement, estimate, experience-multiplier, notification, resource,
staffing, vacation).

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-14 23:29:07 +01:00
parent ad0855902b
commit 625a842d89
74 changed files with 11680 additions and 1583 deletions
@@ -0,0 +1,53 @@
import type { Prisma } from "@planarchy/db";
import type { AssignmentBookingWithFallback } from "./list-assignment-bookings.js";
type ChargeabilityProjectLike = AssignmentBookingWithFallback["project"];
type ChargeabilityBookingLike = Pick<AssignmentBookingWithFallback, "status" | "project">;
function asObject(value: Prisma.JsonValue | null | undefined): Record<string, unknown> | null {
if (value === null || value === undefined || typeof value !== "object" || Array.isArray(value)) {
return null;
}
return value as Record<string, unknown>;
}
export function isImportedTbdDraftProject(project: ChargeabilityProjectLike): boolean {
if (project.status !== "DRAFT") {
return false;
}
const dynamicFields = asObject(project.dynamicFields);
const dispoImport = asObject(dynamicFields?.["dispoImport"] as Prisma.JsonValue | undefined);
return dispoImport?.["isTbd"] === true;
}
export function isChargeabilityRelevantProject(
project: ChargeabilityProjectLike,
includeProposed: boolean,
): boolean {
if (project.status === "ACTIVE") {
return true;
}
if (project.status === "CANCELLED") {
return false;
}
return includeProposed && isImportedTbdDraftProject(project);
}
export function isChargeabilityActualBooking(
booking: ChargeabilityBookingLike,
includeProposed: boolean,
): boolean {
if (!isChargeabilityRelevantProject(booking.project, includeProposed)) {
return false;
}
return (
booking.status === "CONFIRMED" ||
booking.status === "ACTIVE" ||
(includeProposed && booking.status === "PROPOSED")
);
}
@@ -25,6 +25,7 @@ export interface AssignmentBookingWithFallback {
shortCode: string;
status: string;
orderType: string;
dynamicFields: Prisma.JsonValue | null;
};
resource: {
id: string;
@@ -67,7 +68,14 @@ export async function listAssignmentBookings(
dailyCostCents: true,
status: true,
project: {
select: { id: true, name: true, shortCode: true, status: true, orderType: true },
select: {
id: true,
name: true,
shortCode: true,
status: true,
orderType: true,
dynamicFields: true,
},
},
resource: {
select: { id: true, displayName: true, chapter: true },