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/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