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/stock-header.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 { Badge } from "@/components/ui/badge";18import { Button } from "@/components/ui/button";19import { Card, CardContent } from "@/components/ui/card";20import { ArrowLeft, TrendingUp, TrendingDown, Building2, Globe, ExternalLink, DollarSign } from "lucide-react";2122interface CompanyProfile {23 symbol: string;24 companyName: string;25 industry: string;26 sector: string;27 ceo: string;28 website: string;29 description: string;30 country: string;31 city: string;32 address: string;33 employees: number;34 marketCap: number;35 image: string;36 exchange: string;37 isin: string;38 cusip: string;39 phone: string;40}4142interface StockQuote {43 symbol: string;44 price: number;45 change: number;46 changesPercentage: number;47 dayLow: number;48 dayHigh: number;49 yearHigh: number;50 yearLow: number;51 marketCap: number;52 priceAvg50: number;53 priceAvg200: number;54 volume: number;55 avgVolume: number;56 open: number;57 previousClose: number;58 eps: number;59 pe: number;60 earningsAnnouncement: string;61 sharesOutstanding: number;62 timestamp: number;63}6465interface StockHeaderProps {66 ticker: string;67 companyProfile?: CompanyProfile;68 stockQuote?: StockQuote;69 onBack: () => void;70}7172export function StockHeader({ ticker, companyProfile, stockQuote, onBack }: StockHeaderProps) {73 const isPositive = (stockQuote?.changesPercentage ?? 0) >= 0;7475 const formatNumber = (num: number) => {76 if (num >= 1e12) return `$${(num / 1e12).toFixed(2)}T`;77 if (num >= 1e9) return `$${(num / 1e9).toFixed(2)}B`;78 if (num >= 1e6) return `$${(num / 1e6).toFixed(2)}M`;79 if (num >= 1e3) return `$${(num / 1e3).toFixed(2)}K`;80 return `$${num.toFixed(2)}`;81 };8283 const formatVolume = (vol: number) => {84 if (vol >= 1e9) return `${(vol / 1e9).toFixed(2)}B`;85 if (vol >= 1e6) return `${(vol / 1e6).toFixed(2)}M`;86 if (vol >= 1e3) return `${(vol / 1e3).toFixed(2)}K`;87 return vol.toString();88 };8990 return (91 <div className="space-y-6">92 {/* Back Button */}93 <Button94 variant="outline"95 onClick={onBack}96 className="gap-2 hover-elevate active-elevate-2"97 >98 <ArrowLeft className="w-4 h-4" />99 Retour à l'aperçu du marché100 </Button>101102 {/* Main Stock Header */}103 <Card className="bg-gradient-to-br from-card via-card to-primary/5 border-2">104 <CardContent className="pt-6">105 <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">106 {/* Company Info */}107 <div className="lg:col-span-2 space-y-4">108 <div className="flex items-start gap-4">109 {companyProfile?.image && (110 <div className="flex-shrink-0 w-16 h-16 rounded-xl bg-white p-2 shadow-md">111 <img112 src={companyProfile.image}113 alt={companyProfile.companyName}114 className="w-full h-full object-contain"115 />116 </div>117 )}118 <div className="flex-1 min-w-0">119 <div className="flex items-start justify-between gap-4">120 <div>121 <h1 className="text-3xl font-bold mb-1">122 {companyProfile?.companyName || ticker}123 </h1>124 <div className="flex items-center gap-3 flex-wrap">125 <Badge variant="secondary" className="text-base font-mono px-3 py-1">126 {ticker}127 </Badge>128 {companyProfile?.exchange && (129 <Badge variant="outline" className="text-sm">130 {companyProfile.exchange}131 </Badge>132 )}133 {companyProfile?.sector && (134 <Badge variant="outline" className="text-sm">135 {companyProfile.sector}136 </Badge>137 )}138 {companyProfile?.industry && (139 <Badge variant="outline" className="text-sm">140 {companyProfile.industry}141 </Badge>142 )}143 </div>144 </div>145 </div>146147 {companyProfile?.description && (148 <p className="mt-3 text-muted-foreground line-clamp-3">149 {companyProfile.description}150 </p>151 )}152153 {companyProfile && (154 <div className="mt-4 flex flex-wrap gap-x-6 gap-y-2 text-sm">155 {companyProfile.ceo && (156 <div>157 <span className="text-muted-foreground">CEO:</span>{" "}158 <span className="font-semibold">{companyProfile.ceo}</span>159 </div>160 )}161 {companyProfile.employees && (162 <div>163 <span className="text-muted-foreground">Employés:</span>{" "}164 <span className="font-semibold">{companyProfile.employees.toLocaleString()}</span>165 </div>166 )}167 {companyProfile.country && (168 <div>169 <span className="text-muted-foreground">Pays:</span>{" "}170 <span className="font-semibold">{companyProfile.country}</span>171 </div>172 )}173 {companyProfile.website && (174 <a175 href={companyProfile.website}176 target="_blank"177 rel="noopener noreferrer"178 className="flex items-center gap-1 text-primary hover:underline"179 >180 <Globe className="w-4 h-4" />181 Site Web182 <ExternalLink className="w-3 h-3" />183 </a>184 )}185 </div>186 )}187 </div>188 </div>189 </div>190191 {/* Live Quote */}192 {stockQuote && (193 <div className="lg:border-l lg:pl-6">194 <div className="space-y-4">195 <div>196 <div className="text-sm text-muted-foreground mb-1">Prix Actuel</div>197 <div className="text-4xl font-bold">198 ${stockQuote.price?.toFixed(2)}199 </div>200 <div className={`text-xl font-semibold flex items-center gap-2 mt-2 ${201 isPositive ? 'text-green-500' : 'text-red-500'202 }`}>203 {isPositive ? (204 <TrendingUp className="w-5 h-5" />205 ) : (206 <TrendingDown className="w-5 h-5" />207 )}208 <span>209 {isPositive ? '+' : ''}{stockQuote.change?.toFixed(2)}210 </span>211 <span className="text-lg">212 ({isPositive ? '+' : ''}{stockQuote.changesPercentage?.toFixed(2)}%)213 </span>214 </div>215 </div>216217 <div className="pt-4 border-t space-y-3">218 <div className="grid grid-cols-2 gap-3 text-sm">219 <div>220 <div className="text-muted-foreground">Ouverture</div>221 <div className="font-semibold">${stockQuote.open?.toFixed(2)}</div>222 </div>223 <div>224 <div className="text-muted-foreground">Clôture Préc.</div>225 <div className="font-semibold">${stockQuote.previousClose?.toFixed(2)}</div>226 </div>227 <div>228 <div className="text-muted-foreground">Jour Bas</div>229 <div className="font-semibold">${stockQuote.dayLow?.toFixed(2)}</div>230 </div>231 <div>232 <div className="text-muted-foreground">Jour Haut</div>233 <div className="font-semibold">${stockQuote.dayHigh?.toFixed(2)}</div>234 </div>235 <div>236 <div className="text-muted-foreground">52W Bas</div>237 <div className="font-semibold">${stockQuote.yearLow?.toFixed(2)}</div>238 </div>239 <div>240 <div className="text-muted-foreground">52W Haut</div>241 <div className="font-semibold">${stockQuote.yearHigh?.toFixed(2)}</div>242 </div>243 </div>244245 <div className="pt-3 border-t space-y-2 text-sm">246 <div className="flex justify-between">247 <span className="text-muted-foreground">Volume</span>248 <span className="font-semibold">{formatVolume(stockQuote.volume)}</span>249 </div>250 <div className="flex justify-between">251 <span className="text-muted-foreground">Vol. Moyen</span>252 <span className="font-semibold">{formatVolume(stockQuote.avgVolume)}</span>253 </div>254 <div className="flex justify-between">255 <span className="text-muted-foreground">Cap. Boursière</span>256 <span className="font-semibold">{formatNumber(stockQuote.marketCap)}</span>257 </div>258 {stockQuote.pe && (259 <div className="flex justify-between">260 <span className="text-muted-foreground">P/E Ratio</span>261 <span className="font-semibold">{stockQuote.pe.toFixed(2)}</span>262 </div>263 )}264 {stockQuote.eps && (265 <div className="flex justify-between">266 <span className="text-muted-foreground">EPS</span>267 <span className="font-semibold">${stockQuote.eps.toFixed(2)}</span>268 </div>269 )}270 </div>271272 {stockQuote.earningsAnnouncement && (273 <div className="pt-3 border-t">274 <div className="text-xs text-muted-foreground">Prochains Résultats</div>275 <div className="text-sm font-semibold">276 {new Date(stockQuote.earningsAnnouncement).toLocaleDateString('fr-FR', {277 year: 'numeric',278 month: 'long',279 day: 'numeric'280 })}281 </div>282 </div>283 )}284 </div>285 </div>286 </div>287 )}288 </div>289 </CardContent>290 </Card>291 </div>292 );293}294