major update
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user