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/financial-statements-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 { TrendingUp, TrendingDown, FileText, DollarSign, Wallet, Activity } from "lucide-react";2122interface FinancialStatement {23 date: string;24 symbol: string;25 reportedCurrency: string;26 cik: string;27 fillingDate: string;28 acceptedDate: string;29 calendarYear: string;30 period: string;31 link: string;32 finalLink: string;33 [key: string]: any;34}3536interface FinancialStatementsViewProps {37 incomeStatement?: FinancialStatement[];38 balanceSheet?: FinancialStatement[];39 cashFlow?: FinancialStatement[];40 period: "annual" | "quarter";41}4243export function FinancialStatementsView({44 incomeStatement,45 balanceSheet,46 cashFlow,47 period48}: FinancialStatementsViewProps) {49 const formatCurrency = (value: number) => {50 if (!value) return "N/A";51 const absValue = Math.abs(value);52 if (absValue >= 1e12) return `$${(value / 1e12).toFixed(2)}T`;53 if (absValue >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;54 if (absValue >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;55 if (absValue >= 1e3) return `$${(value / 1e3).toFixed(2)}K`;56 return `$${value.toFixed(2)}`;57 };5859 const formatPercent = (value: number) => {60 if (!value) return "N/A";61 return `${(value * 100).toFixed(2)}%`;62 };6364 const formatDate = (dateString: string) => {65 return new Date(dateString).toLocaleDateString('fr-FR', {66 year: 'numeric',67 month: 'short',68 day: 'numeric'69 });70 };7172 const getChangeIndicator = (current: number, previous: number) => {73 if (!current || !previous) return null;74 const change = ((current - previous) / Math.abs(previous)) * 100;75 const isPositive = change >= 0;7677 return (78 <span className={`text-xs flex items-center gap-1 ${isPositive ? 'text-green-500' : 'text-red-500'}`}>79 {isPositive ? <TrendingUp className="w-3 h-3" /> : <TrendingDown className="w-3 h-3" />}80 {isPositive ? '+' : ''}{change.toFixed(1)}%81 </span>82 );83 };8485 // Income Statement86 const incomeStatementRows = [87 { label: "Revenu Total", key: "revenue", highlight: true },88 { label: "Coût des Revenus", key: "costOfRevenue" },89 { label: "Marge Brute", key: "grossProfit", highlight: true },90 { label: "Dépenses R&D", key: "researchAndDevelopmentExpenses" },91 { label: "Dépenses Générales", key: "generalAndAdministrativeExpenses" },92 { label: "Dépenses Marketing", key: "sellingAndMarketingExpenses" },93 { label: "Dépenses d'Exploitation", key: "operatingExpenses" },94 { label: "Revenu d'Exploitation", key: "operatingIncome", highlight: true },95 { label: "Revenu d'Intérêts", key: "interestIncome" },96 { label: "Dépenses d'Intérêts", key: "interestExpense" },97 { label: "Autres Revenus/Dépenses", key: "otherExpenses" },98 { label: "Revenu avant Impôts", key: "incomeBeforeTax", highlight: true },99 { label: "Charge d'Impôt", key: "incomeTaxExpense" },100 { label: "Revenu Net", key: "netIncome", highlight: true, important: true },101 { label: "BPA", key: "eps", isEPS: true },102 { label: "BPA Dilué", key: "epsdiluted", isEPS: true },103 ];104105 // Balance Sheet106 const balanceSheetRows = [107 { label: "Actifs", section: true },108 { label: "Trésorerie", key: "cashAndCashEquivalents" },109 { label: "Placements Court Terme", key: "shortTermInvestments" },110 { label: "Comptes Clients", key: "netReceivables" },111 { label: "Inventaire", key: "inventory" },112 { label: "Actifs Courants", key: "totalCurrentAssets", highlight: true },113 { label: "Immobilisations", key: "propertyPlantEquipmentNet" },114 { label: "Goodwill", key: "goodwill" },115 { label: "Actifs Intangibles", key: "intangibleAssets" },116 { label: "Placements Long Terme", key: "longTermInvestments" },117 { label: "Total Actifs", key: "totalAssets", highlight: true, important: true },118 { label: "Passifs", section: true },119 { label: "Comptes Fournisseurs", key: "accountPayables" },120 { label: "Dette Court Terme", key: "shortTermDebt" },121 { label: "Passifs Courants", key: "totalCurrentLiabilities", highlight: true },122 { label: "Dette Long Terme", key: "longTermDebt" },123 { label: "Total Passifs", key: "totalLiabilities", highlight: true, important: true },124 { label: "Capitaux Propres", section: true },125 { label: "Actions Ordinaires", key: "commonStock" },126 { label: "Bénéfices Non Répartis", key: "retainedEarnings" },127 { label: "Total Capitaux Propres", key: "totalStockholdersEquity", highlight: true, important: true },128 ];129130 // Cash Flow131 const cashFlowRows = [132 { label: "Activités d'Exploitation", section: true },133 { label: "Revenu Net", key: "netIncome", highlight: true },134 { label: "Dépréciation & Amortissement", key: "depreciationAndAmortization" },135 { label: "Variation Fonds de Roulement", key: "changeInWorkingCapital" },136 { label: "Flux de Trésorerie d'Exploitation", key: "operatingCashFlow", highlight: true, important: true },137 { label: "Activités d'Investissement", section: true },138 { label: "Investissements en Immobilisations", key: "capitalExpenditure" },139 { label: "Acquisitions", key: "acquisitionsNet" },140 { label: "Achats d'Investissements", key: "purchasesOfInvestments" },141 { label: "Ventes d'Investissements", key: "salesMaturitiesOfInvestments" },142 { label: "Flux de Trésorerie d'Investissement", key: "netCashUsedForInvestingActivites", highlight: true },143 { label: "Activités de Financement", section: true },144 { label: "Dette Émise", key: "debtRepayment" },145 { label: "Actions Rachetées", key: "commonStockRepurchased" },146 { label: "Dividendes Payés", key: "dividendsPaid" },147 { label: "Flux de Trésorerie de Financement", key: "netCashUsedProvidedByFinancingActivities", highlight: true },148 { label: "Variation Nette de Trésorerie", key: "netChangeInCash", highlight: true, important: true },149 { label: "Flux de Trésorerie Libre", key: "freeCashFlow", highlight: true, important: true },150 ];151152 const renderTable = (153 data: FinancialStatement[] | undefined,154 rows: Array<{ label: string; key?: string; highlight?: boolean; important?: boolean; section?: boolean; isEPS?: boolean }>,155 title: string,156 description: string,157 icon: React.ReactNode158 ) => {159 if (!data || data.length === 0) {160 return (161 <Card>162 <CardHeader>163 <CardTitle className="flex items-center gap-2">164 {icon}165 {title}166 </CardTitle>167 <CardDescription>{description}</CardDescription>168 </CardHeader>169 <CardContent>170 <p className="text-muted-foreground">Aucune donnée disponible</p>171 </CardContent>172 </Card>173 );174 }175176 return (177 <Card>178 <CardHeader>179 <CardTitle className="flex items-center gap-2">180 {icon}181 {title}182 </CardTitle>183 <CardDescription>{description}</CardDescription>184 </CardHeader>185 <CardContent>186 <div className="overflow-x-auto">187 <Table>188 <TableHeader>189 <TableRow>190 <TableHead className="w-[250px] sticky left-0 bg-card z-10">Élément</TableHead>191 {data.slice(0, 5).map((item, idx) => (192 <TableHead key={idx} className="text-right min-w-[150px]">193 <div>194 <div className="font-semibold">{formatDate(item.date)}</div>195 <Badge variant="outline" className="text-xs mt-1">196 {item.period}197 </Badge>198 </div>199 </TableHead>200 ))}201 </TableRow>202 </TableHeader>203 <TableBody>204 {rows.map((row, rowIdx) => {205 if (row.section) {206 return (207 <TableRow key={rowIdx} className="bg-muted/50">208 <TableCell colSpan={6} className="font-bold sticky left-0 bg-muted/50 z-10">209 {row.label}210 </TableCell>211 </TableRow>212 );213 }214215 return (216 <TableRow217 key={rowIdx}218 className={`${row.highlight ? 'bg-primary/5' : ''} ${row.important ? 'border-l-4 border-l-primary' : ''}`}219 >220 <TableCell className={`sticky left-0 bg-card z-10 ${row.highlight ? 'font-semibold' : ''} ${row.important ? 'font-bold text-primary' : ''}`}>221 {row.label}222 </TableCell>223 {data.slice(0, 5).map((item, idx) => {224 const value = row.key ? item[row.key] : null;225 const prevValue = idx < data.length - 1 && row.key ? data[idx + 1][row.key] : null;226227 return (228 <TableCell key={idx} className={`text-right ${row.highlight ? 'font-semibold' : ''} ${row.important ? 'font-bold' : ''}`}>229 <div>230 <div>231 {row.isEPS ? (value ? `$${value.toFixed(2)}` : 'N/A') : formatCurrency(value)}232 </div>233 {value && prevValue && getChangeIndicator(value, prevValue)}234 </div>235 </TableCell>236 );237 })}238 </TableRow>239 );240 })}241 </TableBody>242 </Table>243 </div>244245 {data[0]?.link && (246 <div className="mt-4 pt-4 border-t">247 <a248 href={data[0].link}249 target="_blank"250 rel="noopener noreferrer"251 className="text-sm text-primary hover:underline"252 >253 Voir le document SEC complet →254 </a>255 </div>256 )}257 </CardContent>258 </Card>259 );260 };261262 return (263 <div className="space-y-6">264 {renderTable(265 incomeStatement,266 incomeStatementRows,267 "Compte de Résultat",268 "Performance financière et rentabilité",269 <FileText className="w-5 h-5" />270 )}271272 {renderTable(273 balanceSheet,274 balanceSheetRows,275 "Bilan",276 "Actifs, passifs et capitaux propres",277 <Wallet className="w-5 h-5" />278 )}279280 {renderTable(281 cashFlow,282 cashFlowRows,283 "Tableau de Flux de Trésorerie",284 "Flux de trésorerie par activité",285 <Activity className="w-5 h-5" />286 )}287 </div>288 );289}290