major update
Some checks failed
Deploy / lint (push) Failing after 8s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped

This commit is contained in:
Dennis Thiessen
2026-02-27 16:08:09 +01:00
parent 61ab24490d
commit 181cfe6588
71 changed files with 7647 additions and 281 deletions

View File

@@ -1,6 +1,11 @@
import { useState } from 'react';
import { DimensionBreakdownPanel } from '../ticker/DimensionBreakdownPanel';
import type { DimensionScoreDetail, CompositeBreakdown } from '../../lib/types';
interface ScoreCardProps {
compositeScore: number | null;
dimensions: { dimension: string; score: number }[];
dimensions: DimensionScoreDetail[];
compositeBreakdown?: CompositeBreakdown;
}
function scoreColor(score: number): string {
@@ -46,7 +51,13 @@ function ScoreRing({ score }: { score: number }) {
);
}
export function ScoreCard({ compositeScore, dimensions }: ScoreCardProps) {
export function ScoreCard({ compositeScore, dimensions, compositeBreakdown }: ScoreCardProps) {
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const toggleExpand = (dimension: string) => {
setExpanded((prev) => ({ ...prev, [dimension]: !prev[dimension] }));
};
return (
<div className="glass p-5">
<div className="flex items-center gap-4">
@@ -60,28 +71,79 @@ export function ScoreCard({ compositeScore, dimensions }: ScoreCardProps) {
<p className={`text-2xl font-bold ${compositeScore !== null ? scoreColor(compositeScore) : 'text-gray-500'}`}>
{compositeScore !== null ? Math.round(compositeScore) : '—'}
</p>
{compositeBreakdown && (
<p className="mt-1 text-[10px] text-gray-500 leading-snug max-w-[200px]" data-testid="renorm-explanation">
Weighted average of available dimensions with re-normalized weights.
</p>
)}
</div>
</div>
{dimensions.length > 0 && (
<div className="mt-5 space-y-2.5">
<div className="mt-5 space-y-1">
<p className="text-[10px] font-medium uppercase tracking-widest text-gray-500">Dimensions</p>
{dimensions.map((d) => (
<div key={d.dimension} className="flex items-center justify-between text-sm">
<span className="text-gray-300 capitalize">{d.dimension}</span>
<div className="flex items-center gap-2">
<div className="h-1.5 w-20 rounded-full bg-white/[0.06] overflow-hidden">
<div
className={`h-1.5 rounded-full bg-gradient-to-r ${barGradient(d.score)} transition-all duration-500`}
style={{ width: `${Math.max(0, Math.min(100, d.score))}%` }}
/>
</div>
<span className={`w-8 text-right font-medium text-xs ${scoreColor(d.score)}`}>
{Math.round(d.score)}
</span>
{dimensions.map((d) => {
const isExpanded = expanded[d.dimension] ?? false;
const weight = compositeBreakdown?.renormalized_weights?.[d.dimension]
?? compositeBreakdown?.weights?.[d.dimension];
return (
<div key={d.dimension}>
<button
type="button"
className="flex w-full items-center justify-between text-sm py-1 hover:bg-white/[0.03] rounded transition-colors"
onClick={() => d.breakdown && toggleExpand(d.dimension)}
data-testid={`dimension-row-${d.dimension}`}
>
<span className="text-gray-300 capitalize flex items-center gap-1.5">
{d.breakdown && (
<span className="text-gray-500 text-[10px]">{isExpanded ? '▾' : '▸'}</span>
)}
{d.dimension}
</span>
<div className="flex items-center gap-2">
{weight != null && (
<span className="text-[10px] text-gray-500 tabular-nums" data-testid={`weight-${d.dimension}`}>
{Math.round(weight * 100)}%
</span>
)}
<div className="h-1.5 w-20 rounded-full bg-white/[0.06] overflow-hidden">
<div
className={`h-1.5 rounded-full bg-gradient-to-r ${barGradient(d.score)} transition-all duration-500`}
style={{ width: `${Math.max(0, Math.min(100, d.score))}%` }}
/>
</div>
<span className={`w-8 text-right font-medium text-xs ${scoreColor(d.score)}`}>
{Math.round(d.score)}
</span>
</div>
</button>
{isExpanded && d.breakdown && (
<div className="ml-4 mt-1 mb-2 pl-3 border-l border-white/[0.06]">
<DimensionBreakdownPanel breakdown={d.breakdown} />
</div>
)}
</div>
);
})}
{/* Missing dimensions */}
{compositeBreakdown && compositeBreakdown.missing_dimensions.length > 0 && (
<div className="mt-2 space-y-1">
{compositeBreakdown.missing_dimensions
.filter((dim) => !dimensions.some((d) => d.dimension === dim))
.map((dim) => (
<div
key={dim}
className="flex items-center justify-between text-sm py-1 opacity-40"
data-testid={`missing-dimension-${dim}`}
>
<span className="text-gray-500 capitalize">{dim}</span>
<span className="text-[10px] text-gray-600 italic">redistributed</span>
</div>
))}
</div>
))}
)}
</div>
)}
</div>