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%
7.9 KB · 208 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/components/explore/ownership-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 { Building2, TrendingUp, TrendingDown, Percent, Calendar } from "lucide-react";2122interface InstitutionalHolder {23  holder: string;24  shares: number;25  dateReported: string;26  change: number;27  [key: string]: any;28}2930interface OwnershipViewProps {31  institutionalHolders?: InstitutionalHolder[];32}3334export function OwnershipView({ institutionalHolders }: OwnershipViewProps) {35  const formatNumber = (value: number) => {36    if (!value) return "N/A";37    if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;38    if (value >= 1e6) return `${(value / 1e6).toFixed(2)}M`;39    if (value >= 1e3) return `${(value / 1e3).toFixed(2)}K`;40    return value.toLocaleString();41  };4243  const formatDate = (dateString: string) => {44    return new Date(dateString).toLocaleDateString('fr-FR', {45      year: 'numeric',46      month: 'long',47      day: 'numeric'48    });49  };5051  if (!institutionalHolders || institutionalHolders.length === 0) {52    return (53      <Card>54        <CardHeader>55          <CardTitle className="flex items-center gap-2">56            <Building2 className="w-5 h-5" />57            Actionnariat Institutionnel58          </CardTitle>59        </CardHeader>60        <CardContent>61          <p className="text-muted-foreground">Aucune donnée d'actionnariat disponible</p>62        </CardContent>63      </Card>64    );65  }6667  const totalShares = institutionalHolders.reduce((sum, holder) => sum + (holder.shares || 0), 0);68  const holdersWithIncrease = institutionalHolders.filter(h => (h.change || 0) > 0).length;69  const holdersWithDecrease = institutionalHolders.filter(h => (h.change || 0) < 0).length;7071  return (72    <div className="space-y-6">73      {/* Summary Stats */}74      <div className="grid grid-cols-1 md:grid-cols-4 gap-4">75        <Card>76          <CardHeader className="pb-3">77            <CardDescription>Total Institutions</CardDescription>78          </CardHeader>79          <CardContent>80            <div className="text-3xl font-bold">{institutionalHolders.length}</div>81          </CardContent>82        </Card>8384        <Card>85          <CardHeader className="pb-3">86            <CardDescription>Actions Détenues</CardDescription>87          </CardHeader>88          <CardContent>89            <div className="text-3xl font-bold">{formatNumber(totalShares)}</div>90          </CardContent>91        </Card>9293        <Card>94          <CardHeader className="pb-3">95            <CardDescription>Augmentations</CardDescription>96          </CardHeader>97          <CardContent>98            <div className="text-3xl font-bold text-green-500 flex items-center gap-2">99              <TrendingUp className="w-6 h-6" />100              {holdersWithIncrease}101            </div>102          </CardContent>103        </Card>104105        <Card>106          <CardHeader className="pb-3">107            <CardDescription>Diminutions</CardDescription>108          </CardHeader>109          <CardContent>110            <div className="text-3xl font-bold text-red-500 flex items-center gap-2">111              <TrendingDown className="w-6 h-6" />112              {holdersWithDecrease}113            </div>114          </CardContent>115        </Card>116      </div>117118      {/* Detailed Table */}119      <Card>120        <CardHeader>121          <CardTitle className="flex items-center gap-2">122            <Building2 className="w-5 h-5" />123            Principaux Actionnaires Institutionnels124          </CardTitle>125          <CardDescription>126            Détention institutionnelle et changements récents127          </CardDescription>128        </CardHeader>129        <CardContent>130          <div className="overflow-x-auto">131            <Table>132              <TableHeader>133                <TableRow>134                  <TableHead className="w-[50px]">Rang</TableHead>135                  <TableHead className="min-w-[300px]">Institution</TableHead>136                  <TableHead className="text-right">Actions Détenues</TableHead>137                  <TableHead className="text-right">Variation</TableHead>138                  <TableHead className="text-right">% de Variation</TableHead>139                  <TableHead>Date de Rapport</TableHead>140                </TableRow>141              </TableHeader>142              <TableBody>143                {institutionalHolders.map((holder, idx) => {144                  const changeValue = holder.change || 0;145                  const isPositive = changeValue >= 0;146                  const percentChange = holder.shares > 0 ? (changeValue / holder.shares) * 100 : 0;147148                  return (149                    <TableRow key={idx} className="hover:bg-muted/50">150                      <TableCell className="font-bold text-muted-foreground">151                        #{idx + 1}152                      </TableCell>153                      <TableCell>154                        <div className="flex items-center gap-2">155                          <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">156                            <Building2 className="w-5 h-5 text-primary" />157                          </div>158                          <div>159                            <div className="font-semibold">{holder.holder}</div>160                            {holder.shares > 0 && (161                              <div className="text-xs text-muted-foreground">162                                {((holder.shares / totalShares) * 100).toFixed(2)}% du total163                              </div>164                            )}165                          </div>166                        </div>167                      </TableCell>168                      <TableCell className="text-right font-semibold">169                        {formatNumber(holder.shares)}170                      </TableCell>171                      <TableCell className="text-right">172                        <div className={`flex items-center justify-end gap-1 ${isPositive ? 'text-green-500' : 'text-red-500'}`}>173                          {isPositive ? (174                            <TrendingUp className="w-4 h-4" />175                          ) : (176                            <TrendingDown className="w-4 h-4" />177                          )}178                          <span className="font-semibold">179                            {isPositive ? '+' : ''}{formatNumber(changeValue)}180                          </span>181                        </div>182                      </TableCell>183                      <TableCell className="text-right">184                        <Badge185                          variant={isPositive ? "default" : "destructive"}186                          className="font-semibold"187                        >188                          {isPositive ? '+' : ''}{percentChange.toFixed(2)}%189                        </Badge>190                      </TableCell>191                      <TableCell>192                        <div className="flex items-center gap-2 text-sm text-muted-foreground">193                          <Calendar className="w-4 h-4" />194                          {formatDate(holder.dateReported)}195                        </div>196                      </TableCell>197                    </TableRow>198                  );199                })}200              </TableBody>201            </Table>202          </div>203        </CardContent>204      </Card>205    </div>206  );207}208