feat: shared widget filter system for all dashboard widgets

Shared infrastructure:
- WidgetFilterBar: declarative filter component (search, select, toggle)
- useWidgetFilterOptions: cached hook for clients, countries, roles, chapters

Widget integration (5 widgets):
- ProjectHealth: search (name) + select (client)
- BudgetForecast: search (name) + select (client)
- Chargeability: select (chapter) + toggle (include proposed)
- SkillGap: search (skill name)
- TopValue: select (chapter)

Backend: added clientId/clientName to ProjectHealth and BudgetForecast
query results for client-based filtering.

Filter state persisted via widget config (survives page reload).
All filters use compact 11px inputs with full dark theme support.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-23 09:21:46 +01:00
parent 47b2aeec72
commit 208f866d68
10 changed files with 771 additions and 462 deletions
@@ -4,6 +4,8 @@ import { calculateInclusiveDays, MILLISECONDS_PER_DAY } from "./shared.js";
export interface BudgetForecastRow {
projectName: string;
shortCode: string;
clientId: string | null;
clientName: string | null;
budgetCents: number;
spentCents: number;
burnRate: number;
@@ -23,6 +25,8 @@ export async function getDashboardBudgetForecast(
budgetCents: true,
startDate: true,
endDate: true,
clientId: true,
client: { select: { name: true } },
},
});
@@ -86,6 +90,8 @@ export async function getDashboardBudgetForecast(
return {
projectName: p.name,
shortCode: p.shortCode,
clientId: p.clientId,
clientName: p.client?.name ?? null,
budgetCents: p.budgetCents,
spentCents,
burnRate,
@@ -4,6 +4,8 @@ import { calculateInclusiveDays } from "./shared.js";
export interface ProjectHealthRow {
projectName: string;
shortCode: string;
clientId: string | null;
clientName: string | null;
budgetHealth: number;
staffingHealth: number;
timelineHealth: number;
@@ -21,6 +23,8 @@ export async function getDashboardProjectHealth(
shortCode: true,
budgetCents: true,
endDate: true,
clientId: true,
client: { select: { name: true } },
demandRequirements: {
select: {
id: true,
@@ -92,6 +96,8 @@ export async function getDashboardProjectHealth(
return {
projectName: p.name,
shortCode: p.shortCode,
clientId: p.clientId,
clientName: p.client?.name ?? null,
budgetHealth,
staffingHealth,
timelineHealth,