Files
fusion/packages/dashboard/app/components/ProjectHealthBadge.tsx
gsxdsm 1e49494bac feat(i18n): full-sweep string migration — 5,930 keys across 5 locales (#1352)
Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
  English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
  carry ~5,930 keys each across common/app/errors/cli namespaces;
  CLI bundles regenerated (6 locales incl. ko)

Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
  plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
  moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
  literal {{placeholders}}); dashboard vitest.setup boots a minimal en
  i18next instance for the same reason

Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 19:06:53 -07:00

103 lines
3.7 KiB
TypeScript

import { useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import type { ProjectStatus } from "@fusion/core";
import type { ProjectHealth } from "../api";
import { getProjectStatusConfig, isInitializingStatus } from "../utils/projectStatusConfig";
export interface ProjectHealthBadgeProps {
status: ProjectStatus;
health?: ProjectHealth | null;
size?: "sm" | "md" | "lg";
showTooltip?: boolean;
}
/**
* ProjectHealthBadge - Color-coded badge showing project health status
*
* Displays a status indicator with icon and label. Optionally shows a tooltip
* with detailed health metrics on hover.
*/
export function ProjectHealthBadge({
status,
health,
size = "md",
showTooltip = true,
}: ProjectHealthBadgeProps) {
const { t } = useTranslation("app");
const [isHovered, setIsHovered] = useState(false);
const config = getProjectStatusConfig(status);
const StatusIcon = config.icon;
const handleMouseEnter = useCallback(() => {
if (showTooltip && health) {
setIsHovered(true);
}
}, [showTooltip, health]);
const handleMouseLeave = useCallback(() => {
setIsHovered(false);
}, []);
const sizeClasses = {
sm: "project-health-badge--sm",
md: "project-health-badge--md",
lg: "project-health-badge--lg",
};
const isInitializing = isInitializingStatus(status);
return (
<div
className={`project-health-badge ${sizeClasses[size]}`}
style={{
color: config.color,
borderColor: config.color,
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-status={status}
>
<StatusIcon
size={size === "sm" ? 10 : size === "md" ? 12 : 14}
className={isInitializing ? "animate-spin" : ""}
/>
<span className="project-health-badge__label">{config.label}</span>
{/* Tooltip with health metrics */}
{isHovered && health && (
<div className="project-health-badge__tooltip">
<div className="project-health-tooltip__header">
<strong>{t("health.metricsTitle", "Health Metrics")}</strong>
</div>
<div className="project-health-tooltip__content">
<div className="project-health-tooltip__metric">
<span className="project-health-tooltip__label">{t("health.activeTasks", "Active Tasks:")}:</span>
<span className="project-health-tooltip__value">{health.activeTaskCount}</span>
</div>
<div className="project-health-tooltip__metric">
<span className="project-health-tooltip__label">{t("health.inFlightAgents", "In-Flight Agents:")}:</span>
<span className="project-health-tooltip__value">{health.inFlightAgentCount}</span>
</div>
<div className="project-health-tooltip__metric">
<span className="project-health-tooltip__label">{t("health.completed", "Completed:")}:</span>
<span className="project-health-tooltip__value">{health.totalTasksCompleted}</span>
</div>
<div className="project-health-tooltip__metric">
<span className="project-health-tooltip__label">{t("health.failed", "Failed:")}:</span>
<span className="project-health-tooltip__value">{health.totalTasksFailed}</span>
</div>
{health.lastErrorMessage && (
<div className="project-health-tooltip__error">
<span className="project-health-tooltip__label">{t("health.lastError", "Last Error:")}:</span>
<span className="project-health-tooltip__error-text">
{health.lastErrorMessage}
</span>
</div>
)}
</div>
</div>
)}
</div>
);
}