SPB Git

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%
11.7 KB · 294 lines tsx
Raw Blame History
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