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
@@ -5,15 +5,25 @@ import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
import { formatMoney } from "~/lib/format.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
function StatCard({ label, value, sub, info }: { label: string; value: string | number; sub?: string; info?: React.ReactNode }) {
function StatCard({
label,
value,
sub,
info,
}: {
label: string;
value: string | number;
sub?: string;
info?: React.ReactNode;
}) {
return (
<div className="flex flex-col gap-1">
<span className="text-xs font-medium text-gray-500 flex items-center">
<div className="rounded-2xl border border-gray-200 bg-white/80 p-4 shadow-sm dark:border-gray-700 dark:bg-gray-900/70">
<span className="flex items-center text-xs font-semibold uppercase tracking-[0.18em] text-gray-500">
{label}
{info && <InfoTooltip content={info} />}
</span>
<span className="text-2xl font-bold text-gray-900">{value}</span>
{sub && <span className="text-xs text-gray-400">{sub}</span>}
<span className="mt-2 text-2xl font-semibold text-gray-900 dark:text-gray-50">{value}</span>
{sub && <span className="mt-1 text-xs text-gray-500 dark:text-gray-400">{sub}</span>}
</div>
);
}
@@ -29,7 +39,10 @@ export function StatCardsWidget(_props: Partial<WidgetProps> = {}) {
return (
<div className="grid grid-cols-2 gap-3 h-full animate-pulse">
{[...Array(4)].map((_, i) => (
<div key={i} className="rounded-xl bg-gray-100 dark:bg-gray-800 p-4 flex flex-col gap-2">
<div
key={i}
className="rounded-2xl border border-gray-200 bg-gray-100 p-4 dark:border-gray-700 dark:bg-gray-800"
>
<div className="h-3 w-20 bg-gray-200 dark:bg-gray-700 rounded" />
<div className="h-7 w-16 bg-gray-300 dark:bg-gray-600 rounded" />
<div className="h-2 w-24 bg-gray-200 dark:bg-gray-700 rounded" />