fix: add InfoTooltip column descriptions + dark theme to 3 new widgets
Budget Forecast Widget: - Project: "Active projects with a defined budget" - Budget Usage: "Percentage of total budget consumed by current allocations" - Burn/mo: "Monthly burn rate based on currently active allocations" - Exhaustion: "Projected date when budget will be fully consumed" Project Health Widget: - Project: "Active projects scored across three health dimensions" - B / S / T: "Budget health, Staffing health, Timeline health" - Score: "Composite score: average of Budget, Staffing, Timeline (0-100)" - Score badge: dark theme variants for green/amber/red Skill Gap Widget: - Skill: "Skills required by open demand positions" - Demand: "Number of unfilled demand requirements needing this skill" - Supply: "Number of active resources with this skill at proficiency 3+" - Gap: "Supply minus Demand: negative = shortage, positive = surplus" All three: added dark:bg-gray-800/50 thead, dark:divide-gray-800 tbody, dark:hover:bg-gray-800/30 rows, dark:text-gray-100/300/400 text colors. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { trpc } from "~/lib/trpc/client.js";
|
import { trpc } from "~/lib/trpc/client.js";
|
||||||
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
|
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
|
||||||
|
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||||
|
|
||||||
function colorClass(pct: number): string {
|
function colorClass(pct: number): string {
|
||||||
if (pct > 90) return "bg-red-500";
|
if (pct > 90) return "bg-red-500";
|
||||||
@@ -48,24 +49,32 @@ export function BudgetForecastWidget(_props: WidgetProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="overflow-auto h-full">
|
<div className="overflow-auto h-full">
|
||||||
<table className="w-full text-xs">
|
<table className="w-full text-xs">
|
||||||
<thead className="bg-gray-50 sticky top-0">
|
<thead className="bg-gray-50 dark:bg-gray-800/50 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left font-medium text-gray-500">Project</th>
|
<th className="px-3 py-2 text-left font-medium text-gray-500 dark:text-gray-400">
|
||||||
<th className="px-3 py-2 text-left font-medium text-gray-500">Budget Usage</th>
|
Project <InfoTooltip content="Active projects with a defined budget" />
|
||||||
<th className="px-3 py-2 text-right font-medium text-gray-500">Burn/mo</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-medium text-gray-500">Exhaustion</th>
|
<th className="px-3 py-2 text-left font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Budget Usage <InfoTooltip content="Percentage of total budget consumed by current allocations" />
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Burn/mo <InfoTooltip content="Monthly burn rate based on currently active allocations" />
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Exhaustion <InfoTooltip content="Projected date when budget will be fully consumed at the current burn rate" />
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-100">
|
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||||
{rows.map((row) => (
|
{rows.map((row) => (
|
||||||
<tr key={row.shortCode} className="hover:bg-gray-50">
|
<tr key={row.shortCode} className="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||||||
<td className="px-3 py-2 font-medium text-gray-900 max-w-[140px] truncate">
|
<td className="px-3 py-2 font-medium text-gray-900 dark:text-gray-100 max-w-[140px] truncate">
|
||||||
<span className="font-mono text-gray-500 mr-1">{row.shortCode}</span>
|
<span className="font-mono text-gray-500 dark:text-gray-400 mr-1">{row.shortCode}</span>
|
||||||
{row.projectName}
|
{row.projectName}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex-1 h-2 bg-gray-100 rounded-full overflow-hidden">
|
<div className="flex-1 h-2 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={`h-full rounded-full transition-all ${colorClass(row.pctUsed)}`}
|
className={`h-full rounded-full transition-all ${colorClass(row.pctUsed)}`}
|
||||||
style={{ width: `${Math.min(row.pctUsed, 100)}%` }}
|
style={{ width: `${Math.min(row.pctUsed, 100)}%` }}
|
||||||
@@ -76,12 +85,12 @@ export function BudgetForecastWidget(_props: WidgetProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-gray-700 tabular-nums">
|
<td className="px-3 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
|
||||||
{row.burnRate > 0
|
{row.burnRate > 0
|
||||||
? `${(row.burnRate / 100).toLocaleString("de-DE", { maximumFractionDigits: 0 })} \u20AC`
|
? `${(row.burnRate / 100).toLocaleString("de-DE", { maximumFractionDigits: 0 })} \u20AC`
|
||||||
: "\u2014"}
|
: "\u2014"}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-gray-500 tabular-nums">
|
<td className="px-3 py-2 text-right text-gray-500 dark:text-gray-400 tabular-nums">
|
||||||
{row.estimatedExhaustionDate ?? "\u2014"}
|
{row.estimatedExhaustionDate ?? "\u2014"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { trpc } from "~/lib/trpc/client.js";
|
import { trpc } from "~/lib/trpc/client.js";
|
||||||
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
|
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
|
||||||
|
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||||
|
|
||||||
function healthDot(value: number): string {
|
function healthDot(value: number): string {
|
||||||
if (value >= 70) return "bg-green-500";
|
if (value >= 70) return "bg-green-500";
|
||||||
@@ -10,9 +11,9 @@ function healthDot(value: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scoreBadge(score: number): string {
|
function scoreBadge(score: number): string {
|
||||||
if (score >= 70) return "bg-green-100 text-green-700";
|
if (score >= 70) return "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300";
|
||||||
if (score >= 40) return "bg-amber-100 text-amber-700";
|
if (score >= 40) return "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300";
|
||||||
return "bg-red-100 text-red-700";
|
return "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProjectHealthWidget(_props: WidgetProps) {
|
export function ProjectHealthWidget(_props: WidgetProps) {
|
||||||
@@ -53,20 +54,24 @@ export function ProjectHealthWidget(_props: WidgetProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="overflow-auto h-full">
|
<div className="overflow-auto h-full">
|
||||||
<table className="w-full text-xs">
|
<table className="w-full text-xs">
|
||||||
<thead className="bg-gray-50 sticky top-0">
|
<thead className="bg-gray-50 dark:bg-gray-800/50 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left font-medium text-gray-500">Project</th>
|
<th className="px-3 py-2 text-left font-medium text-gray-500 dark:text-gray-400">
|
||||||
<th className="px-3 py-2 text-center font-medium text-gray-500" title="Budget / Staffing / Timeline">
|
Project <InfoTooltip content="Active projects scored across three health dimensions" />
|
||||||
B / S / T
|
</th>
|
||||||
|
<th className="px-3 py-2 text-center font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
B / S / T <InfoTooltip content="Budget health (spent vs budget), Staffing health (filled vs total demands), Timeline health (within end date)" />
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Score <InfoTooltip content="Composite score: average of Budget, Staffing, and Timeline health (0-100)" />
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-medium text-gray-500">Score</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-100">
|
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||||
{rows.map((row) => (
|
{rows.map((row) => (
|
||||||
<tr key={row.shortCode} className="hover:bg-gray-50">
|
<tr key={row.shortCode} className="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||||||
<td className="px-3 py-2 font-medium text-gray-900 max-w-[160px] truncate">
|
<td className="px-3 py-2 font-medium text-gray-900 dark:text-gray-100 max-w-[160px] truncate">
|
||||||
<span className="font-mono text-gray-500 mr-1">{row.shortCode}</span>
|
<span className="font-mono text-gray-500 dark:text-gray-400 mr-1">{row.shortCode}</span>
|
||||||
{row.projectName}
|
{row.projectName}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { trpc } from "~/lib/trpc/client.js";
|
import { trpc } from "~/lib/trpc/client.js";
|
||||||
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
|
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
|
||||||
|
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||||
|
|
||||||
export function SkillGapWidget(_props: WidgetProps) {
|
export function SkillGapWidget(_props: WidgetProps) {
|
||||||
const { data, isLoading } = trpc.dashboard.getSkillGaps.useQuery(
|
const { data, isLoading } = trpc.dashboard.getSkillGaps.useQuery(
|
||||||
@@ -37,27 +38,35 @@ export function SkillGapWidget(_props: WidgetProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="overflow-auto h-full">
|
<div className="overflow-auto h-full">
|
||||||
<table className="w-full text-xs">
|
<table className="w-full text-xs">
|
||||||
<thead className="bg-gray-50 sticky top-0">
|
<thead className="bg-gray-50 dark:bg-gray-800/50 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left font-medium text-gray-500">Skill</th>
|
<th className="px-3 py-2 text-left font-medium text-gray-500 dark:text-gray-400">
|
||||||
<th className="px-3 py-2 text-right font-medium text-gray-500">Demand</th>
|
Skill <InfoTooltip content="Skills required by open demand positions" />
|
||||||
<th className="px-3 py-2 text-right font-medium text-gray-500">Supply</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-center font-medium text-gray-500">Gap</th>
|
<th className="px-3 py-2 text-right font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Demand <InfoTooltip content="Number of unfilled demand requirements needing this skill" />
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Supply <InfoTooltip content="Number of active resources with this skill at proficiency 3+" />
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2 text-center font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Gap <InfoTooltip content="Supply minus Demand: negative (red) = shortage, positive (green) = surplus" />
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-100">
|
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||||
{rows.map((row) => {
|
{rows.map((row) => {
|
||||||
const isShortage = row.gap < 0;
|
const isShortage = row.gap < 0;
|
||||||
const isSurplus = row.gap > 0;
|
const isSurplus = row.gap > 0;
|
||||||
return (
|
return (
|
||||||
<tr key={row.skill} className="hover:bg-gray-50">
|
<tr key={row.skill} className="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||||||
<td className="px-3 py-2 font-medium text-gray-900 max-w-[180px] truncate">
|
<td className="px-3 py-2 font-medium text-gray-900 dark:text-gray-100 max-w-[180px] truncate">
|
||||||
{row.skill}
|
{row.skill}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-gray-700 tabular-nums">
|
<td className="px-3 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
|
||||||
{row.demand}
|
{row.demand}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-gray-700 tabular-nums">
|
<td className="px-3 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
|
||||||
{row.supply}
|
{row.supply}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-center">
|
<td className="px-3 py-2 text-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user