From 4bbcd7a3a6cf7c4d0d69073d40ef03626da2e49b Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Tue, 16 Jun 2026 22:05:51 +0200 Subject: [PATCH] feat: score justification on trade cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ScorePillars: compact 4-pillar mini-bars always visible under score bar (πŸ“°/πŸ“…/πŸ“ˆ/βš–οΈ with color-coded fill β€” instant visual of pillar distribution) - Add ScoreJustification: replaces old per-click BucketBreakdown with full text reasoning shown at once β€” key_catalyst highlighted in yellow callout, each pillar shows score + GPT-4o comment + all sub-pillars with comments - Rename expand button to "↳ Justification du score" for clarity Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/pages/Dashboard.tsx | 104 +++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 0aa02e9..f831c70 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -53,46 +53,74 @@ function BucketBar({ score, max }: { score: number; max: number }) { ) } -function BucketBreakdown({ buckets }: { buckets: any[] }) { - const [openBucket, setOpenBucket] = useState(null) +// Compact 4-pillar mini-bars always visible on card +function ScorePillars({ buckets }: { buckets: any[] }) { + if (!buckets?.length) return null return ( -
+
{buckets.map((b: any) => { const pct = b.max > 0 ? Math.round((b.score / b.max) * 100) : 0 - const isOpen = openBucket === b.id + const color = pct >= 70 ? 'bg-emerald-500' : pct >= 45 ? 'bg-yellow-500' : 'bg-red-500/70' return ( -
- - {isOpen && ( -
- {b.comment && ( -

{b.comment}

- )} - {b.subs?.map((sub: any) => { +
+ {/* Pillar comment */} + {b.comment && ( +

{b.comment}

+ )} + {/* Sub-pillars */} + {b.subs?.length > 0 && ( +
+ {b.subs.map((sub: any) => { const subPct = sub.max > 0 ? Math.round((sub.score / sub.max) * 100) : 0 + const subColor = subPct >= 70 ? 'text-emerald-400' : subPct >= 45 ? 'text-yellow-400' : 'text-slate-500' return ( -
-
- {BUCKET_ICONS[sub.id] ?? 'β€Ί'} +
+
+ {BUCKET_ICONS[sub.id] ?? 'β€Ί'} {sub.label} - - = 75 ? 'text-emerald-400' : subPct >= 50 ? 'text-yellow-400' : 'text-slate-600')}> + {sub.score}/{sub.max}
{sub.comment && ( -

{sub.comment}

+

{sub.comment}

)}
) @@ -221,11 +249,16 @@ function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: { )}
- {/* Score bar */} + {/* Score bar + 4-pillar mini breakdown */} {effectiveScore !== null && ( -
-
-
+ <> +
+
+
+ {scoreInfo?.buckets?.length > 0 && ( + + )} + )} {/* EV / Profile match row β€” always shown when scored, helps understand why trade is/isn't logged */} @@ -300,18 +333,21 @@ function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: {
)} - {/* Expandable score breakdown */} + {/* Expandable score justification */} {effectiveScore !== null && (scoreInfo?.buckets?.length > 0 || Object.keys(breakdown).length > 0) && ( <> {expanded && (
{scoreInfo?.buckets?.length > 0 ? ( - + ) : (
{Object.entries(breakdown).map(([k, v]: [string, any]) => (