Skip to content

Commit 21a0e1d

Browse files
eh par contre, pour les entreprise utilisateur qui parte en bourse, depu
1 parent ab9f8b8 commit 21a0e1d

File tree

12 files changed

+361
-660
lines changed

12 files changed

+361
-660
lines changed

src/app/companies/[companyId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
8484
)}
8585
{company.isListed ? (
8686
<Button asChild>
87-
<Link href={`/trading/${company.ticker}`}>Trader sur le marché</Link>
87+
<Link href="/companies">Trader à la Bourse</Link>
8888
</Button>
8989
) : (
9090
<InvestDialog company={company}>

src/app/trading/[ticker]/page.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ export default function AssetDetailPage() {
2626
}
2727

2828
if (!asset) {
29+
// This part of the UI might be briefly shown if a user navigates
30+
// to a company ticker URL directly. The router should redirect them,
31+
// but as a fallback, we show a helpful message.
2932
return (
3033
<div className="text-center">
31-
<h2 className="text-2xl font-bold">Actif non trouvé</h2>
32-
<p className="text-muted-foreground">L'actif avec le ticker "{ticker}" n'existe pas.</p>
33-
<Button onClick={() => router.push('/trading')} className="mt-4">
34-
Retour à la Salle des Marchés
34+
<h2 className="text-2xl font-bold">Actif non trouvé ou non listé</h2>
35+
<p className="text-muted-foreground">L'actif "{ticker}" n'est pas sur le marché principal. Les entreprises de joueurs sont sur la page "Entreprises".</p>
36+
<Button onClick={() => router.push('/companies')} className="mt-4 mr-2">
37+
Aller à la Bourse des Entreprises
38+
</Button>
39+
<Button onClick={() => router.push('/trading')} className="mt-4" variant="secondary">
40+
Aller à la Salle des Marchés
3541
</Button>
3642
</div>
3743
);

src/app/trading/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default function TradingPage() {
6868
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
6969
<div>
7070
<CardTitle>Salle des Marchés</CardTitle>
71-
<CardDescription>Achetez et vendez des actifs en utilisant les données du marché en temps réel.</CardDescription>
71+
<CardDescription>Achetez et vendez des actions et des cryptos. Les entreprises de joueurs sont dans la section "Entreprises".</CardDescription>
7272
</div>
7373
<div className="flex items-center gap-2">
7474
<div className="relative">

src/components/companies-client-page.tsx

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,39 @@ function CompanyTableRow({ company, type }: { company: any, type: 'managed' | 'i
4343
<Link href={`/companies/${company.id}`}>Détails</Link>
4444
</Button>
4545

46-
{hasShares && (
47-
company.isListed ? (
48-
<Button asChild variant="secondary" size="sm">
49-
<Link href={`/trading/${company.ticker}`}>Trader</Link>
50-
</Button>
51-
) : (
52-
<SellSharesDialog
53-
companyId={company.id}
54-
companyName={company.name}
55-
sharePrice={company.sharePrice}
56-
sharesHeld={company.sharesHeld}
57-
>
58-
<Button size="sm" variant="destructive">Vendre</Button>
59-
</SellSharesDialog>
60-
)
61-
)}
62-
63-
{type === 'other' && !company.isListed && (
64-
<InvestDialog company={company as CompanyWithDetails}>
65-
<Button size="sm">Investir</Button>
66-
</InvestDialog>
46+
{company.isListed ? (
47+
<>
48+
<InvestDialog company={company as CompanyWithDetails} isListed>
49+
<Button size="sm">Acheter</Button>
50+
</InvestDialog>
51+
{hasShares && (
52+
<SellSharesDialog
53+
companyId={company.id}
54+
companyName={company.name}
55+
sharePrice={company.sharePrice}
56+
sharesHeld={company.sharesHeld}
57+
isListed
58+
>
59+
<Button size="sm" variant="destructive">Vendre</Button>
60+
</SellSharesDialog>
61+
)}
62+
</>
63+
) : ( // Not listed
64+
<>
65+
{hasShares && (
66+
<SellSharesDialog
67+
companyId={company.id}
68+
companyName={company.name}
69+
sharePrice={company.sharePrice}
70+
sharesHeld={company.sharesHeld}
71+
>
72+
<Button size="sm" variant="destructive">Vendre</Button>
73+
</SellSharesDialog>
74+
)}
75+
<InvestDialog company={company as CompanyWithDetails}>
76+
<Button size="sm">Investir</Button>
77+
</InvestDialog>
78+
</>
6779
)}
6880
</TableCell>
6981
</TableRow>
@@ -121,8 +133,8 @@ export function CompaniesClientPage({ managedCompanies, investedCompanies, other
121133
<div className="space-y-6">
122134
<div className="flex items-center justify-between">
123135
<div>
124-
<h1 className="text-2xl font-bold tracking-tight">Espace Entreprises</h1>
125-
<p className="text-muted-foreground">Créez, gérez et investissez dans des entreprises dirigées par des joueurs.</p>
136+
<h1 className="text-2xl font-bold tracking-tight">Espace Entreprises & Bourse</h1>
137+
<p className="text-muted-foreground">Créez, gérez et tradez des entreprises dirigées par des joueurs.</p>
126138
</div>
127139
<CreateCompanyDialog />
128140
</div>
@@ -146,8 +158,8 @@ export function CompaniesClientPage({ managedCompanies, investedCompanies, other
146158
)}
147159

148160
<CompanyTable
149-
title="Marché des Entreprises"
150-
description="Toutes les entreprises disponibles à l'investissement."
161+
title="Bourse des Entreprises"
162+
description="Toutes les entreprises disponibles à l'investissement ou au trading."
151163
companies={otherCompanies}
152164
type="other"
153165
/>

src/components/invest-dialog.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { useRouter } from 'next/navigation';
1919
interface InvestDialogProps {
2020
company: CompanyWithDetails;
2121
children: React.ReactNode;
22+
isListed?: boolean;
2223
}
2324

2425
const formSchema = z.object({
2526
amount: z.coerce.number().positive({ message: 'Le montant doit être supérieur à zéro.' }),
2627
});
2728

28-
export function InvestDialog({ company, children }: InvestDialogProps) {
29+
export function InvestDialog({ company, children, isListed = false }: InvestDialogProps) {
2930
const [open, setOpen] = useState(false);
3031
const { cash, refreshPortfolio } = usePortfolio();
3132
const { toast } = useToast();
@@ -40,6 +41,9 @@ export function InvestDialog({ company, children }: InvestDialogProps) {
4041
const amount = form.watch('amount') || 0;
4142
const sharesToReceive = amount > 0 && company.sharePrice > 0 ? (amount / company.sharePrice) : 0;
4243

44+
const title = isListed ? `Acheter des actions ${company.name}` : `Investir dans ${company.name}`;
45+
const buttonText = isListed ? `Acheter pour` : `Investir`;
46+
4347
async function onSubmit(values: z.infer<typeof formSchema>) {
4448
const result = await investInCompany(company.id, values.amount);
4549
if (result.error) {
@@ -61,9 +65,9 @@ export function InvestDialog({ company, children }: InvestDialogProps) {
6165
<DialogTrigger asChild>{children}</DialogTrigger>
6266
<DialogContent>
6367
<DialogHeader>
64-
<DialogTitle>Investir dans {company.name}</DialogTitle>
68+
<DialogTitle>{title}</DialogTitle>
6569
<DialogDescription>
66-
Prix de l'action : ${company.sharePrice.toFixed(2)}. Fonds disponibles : ${cash.toFixed(2)}
70+
Prix de l'action : ${company.sharePrice.toFixed(4)}. Fonds disponibles : ${cash.toFixed(2)}
6771
</DialogDescription>
6872
</DialogHeader>
6973
<Form {...form}>
@@ -73,7 +77,7 @@ export function InvestDialog({ company, children }: InvestDialogProps) {
7377
name="amount"
7478
render={({ field }) => (
7579
<FormItem>
76-
<FormLabel>Montant à investir</FormLabel>
80+
<FormLabel>Montant</FormLabel>
7781
<div className="relative">
7882
<FormControl>
7983
<Input
@@ -101,7 +105,7 @@ export function InvestDialog({ company, children }: InvestDialogProps) {
101105
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>Annuler</Button>
102106
<Button type="submit" disabled={form.formState.isSubmitting || amount > cash || !form.formState.isValid}>
103107
{form.formState.isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
104-
Investir ${amount > 0 ? amount.toFixed(2) : '0.00'}
108+
{buttonText} ${amount > 0 ? amount.toFixed(2) : '0.00'}
105109
</Button>
106110
</DialogFooter>
107111
</form>

src/components/portfolio-client-page.tsx

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,38 @@ import { TradeDialog } from '@/components/trade-dialog';
1010
import { Button } from '@/components/ui/button';
1111
import { Loader2 } from 'lucide-react';
1212
import type { AssetFromDb } from '@/lib/actions/assets';
13+
import { SellSharesDialog } from './sell-shares-dialog';
14+
import { InvestDialog } from './invest-dialog';
15+
import type { CompanyWithDetails } from '@/lib/actions/companies';
1316

1417
export default function PortfolioClientPage() {
1518
const { holdings, cash, initialCash, loading } = usePortfolio();
1619
const { getAssetByTicker, assets: marketAssets, loading: marketLoading } = useMarketData();
1720

1821
const holdingsWithMarketData = useMemo(() => {
19-
if (!marketAssets.length) return []; // Don't compute until market data is ready
22+
if (marketLoading && !marketAssets.length) return [];
2023
return holdings.map(holding => {
21-
const asset = getAssetByTicker(holding.ticker);
22-
const currentPrice = asset?.price || holding.avgCost;
24+
const isCompany = holding.type === 'Company Share';
25+
const asset = !isCompany ? getAssetByTicker(holding.ticker) : undefined;
26+
27+
// For company shares, the price is stored directly in the holding from the portfolio context
28+
const currentPrice = isCompany ? holding.avgCost : (asset?.price || holding.avgCost);
29+
2330
const currentValue = holding.quantity * currentPrice;
24-
const totalCost = holding.quantity * holding.avgCost;
31+
const totalCost = holding.quantity * holding.avgCost; // This might be less accurate for company shares over time, but good for a start
2532
const pnl = currentValue - totalCost;
2633
const pnlPercent = totalCost > 0 ? (pnl / totalCost) * 100 : 0;
2734
return {
2835
...holding,
29-
asset: asset!, // Asset should exist if we have a holding
36+
asset: asset,
37+
isCompanyShare: isCompany,
3038
currentPrice,
3139
currentValue,
3240
pnl,
3341
pnlPercent
3442
};
3543
}).sort((a, b) => b.currentValue - a.currentValue);
36-
}, [holdings, getAssetByTicker, marketAssets]);
44+
}, [holdings, getAssetByTicker, marketAssets, marketLoading]);
3745

3846
const assetsValue = useMemo(() => holdingsWithMarketData.reduce((sum, holding) => sum + holding.currentValue, 0), [holdingsWithMarketData]);
3947
const portfolioValue = assetsValue + cash;
@@ -84,10 +92,8 @@ export default function PortfolioClientPage() {
8492
<TableRow>
8593
<TableHead>Actif</TableHead>
8694
<TableHead>Quantité</TableHead>
87-
<TableHead>Coût Moyen</TableHead>
8895
<TableHead>Prix Actuel</TableHead>
8996
<TableHead>Valeur Actuelle</TableHead>
90-
<TableHead>Gains/Pertes</TableHead>
9197
<TableHead className="text-right">Actions</TableHead>
9298
</TableRow>
9399
</TableHeader>
@@ -100,23 +106,37 @@ export default function PortfolioClientPage() {
100106
<div className="text-sm text-muted-foreground">{holding.ticker}</div>
101107
</TableCell>
102108
<TableCell>{holding.quantity.toLocaleString(undefined, { maximumFractionDigits: 8 })}</TableCell>
103-
<TableCell>${holding.avgCost.toFixed(2)}</TableCell>
104-
<TableCell>${holding.currentPrice.toFixed(2)}</TableCell>
109+
<TableCell>${holding.currentPrice.toFixed(holding.currentPrice > 10 ? 2 : 4)}</TableCell>
105110
<TableCell>${holding.currentValue.toFixed(2)}</TableCell>
106-
<TableCell className={`font-medium ${holding.pnl >= 0 ? 'text-green-500' : 'text-red-500'}`}>
107-
<div>{holding.pnl >= 0 ? '+' : '-'}${Math.abs(holding.pnl).toFixed(2)}</div>
108-
<div className="text-xs">({holding.pnlPercent.toFixed(2)}%)</div>
109-
</TableCell>
110111
<TableCell className="text-right space-x-2">
111-
<Button asChild variant="outline" size="sm">
112-
<Link href={`/trading/${holding.asset.ticker}`}>Détails</Link>
113-
</Button>
114-
{holding.asset ? (
115-
<TradeDialog asset={holding.asset as AssetFromDb} tradeType="Sell">
116-
<Button variant="secondary" size="sm">Vendre</Button>
117-
</TradeDialog>
112+
{holding.isCompanyShare ? (
113+
<>
114+
<Button asChild variant="outline" size="sm">
115+
<Link href={`/companies/${holding.id}`}>Détails</Link>
116+
</Button>
117+
<SellSharesDialog
118+
companyId={holding.id}
119+
companyName={holding.name}
120+
sharePrice={holding.currentPrice}
121+
sharesHeld={holding.quantity}
122+
isListed={true}
123+
>
124+
<Button variant="secondary" size="sm">Vendre</Button>
125+
</SellSharesDialog>
126+
</>
118127
) : (
119-
<Button variant="secondary" size="sm" disabled>Vendre</Button>
128+
<>
129+
<Button asChild variant="outline" size="sm">
130+
<Link href={`/trading/${holding.asset!.ticker}`}>Détails</Link>
131+
</Button>
132+
{holding.asset ? (
133+
<TradeDialog asset={holding.asset as AssetFromDb} tradeType="Sell">
134+
<Button variant="secondary" size="sm">Vendre</Button>
135+
</TradeDialog>
136+
) : (
137+
<Button variant="secondary" size="sm" disabled>Vendre</Button>
138+
)}
139+
</>
120140
)}
121141
</TableCell>
122142
</TableRow>

src/components/sell-shares-dialog.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ interface SellSharesDialogProps {
2020
sharePrice: number;
2121
sharesHeld: number;
2222
children: React.ReactNode;
23+
isListed?: boolean;
2324
}
2425

2526
const formSchema = z.object({
2627
quantity: z.coerce.number().positive({ message: 'La quantité doit être supérieure à zéro.' }),
2728
});
2829

29-
export function SellSharesDialog({ companyId, companyName, sharePrice, sharesHeld, children }: SellSharesDialogProps) {
30+
export function SellSharesDialog({ companyId, companyName, sharePrice, sharesHeld, children, isListed = false }: SellSharesDialogProps) {
3031
const [open, setOpen] = useState(false);
3132
const { toast } = useToast();
3233
const router = useRouter();
@@ -41,6 +42,11 @@ export function SellSharesDialog({ companyId, companyName, sharePrice, sharesHel
4142

4243
const quantity = form.watch('quantity') || 0;
4344
const proceeds = quantity * sharePrice;
45+
const title = isListed ? `Vendre des actions de ${companyName}` : `Vendre des parts de ${companyName}`;
46+
const description = isListed
47+
? `Prix de vente par action : $${sharePrice.toFixed(4)}. Parts détenues : ${sharesHeld.toLocaleString(undefined, {maximumFractionDigits: 4})}`
48+
: `Prix de rachat par part : $${sharePrice.toFixed(4)}. Parts détenues : ${sharesHeld.toLocaleString(undefined, {maximumFractionDigits: 4})}`;
49+
4450

4551
async function onSubmit(values: z.infer<typeof formSchema>) {
4652
if (values.quantity > sharesHeld) {
@@ -67,9 +73,9 @@ export function SellSharesDialog({ companyId, companyName, sharePrice, sharesHel
6773
<DialogTrigger asChild>{children}</DialogTrigger>
6874
<DialogContent>
6975
<DialogHeader>
70-
<DialogTitle>Vendre des Parts de {companyName}</DialogTitle>
76+
<DialogTitle>{title}</DialogTitle>
7177
<DialogDescription>
72-
Prix de rachat par part : ${sharePrice.toFixed(2)}. Parts détenues : {sharesHeld.toLocaleString(undefined, {maximumFractionDigits: 4})}
78+
{description}
7379
</DialogDescription>
7480
</DialogHeader>
7581
<Form {...form}>

0 commit comments

Comments
 (0)