"use client"; import { Badge, Box, Card, Grid, HStack, Icon, IconButton, SimpleGrid, Text, VStack, } from "@chakra-ui/react"; import { LuBadgeCheck, LuCircleHelp, LuRectangleVertical, LuShieldAlert, LuTarget, LuTrendingUp, } from "react-icons/lu"; import { useColorModeValue } from "@/components/ui/color-mode"; import { Tooltip } from "@/components/ui/overlays/tooltip"; import type { HtftComboKey, OddsBandCardsDto, OddsBandHtftComboDto, TripleValueEntryDto, V27EngineDto, } from "@/lib/api/predictions/types"; // ────────────────────────────────────── // Helpers // ────────────────────────────────────── function pct(v: number, d = 0): string { if (!v && v !== 0) return "-"; return `${(v * 100).toFixed(d)}%`; } function edgeStr(edge: number): string { const sign = edge > 0 ? "+" : ""; return `${sign}${(edge * 100).toFixed(1)}%`; } const TRIPLE_VALUE_LABELS: Record = { home: "MS Ev", away: "MS Dep", ou25_over: "ÜST 2.5", btts_yes: "KG Var", ou15_over: "ÜST 1.5", ou35_over: "ÜST 3.5", dc_1x: "ÇŞ 1X", dc_x2: "ÇŞ X2", dc_12: "ÇŞ 12", ht_home: "İY Ev", ht_away: "İY Dep", ht_ou05_over: "İY ÜST 0.5", ht_ou15_over: "İY ÜST 1.5", oe_odd: "Tek", cards_over: "Kart ÜST", htft_11: "İY/MS 1/1", htft_1x: "İY/MS 1/X", htft_12: "İY/MS 1/2", htft_x1: "İY/MS X/1", htft_xx: "İY/MS X/X", htft_x2: "İY/MS X/2", htft_21: "İY/MS 2/1", htft_2x: "İY/MS 2/X", htft_22: "İY/MS 2/2", }; const HTFT_DISPLAY: Record = { "11": "1/1", "1x": "1/X", "12": "1/2", x1: "X/1", xx: "X/X", x2: "X/2", "21": "2/1", "2x": "2/X", "22": "2/2", }; const HTFT_ROWS: HtftComboKey[][] = [ ["11", "1x", "12"], ["x1", "xx", "x2"], ["21", "2x", "22"], ]; function TooltipIcon({ content }: { content: string }) { return ( ); } function SectionTitle({ icon, title, info, }: { icon: React.ElementType; title: string; info?: string; }) { return ( {title} {info ? : null} ); } // ────────────────────────────────────── // Triple Value Card // ────────────────────────────────────── function TripleValueCard({ label, entry, }: { label: string; entry: TripleValueEntryDto; }) { const isValue = entry.is_value; const hasSample = entry.band_sample >= 5; const cardBg = useColorModeValue( isValue ? "green.50" : "gray.50", isValue ? "green.950" : "whiteAlpha.50", ); const borderCol = useColorModeValue( isValue ? "green.300" : "gray.200", isValue ? "green.700" : "gray.700", ); const edgeColor = entry.edge > 0.03 ? "green.500" : entry.edge < -0.03 ? "red.400" : "fg.muted"; return ( {isValue && ( )} {label} {isValue ? ( DEĞER ) : hasSample ? ( PAS ) : ( YETERSİZ )} {edgeStr(entry.edge)} Band: {pct(entry.band_rate, 1)} Oran: {pct(entry.implied_prob, 1)} {entry.confirmations !== undefined && ( Onay: {entry.confirmations}/2 )} {entry.band_sample} maç ); } // ────────────────────────────────────── // Cards Section // ────────────────────────────────────── function ProgressBar({ value, max, color, }: { value: number; max: number; color: string; }) { const trackBg = useColorModeValue("gray.100", "gray.700"); const w = max > 0 ? Math.min(100, (value / max) * 100) : 0; return ( ); } function CardsSection({ cards }: { cards: OddsBandCardsDto }) { const cardBg = useColorModeValue("white", "gray.800"); const borderColor = useColorModeValue("gray.200", "gray.700"); const hasData = cards.sample >= 3; if (!hasData) { return ( Kart Analizi Yetersiz veri — henüz yeterli maç örneği bulunamadı. ); } const overPct = cards.combined_over_rate * 100; const overColor = overPct >= 65 ? "red.400" : overPct >= 50 ? "orange.400" : "green.400"; return ( Kart Analizi {cards.sample} maç {/* Referee profile */} Hakem Profili Ort: {cards.referee_avg.toFixed(1)} kart Üst oranı: {pct(cards.referee_over_rate, 0)} {cards.referee_sample} maç {/* Team profile */} Takım Profili Ort: {cards.team_avg.toFixed(1)} kart Üst oranı: {pct(cards.team_over_rate, 0)} {cards.team_sample} maç {/* Combined */} Kombine ÜST Oranı %60 Hakem + %40 Takım ağırlıklı {overPct.toFixed(0)}% ); } // ────────────────────────────────────── // HTFT 3x3 Grid // ────────────────────────────────────── function HtftGrid({ htft, }: { htft: Record; }) { const cardBg = useColorModeValue("white", "gray.800"); const borderColor = useColorModeValue("gray.200", "gray.700"); // Find the max rate for highlighting let maxRate = 0; let maxKey: HtftComboKey = "11"; let totalSample = 0; for (const [key, val] of Object.entries(htft) as [ HtftComboKey, OddsBandHtftComboDto, ][]) { if (val.rate > maxRate) { maxRate = val.rate; maxKey = key; } totalSample = Math.max(totalSample, val.sample); } const getCellColor = (rate: number, isMax: boolean) => { if (isMax) return { bg: "green.500", text: "white" }; if (rate >= 0.2) return { bg: "green.100", text: "green.800" }; if (rate >= 0.12) return { bg: "yellow.100", text: "yellow.800" }; if (rate >= 0.06) return { bg: "orange.50", text: "orange.700" }; return { bg: "gray.50", text: "gray.500" }; }; const getCellColorDark = (rate: number, isMax: boolean) => { if (isMax) return { bg: "green.600", text: "white" }; if (rate >= 0.2) return { bg: "green.900", text: "green.200" }; if (rate >= 0.12) return { bg: "yellow.900", text: "yellow.200" }; if (rate >= 0.06) return { bg: "orange.900", text: "orange.200" }; return { bg: "whiteAlpha.50", text: "gray.500" }; }; const lightMode = useColorModeValue(true, false); return ( İY/MS Kombinasyonları {/* Column headers */} MS 1 MS X MS 2 {/* Grid rows */} {HTFT_ROWS.map((row, rowIdx) => { const rowLabels = ["İY 1", "İY X", "İY 2"]; return ( {rowLabels[rowIdx]} {row.map((comboKey) => { const data = htft[comboKey] || { rate: 0, sample: 0 }; const isMax = comboKey === maxKey && maxRate > 0.05; const colors = lightMode ? getCellColor(data.rate, isMax) : getCellColorDark(data.rate, isMax); return ( {HTFT_DISPLAY[comboKey]} {pct(data.rate, 0)} {data.sample} maç {isMax && ( )} ); })} ); })} {/* Best combo callout */} {maxRate > 0.05 && ( En güçlü:{" "} {HTFT_DISPLAY[maxKey]} ({pct(maxRate, 0)}) )} ); } // ────────────────────────────────────── // Main Panel Export // ────────────────────────────────────── interface V28OddsBandPanelProps { engine: V27EngineDto; } export default function V28OddsBandPanel({ engine }: V28OddsBandPanelProps) { const cardBg = useColorModeValue("white", "gray.800"); const borderColor = useColorModeValue("gray.200", "gray.700"); const tripleValue = engine.triple_value; const cards = engine.odds_band?.cards as OddsBandCardsDto | undefined; const htft = engine.odds_band?.htft as | Record | undefined; // Filter out HTFT triple-value entries from the main grid (shown in HTFT section) const mainValueEntries = Object.entries(tripleValue || {}).filter( ([key]) => !key.startsWith("htft_"), ); // Separate value hits from non-hits for priority ordering const valueHits = mainValueEntries.filter(([, e]) => e.is_value); const valueNon = mainValueEntries.filter(([, e]) => !e.is_value); const orderedEntries = [...valueHits, ...valueNon]; const hasTriple = orderedEntries.length > 0; const hasCards = cards && cards.sample >= 1; const hasHtft = htft && Object.values(htft).some((v) => v.sample > 0); if (!hasTriple && !hasCards && !hasHtft) return null; return ( {/* Engine version badge */} {engine.version} {engine.consensus && ( {engine.consensus === "AGREE" ? "Motorlar Uyumlu" : "Motorlar Farklı"} )} {valueHits.length > 0 && ( {valueHits.length} Değer Sinyali )} {/* Triple Value Grid */} {hasTriple && ( Değer Tespiti (Triple Value) {orderedEntries.map(([key, entry]) => ( ))} )} {/* Cards + HTFT side by side on large screens */} {(hasCards || hasHtft) && ( {hasCards && } {hasHtft && } )} ); }