Skip to content

Commit 0ae0c74

Browse files
continue le syst d'ordre automatique
--- fait que quand tu recuo le B
1 parent 91a307f commit 0ae0c74

File tree

5 files changed

+219
-52
lines changed

5 files changed

+219
-52
lines changed

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { getRigById } from '@/lib/mining';
1515
import { ManageMembersDialog } from '@/components/manage-members-dialog';
1616
import { WithdrawCompanyCashDialog } from '@/components/withdraw-company-cash-dialog';
1717
import { ListCompanyButton } from '@/components/list-company-button';
18-
import { claimCompanyBtc } from '@/lib/actions/companies';
18+
import { ClaimBtcButton } from '@/components/claim-btc-button';
1919

2020

2121
function getInitials(name: string) {
@@ -48,8 +48,6 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
4848
const rigData = getRigById(ownedRig.rigId);
4949
return total + (rigData?.hashRateMhs || 0) * ownedRig.quantity;
5050
}, 0);
51-
52-
const claimCompanyBtcWithId = claimCompanyBtc.bind(null, company.id);
5351

5452
return (
5553
<div className="space-y-6">
@@ -154,9 +152,7 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
154152
<div className="text-2xl font-bold">{company.unclaimedBtc.toFixed(8)} BTC</div>
155153
<p className="text-xs text-muted-foreground">Généré par les opérations de minage</p>
156154
{isCEO && company.unclaimedBtc > 1e-9 && (
157-
<form action={claimCompanyBtcWithId}>
158-
<Button size="sm" className="mt-4 w-full" type="submit">Réclamer les BTC</Button>
159-
</form>
155+
<ClaimBtcButton companyId={company.id} />
160156
)}
161157
</CardContent>
162158
</Card>

src/app/trading/page.tsx

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ import { Button } from "@/components/ui/button";
88
import { Badge } from "@/components/ui/badge";
99
import { TradeDialog } from "@/components/trade-dialog";
1010
import { useMarketData } from '@/context/market-data-context';
11-
import { Loader2, Search } from 'lucide-react';
11+
import { Loader2, Search, List, LayoutGrid } from 'lucide-react';
1212
import { Input } from '@/components/ui/input';
1313
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
14+
import { AssetCard } from '@/components/asset-card';
1415

1516

1617
export default function TradingPage() {
1718
const { assets, loading } = useMarketData();
1819
const [searchTerm, setSearchTerm] = useState('');
1920
const [sortOption, setSortOption] = useState('marketCap_desc');
21+
const [viewMode, setViewMode] = useState<'list' | 'grid'>('list');
2022

2123
const filteredAndSortedAssets = useMemo(() => {
2224
let processedAssets = [...assets];
@@ -96,53 +98,69 @@ export default function TradingPage() {
9698
<SelectItem value="change_asc">Variation (Croissant)</SelectItem>
9799
</SelectContent>
98100
</Select>
101+
<div className="flex items-center gap-1 rounded-md bg-muted p-1">
102+
<Button variant={viewMode === 'list' ? 'secondary' : 'ghost'} size="icon" className="h-8 w-8" onClick={() => setViewMode('list')}>
103+
<List className="h-4 w-4" />
104+
</Button>
105+
<Button variant={viewMode === 'grid' ? 'secondary' : 'ghost'} size="icon" className="h-8 w-8" onClick={() => setViewMode('grid')}>
106+
<LayoutGrid className="h-4 w-4" />
107+
</Button>
108+
</div>
99109
</div>
100110
</div>
101111
</CardHeader>
102112
<CardContent>
103-
<Table>
104-
<TableHeader>
105-
<TableRow>
106-
<TableHead>Actif</TableHead>
107-
<TableHead>Type</TableHead>
108-
<TableHead>Prix</TableHead>
109-
<TableHead>Variation (24h)</TableHead>
110-
<TableHead>Cap. Boursière</TableHead>
111-
<TableHead className="text-right">Actions</TableHead>
112-
</TableRow>
113-
</TableHeader>
114-
<TableBody>
115-
{filteredAndSortedAssets.map((asset) => {
116-
const changeIsPositive = asset.change24h.startsWith('+');
117-
return (
118-
<TableRow key={asset.ticker}>
119-
<TableCell>
120-
<div className="font-medium">{asset.name}</div>
121-
<div className="text-sm text-muted-foreground">{asset.ticker}</div>
122-
</TableCell>
123-
<TableCell>
124-
<Badge variant="outline">{asset.type}</Badge>
125-
</TableCell>
126-
<TableCell className="font-mono">${asset.price.toFixed(2)}</TableCell>
127-
<TableCell className={changeIsPositive ? 'text-green-500 dark:text-green-400' : 'text-red-500 dark:text-red-400'}>
128-
{asset.change24h}
129-
</TableCell>
130-
<TableCell>{asset.marketCap}</TableCell>
131-
<TableCell className="text-right space-x-2">
132-
<Button asChild variant="outline" size="sm">
133-
<Link href={`/trading/${asset.ticker}`}>Détails</Link>
134-
</Button>
135-
<TradeDialog asset={asset} tradeType="Buy">
136-
<Button variant="outline" size="sm">Acheter</Button>
137-
</TradeDialog>
138-
<TradeDialog asset={asset} tradeType="Sell">
139-
<Button variant="secondary" size="sm">Vendre</Button>
140-
</TradeDialog>
141-
</TableCell>
142-
</TableRow>
143-
)})}
144-
</TableBody>
145-
</Table>
113+
{viewMode === 'list' ? (
114+
<Table>
115+
<TableHeader>
116+
<TableRow>
117+
<TableHead>Actif</TableHead>
118+
<TableHead>Type</TableHead>
119+
<TableHead>Prix</TableHead>
120+
<TableHead>Variation (24h)</TableHead>
121+
<TableHead>Cap. Boursière</TableHead>
122+
<TableHead className="text-right">Actions</TableHead>
123+
</TableRow>
124+
</TableHeader>
125+
<TableBody>
126+
{filteredAndSortedAssets.map((asset) => {
127+
const changeIsPositive = asset.change24h.startsWith('+');
128+
return (
129+
<TableRow key={asset.ticker}>
130+
<TableCell>
131+
<div className="font-medium">{asset.name}</div>
132+
<div className="text-sm text-muted-foreground">{asset.ticker}</div>
133+
</TableCell>
134+
<TableCell>
135+
<Badge variant="outline">{asset.type}</Badge>
136+
</TableCell>
137+
<TableCell className="font-mono">${asset.price.toFixed(2)}</TableCell>
138+
<TableCell className={changeIsPositive ? 'text-green-500 dark:text-green-400' : 'text-red-500 dark:text-red-400'}>
139+
{asset.change24h}
140+
</TableCell>
141+
<TableCell>{asset.marketCap}</TableCell>
142+
<TableCell className="text-right space-x-2">
143+
<Button asChild variant="outline" size="sm">
144+
<Link href={`/trading/${asset.ticker}`}>Détails</Link>
145+
</Button>
146+
<TradeDialog asset={asset} tradeType="Buy">
147+
<Button variant="outline" size="sm">Acheter</Button>
148+
</TradeDialog>
149+
<TradeDialog asset={asset} tradeType="Sell">
150+
<Button variant="secondary" size="sm">Vendre</Button>
151+
</TradeDialog>
152+
</TableCell>
153+
</TableRow>
154+
)})}
155+
</TableBody>
156+
</Table>
157+
) : (
158+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
159+
{filteredAndSortedAssets.map((asset) => (
160+
<AssetCard key={asset.ticker} asset={asset} />
161+
))}
162+
</div>
163+
)}
146164
</CardContent>
147165
</Card>
148166
);

src/components/asset-card.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use client';
2+
3+
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";
4+
import { Button } from "@/components/ui/button";
5+
import { Area, AreaChart, Tooltip } from 'recharts';
6+
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart";
7+
import type { AssetFromDb } from '@/lib/actions/assets';
8+
import { useMarketData } from '@/context/market-data-context';
9+
import { TradeDialog } from './trade-dialog';
10+
import Link from 'next/link';
11+
import { Badge } from "./ui/badge";
12+
13+
export function AssetCard({ asset }: { asset: AssetFromDb }) {
14+
const { getHistoricalData } = useMarketData();
15+
const historicalData = getHistoricalData(asset.ticker);
16+
17+
const changeIsPositive = asset.change24h.startsWith('+');
18+
const chartConfig = {
19+
price: {
20+
label: 'Prix',
21+
color: changeIsPositive ? 'hsl(var(--chart-1))' : 'hsl(var(--destructive))',
22+
},
23+
};
24+
25+
return (
26+
<Card className="flex flex-col">
27+
<CardHeader>
28+
<div className="flex items-start justify-between">
29+
<div>
30+
<CardTitle className="text-base">{asset.name} ({asset.ticker})</CardTitle>
31+
<CardDescription>{asset.marketCap}</CardDescription>
32+
</div>
33+
<Badge variant="outline">{asset.type}</Badge>
34+
</div>
35+
</CardHeader>
36+
<CardContent className="flex-grow space-y-4">
37+
<div className="h-[100px] w-full -translate-x-4">
38+
<ChartContainer config={chartConfig}>
39+
<AreaChart
40+
accessibilityLayer
41+
data={historicalData}
42+
margin={{ top: 5, right: 10, left: 10, bottom: 0 }}
43+
>
44+
<defs>
45+
<linearGradient id={`fill-chart-${asset.ticker}`} x1="0" y1="0" x2="0" y2="1">
46+
<stop offset="5%" stopColor="var(--color-price)" stopOpacity={0.8}/>
47+
<stop offset="95%" stopColor="var(--color-price)" stopOpacity={0.1}/>
48+
</linearGradient>
49+
</defs>
50+
<Tooltip
51+
cursor={false}
52+
content={<ChartTooltipContent indicator="dot" hideLabel />}
53+
/>
54+
<Area
55+
dataKey="price"
56+
type="natural"
57+
fill={`url(#fill-chart-${asset.ticker})`}
58+
strokeWidth={2}
59+
stroke="var(--color-price)"
60+
stackId="a"
61+
/>
62+
</AreaChart>
63+
</ChartContainer>
64+
</div>
65+
<div>
66+
<div className="text-xl font-bold">${asset.price.toFixed(asset.price > 10 ? 2 : 4)}</div>
67+
<p className={`text-xs ${changeIsPositive ? 'text-green-500' : 'text-red-500'}`}>
68+
{asset.change24h} (24h)
69+
</p>
70+
</div>
71+
</CardContent>
72+
<CardFooter className="flex justify-end gap-2">
73+
<Button asChild variant="outline" size="sm">
74+
<Link href={`/trading/${asset.ticker}`}>Détails</Link>
75+
</Button>
76+
<TradeDialog asset={asset} tradeType="Buy">
77+
<Button size="sm">Acheter</Button>
78+
</TradeDialog>
79+
</CardFooter>
80+
</Card>
81+
);
82+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use client';
2+
3+
import { useTransition } from 'react';
4+
import { Button } from '@/components/ui/button';
5+
import { claimCompanyBtc } from '@/lib/actions/companies';
6+
import { useToast } from '@/hooks/use-toast';
7+
import { useRouter } from 'next/navigation';
8+
import { Loader2 } from 'lucide-react';
9+
import { usePortfolio } from '@/context/portfolio-context';
10+
11+
export function ClaimBtcButton({ companyId }: { companyId: number }) {
12+
const [isPending, startTransition] = useTransition();
13+
const { toast } = useToast();
14+
const router = useRouter();
15+
const { refreshPortfolio } = usePortfolio();
16+
17+
const handleClaim = () => {
18+
startTransition(async () => {
19+
const result = await claimCompanyBtc(companyId);
20+
if (result.error) {
21+
toast({ variant: 'destructive', title: 'Erreur', description: result.error });
22+
} else {
23+
toast({ title: 'Succès !', description: result.success });
24+
await refreshPortfolio(); // Refresh client-side portfolio
25+
router.refresh(); // Refresh server components
26+
}
27+
});
28+
};
29+
30+
return (
31+
<Button size="sm" className="mt-4 w-full" onClick={handleClaim} disabled={isPending}>
32+
{isPending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
33+
Réclamer les BTC
34+
</Button>
35+
);
36+
}

src/components/trade-dialog.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
1919
import { Input } from '@/components/ui/input';
2020
import { Loader2 } from 'lucide-react';
2121
import { AssetFromDb } from '@/lib/actions/assets';
22+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
23+
import { Badge } from '@/components/ui/badge';
2224

2325
interface TradeDialogProps {
2426
asset: AssetFromDb;
@@ -82,7 +84,7 @@ export function TradeDialog({ asset, tradeType, children }: TradeDialogProps) {
8284
{tradeTypeFr} {asset.name} ({asset.ticker})
8385
</DialogTitle>
8486
<DialogDescription>
85-
Prix actuel: ${asset.price.toFixed(2)}.
87+
Prix actuel: ${asset.price.toFixed(asset.price > 10 ? 2 : 4)}.
8688
{tradeType === 'Buy' ? ` Fonds disponibles: $${cash.toFixed(2)}.` : ` Vous possédez: ${holdingQuantity.toLocaleString()}.`}
8789
</DialogDescription>
8890
</DialogHeader>
@@ -130,9 +132,42 @@ export function TradeDialog({ asset, tradeType, children }: TradeDialogProps) {
130132
</FormItem>
131133
)}
132134
/>
133-
<div className="text-sm font-medium">
135+
136+
<Accordion type="single" collapsible className="w-full">
137+
<AccordionItem value="item-1">
138+
<AccordionTrigger>Ordre Automatique (Avancé)</AccordionTrigger>
139+
<AccordionContent>
140+
<div className="space-y-4 pt-2">
141+
<div className="flex items-center justify-between">
142+
<h4 className="text-sm font-medium">Définir des ordres Stop-Loss / Take-Profit</h4>
143+
<Badge variant="outline">Bientôt disponible</Badge>
144+
</div>
145+
<p className="text-sm text-muted-foreground">
146+
Ces ordres se déclencheront automatiquement pour vendre vos actifs si le prix atteint les seuils que vous avez définis.
147+
</p>
148+
<div className="grid grid-cols-2 gap-4">
149+
<FormItem>
150+
<FormLabel>Prix Stop-Loss</FormLabel>
151+
<FormControl>
152+
<Input type="number" placeholder={`< ${asset.price.toFixed(2)}`} disabled />
153+
</FormControl>
154+
</FormItem>
155+
<FormItem>
156+
<FormLabel>Prix Take-Profit</FormLabel>
157+
<FormControl>
158+
<Input type="number" placeholder={`> ${asset.price.toFixed(2)}`} disabled />
159+
</FormControl>
160+
</FormItem>
161+
</div>
162+
</div>
163+
</AccordionContent>
164+
</AccordionItem>
165+
</Accordion>
166+
167+
<div className="text-sm font-medium pt-2">
134168
{tradeType === 'Buy' ? 'Coût total' : 'Produit total'}: ${totalValue.toFixed(2)}
135169
</div>
170+
136171
<DialogFooter>
137172
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
138173
Annuler

0 commit comments

Comments
 (0)