spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/components/explore/metrics-view.tsx6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";18import { Badge } from "@/components/ui/badge";19import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";20import {21 TrendingUp,22 TrendingDown,23 DollarSign,24 Percent,25 TrendingUpDown,26 Calculator,27 PieChart,28 Activity29} from "lucide-react";3031interface KeyMetrics {32 date: string;33 symbol: string;34 period: string;35 revenuePerShare: number;36 netIncomePerShare: number;37 operatingCashFlowPerShare: number;38 freeCashFlowPerShare: number;39 cashPerShare: number;40 bookValuePerShare: number;41 tangibleBookValuePerShare: number;42 shareholdersEquityPerShare: number;43 interestDebtPerShare: number;44 marketCap: number;45 enterpriseValue: number;46 peRatio: number;47 priceToSalesRatio: number;48 pocfratio: number;49 pfcfRatio: number;50 pbRatio: number;51 ptbRatio: number;52 evToSales: number;53 enterpriseValueOverEBITDA: number;54 evToOperatingCashFlow: number;55 evToFreeCashFlow: number;56 earningsYield: number;57 freeCashFlowYield: number;58 debtToEquity: number;59 debtToAssets: number;60 netDebtToEBITDA: number;61 currentRatio: number;62 interestCoverage: number;63 incomeQuality: number;64 dividendYield: number;65 payoutRatio: number;66 salesGeneralAndAdministrativeToRevenue: number;67 researchAndDdevelopementToRevenue: number;68 intangiblesToTotalAssets: number;69 capexToOperatingCashFlow: number;70 capexToRevenue: number;71 capexToDepreciation: number;72 stockBasedCompensationToRevenue: number;73 grahamNumber: number;74 roic: number;75 returnOnTangibleAssets: number;76 grahamNetNet: number;77 workingCapital: number;78 tangibleAssetValue: number;79 netCurrentAssetValue: number;80 investedCapital: number;81 averageReceivables: number;82 averagePayables: number;83 averageInventory: number;84 daysSalesOutstanding: number;85 daysPayablesOutstanding: number;86 daysOfInventoryOnHand: number;87 receivablesTurnover: number;88 payablesTurnover: number;89 inventoryTurnover: number;90 roe: number;91 capexPerShare: number;92 [key: string]: any;93}9495interface MetricsViewProps {96 keyMetrics?: KeyMetrics[];97}9899export function MetricsView({ keyMetrics }: MetricsViewProps) {100 const formatNumber = (value: number) => {101 if (!value) return "N/A";102 const absValue = Math.abs(value);103 if (absValue >= 1e12) return `$${(value / 1e12).toFixed(2)}T`;104 if (absValue >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;105 if (absValue >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;106 if (absValue >= 1e3) return `$${(value / 1e3).toFixed(2)}K`;107 return `$${value.toFixed(2)}`;108 };109110 const formatPercent = (value: number) => {111 if (!value && value !== 0) return "N/A";112 return `${(value * 100).toFixed(2)}%`;113 };114115 const formatRatio = (value: number) => {116 if (!value && value !== 0) return "N/A";117 return value.toFixed(2);118 };119120 const formatDate = (dateString: string) => {121 return new Date(dateString).toLocaleDateString('fr-FR', {122 year: 'numeric',123 month: 'short'124 });125 };126127 const getChangeIndicator = (current: number, previous: number) => {128 if (!current || !previous) return null;129 const change = ((current - previous) / Math.abs(previous)) * 100;130 const isPositive = change >= 0;131132 return (133 <span className={`text-xs flex items-center gap-1 ${isPositive ? 'text-green-500' : 'text-red-500'}`}>134 {isPositive ? <TrendingUp className="w-3 h-3" /> : <TrendingDown className="w-3 h-3" />}135 {isPositive ? '+' : ''}{change.toFixed(1)}%136 </span>137 );138 };139140 if (!keyMetrics || keyMetrics.length === 0) {141 return (142 <Card>143 <CardHeader>144 <CardTitle>Métriques Clés</CardTitle>145 <CardDescription>Aucune donnée disponible</CardDescription>146 </CardHeader>147 </Card>148 );149 }150151 const metricsSections = [152 {153 title: "Métriques de Valorisation",154 icon: <DollarSign className="w-5 h-5" />,155 description: "Ratios de valorisation et prix",156 metrics: [157 { label: "Capitalisation Boursière", key: "marketCap", format: "currency" },158 { label: "Valeur d'Entreprise", key: "enterpriseValue", format: "currency" },159 { label: "P/E Ratio", key: "peRatio", format: "ratio" },160 { label: "P/S Ratio", key: "priceToSalesRatio", format: "ratio" },161 { label: "P/B Ratio", key: "pbRatio", format: "ratio" },162 { label: "P/TBV Ratio", key: "ptbRatio", format: "ratio" },163 { label: "P/FCF Ratio", key: "pfcfRatio", format: "ratio" },164 { label: "EV/Sales", key: "evToSales", format: "ratio" },165 { label: "EV/EBITDA", key: "enterpriseValueOverEBITDA", format: "ratio" },166 { label: "EV/Operating CF", key: "evToOperatingCashFlow", format: "ratio" },167 { label: "EV/Free CF", key: "evToFreeCashFlow", format: "ratio" },168 { label: "Graham Number", key: "grahamNumber", format: "currency" },169 ]170 },171 {172 title: "Rentabilité & Rendements",173 icon: <TrendingUp className="w-5 h-5" />,174 description: "Indicateurs de rentabilité",175 metrics: [176 { label: "ROE (Return on Equity)", key: "roe", format: "percent" },177 { label: "ROIC (Return on Invested Capital)", key: "roic", format: "percent" },178 { label: "Return on Tangible Assets", key: "returnOnTangibleAssets", format: "percent" },179 { label: "Earnings Yield", key: "earningsYield", format: "percent" },180 { label: "Free Cash Flow Yield", key: "freeCashFlowYield", format: "percent" },181 { label: "Dividend Yield", key: "dividendYield", format: "percent" },182 { label: "Payout Ratio", key: "payoutRatio", format: "percent" },183 { label: "Income Quality", key: "incomeQuality", format: "ratio" },184 ]185 },186 {187 title: "Métriques par Action",188 icon: <PieChart className="w-5 h-5" />,189 description: "Données normalisées par action",190 metrics: [191 { label: "Revenu par Action", key: "revenuePerShare", format: "currency" },192 { label: "Bénéfice Net par Action", key: "netIncomePerShare", format: "currency" },193 { label: "Operating CF par Action", key: "operatingCashFlowPerShare", format: "currency" },194 { label: "Free CF par Action", key: "freeCashFlowPerShare", format: "currency" },195 { label: "Cash par Action", key: "cashPerShare", format: "currency" },196 { label: "Book Value par Action", key: "bookValuePerShare", format: "currency" },197 { label: "Tangible BV par Action", key: "tangibleBookValuePerShare", format: "currency" },198 { label: "Shareholders Equity par Action", key: "shareholdersEquityPerShare", format: "currency" },199 { label: "Capex par Action", key: "capexPerShare", format: "currency" },200 ]201 },202 {203 title: "Santé Financière & Dette",204 icon: <Calculator className="w-5 h-5" />,205 description: "Indicateurs de solvabilité et liquidité",206 metrics: [207 { label: "Dette/Capitaux Propres", key: "debtToEquity", format: "ratio" },208 { label: "Dette/Actifs", key: "debtToAssets", format: "ratio" },209 { label: "Dette Nette/EBITDA", key: "netDebtToEBITDA", format: "ratio" },210 { label: "Ratio de Liquidité", key: "currentRatio", format: "ratio" },211 { label: "Couverture des Intérêts", key: "interestCoverage", format: "ratio" },212 { label: "Fonds de Roulement", key: "workingCapital", format: "currency" },213 { label: "Capital Investi", key: "investedCapital", format: "currency" },214 { label: "Tangible Asset Value", key: "tangibleAssetValue", format: "currency" },215 ]216 },217 {218 title: "Efficacité Opérationnelle",219 icon: <Activity className="w-5 h-5" />,220 description: "Ratios de rotation et cycles d'exploitation",221 metrics: [222 { label: "Rotation des Créances", key: "receivablesTurnover", format: "ratio" },223 { label: "Rotation des Dettes", key: "payablesTurnover", format: "ratio" },224 { label: "Rotation des Stocks", key: "inventoryTurnover", format: "ratio" },225 { label: "Jours de Recouvrement (DSO)", key: "daysSalesOutstanding", format: "ratio" },226 { label: "Jours de Paiement (DPO)", key: "daysPayablesOutstanding", format: "ratio" },227 { label: "Jours de Stock (DIO)", key: "daysOfInventoryOnHand", format: "ratio" },228 { label: "Créances Moyennes", key: "averageReceivables", format: "currency" },229 { label: "Dettes Moyennes", key: "averagePayables", format: "currency" },230 { label: "Stock Moyen", key: "averageInventory", format: "currency" },231 ]232 },233 {234 title: "Dépenses & Allocation",235 icon: <Percent className="w-5 h-5" />,236 description: "Structure des dépenses en % du revenu",237 metrics: [238 { label: "SG&A / Revenu", key: "salesGeneralAndAdministrativeToRevenue", format: "percent" },239 { label: "R&D / Revenu", key: "researchAndDdevelopementToRevenue", format: "percent" },240 { label: "Stock Comp / Revenu", key: "stockBasedCompensationToRevenue", format: "percent" },241 { label: "Capex / Operating CF", key: "capexToOperatingCashFlow", format: "percent" },242 { label: "Capex / Revenu", key: "capexToRevenue", format: "percent" },243 { label: "Capex / Depreciation", key: "capexToDepreciation", format: "ratio" },244 { label: "Intangibles / Total Assets", key: "intangiblesToTotalAssets", format: "percent" },245 ]246 },247 ];248249 return (250 <div className="space-y-6">251 {metricsSections.map((section, sectionIdx) => (252 <Card key={sectionIdx}>253 <CardHeader>254 <CardTitle className="flex items-center gap-2">255 {section.icon}256 {section.title}257 </CardTitle>258 <CardDescription>{section.description}</CardDescription>259 </CardHeader>260 <CardContent>261 <div className="overflow-x-auto">262 <Table>263 <TableHeader>264 <TableRow>265 <TableHead className="w-[250px] sticky left-0 bg-card z-10">Métrique</TableHead>266 {keyMetrics.slice(0, 5).map((item, idx) => (267 <TableHead key={idx} className="text-right min-w-[150px]">268 <div>269 <div className="font-semibold">{formatDate(item.date)}</div>270 <Badge variant="outline" className="text-xs mt-1">271 {item.period}272 </Badge>273 </div>274 </TableHead>275 ))}276 </TableRow>277 </TableHeader>278 <TableBody>279 {section.metrics.map((metric, metricIdx) => (280 <TableRow key={metricIdx} className="hover:bg-muted/50">281 <TableCell className="font-medium sticky left-0 bg-card z-10">282 {metric.label}283 </TableCell>284 {keyMetrics.slice(0, 5).map((item, idx) => {285 const value = item[metric.key];286 const prevValue = idx < keyMetrics.length - 1 ? keyMetrics[idx + 1][metric.key] : null;287 let formattedValue = "N/A";288289 if (value || value === 0) {290 switch (metric.format) {291 case "currency":292 formattedValue = formatNumber(value);293 break;294 case "percent":295 formattedValue = formatPercent(value);296 break;297 case "ratio":298 formattedValue = formatRatio(value);299 break;300 default:301 formattedValue = value.toString();302 }303 }304305 return (306 <TableCell key={idx} className="text-right">307 <div>308 <div className="font-semibold">{formattedValue}</div>309 {value && prevValue && getChangeIndicator(value, prevValue)}310 </div>311 </TableCell>312 );313 })}314 </TableRow>315 ))}316 </TableBody>317 </Table>318 </div>319 </CardContent>320 </Card>321 ))}322 </div>323 );324}325