Skip to content

Commit bc16bc1

Browse files
dans le truc de vente met aussi le nombre d'actif de l'entreprise
---
1 parent 03c6a13 commit bc16bc1

File tree

6 files changed

+189
-11
lines changed

6 files changed

+189
-11
lines changed

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,28 @@ import { notFound } from 'next/navigation';
33
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
44
import { Badge } from '@/components/ui/badge';
55
import { Button } from '@/components/ui/button';
6-
import { ArrowLeft, Landmark, Users, DollarSign, LineChart, Briefcase, Percent, Package } from 'lucide-react';
6+
import { ArrowLeft, Landmark, Users, DollarSign, LineChart, Briefcase, Percent, Package, Cpu } from 'lucide-react';
77
import Link from 'next/link';
88
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
99
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
1010
import { InvestDialog } from '@/components/invest-dialog';
1111
import { getSession } from '@/lib/session';
1212
import { ManageCompanyAssetsDialog } from '@/components/manage-company-assets-dialog';
1313
import { AddCompanyCashDialog } from '@/components/add-company-cash-dialog';
14+
import { getRigById } from '@/lib/mining';
1415

1516

1617
function getInitials(name: string) {
1718
if (!name) return '?';
1819
return name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
1920
}
2021

22+
const formatHashRate = (mhs: number) => {
23+
if (mhs >= 1_000_000) return `${(mhs / 1_000_000).toFixed(2)} TH/s`;
24+
if (mhs >= 1_000) return `${(mhs / 1_000).toFixed(2)} GH/s`;
25+
return `${mhs.toFixed(0)} MH/s`;
26+
};
27+
2128
export default async function CompanyDetailPage({ params }: { params: { companyId: string } }) {
2229
const companyId = parseInt(params.companyId, 10);
2330
if (isNaN(companyId)) {
@@ -33,6 +40,11 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
3340
const session = await getSession();
3441
const isCEO = company.members.some(member => member.userId === session?.id && member.role === 'ceo');
3542

43+
const totalCompanyHashRate = company.miningRigs.reduce((total, ownedRig) => {
44+
const rigData = getRigById(ownedRig.rigId);
45+
return total + (rigData?.hashRateMhs || 0) * ownedRig.quantity;
46+
}, 0);
47+
3648
return (
3749
<div className="space-y-6">
3850
<div className="flex items-center gap-4">
@@ -233,6 +245,49 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
233245
</Table>
234246
</CardContent>
235247
</Card>
248+
249+
<Card>
250+
<CardHeader>
251+
<CardTitle className="flex items-center gap-2">
252+
<Cpu /> Opération de Minage de l'Entreprise
253+
</CardTitle>
254+
<CardDescription>
255+
Matériel de minage détenu par {company.name}. Puissance totale : {formatHashRate(totalCompanyHashRate)}.
256+
</CardDescription>
257+
</CardHeader>
258+
<CardContent>
259+
<Table>
260+
<TableHeader>
261+
<TableRow>
262+
<TableHead>Matériel</TableHead>
263+
<TableHead>Quantité</TableHead>
264+
<TableHead className="text-right">Puissance de Hachage</TableHead>
265+
</TableRow>
266+
</TableHeader>
267+
<TableBody>
268+
{company.miningRigs.length > 0 ? company.miningRigs.map(ownedRig => {
269+
const rigData = getRigById(ownedRig.rigId);
270+
if (!rigData) return null;
271+
return (
272+
<TableRow key={ownedRig.id}>
273+
<TableCell>
274+
<div className="font-medium">{rigData.name}</div>
275+
</TableCell>
276+
<TableCell>{ownedRig.quantity}</TableCell>
277+
<TableCell className="text-right">{formatHashRate(rigData.hashRateMhs * ownedRig.quantity)}</TableCell>
278+
</TableRow>
279+
)
280+
}) : (
281+
<TableRow>
282+
<TableCell colSpan={3} className="h-24 text-center text-muted-foreground">
283+
Cette entreprise ne possède aucun matériel de minage.
284+
</TableCell>
285+
</TableRow>
286+
)}
287+
</TableBody>
288+
</Table>
289+
</CardContent>
290+
</Card>
236291
</div>
237292
);
238293
}

src/app/trading/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function TradingPage() {
2222
let processedAssets = [...assets];
2323

2424
const parseMarketCap = (mc: string): number => {
25-
if (!mc || typeof mc !== 'string') return 0;
25+
if (!mc || typeof mc !== 'string' || mc === 'N/A') return 0;
2626
const value = parseFloat(mc.replace(/[^0-9.]/g, ''));
2727
if (mc.includes('T')) return value * 1e12;
2828
if (mc.includes('B')) return value * 1e9;

src/components/manage-company-assets-dialog.tsx

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ import { zodResolver } from '@hookform/resolvers/zod';
66
import { z } from 'zod';
77
import { Button } from '@/components/ui/button';
88
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
9-
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
9+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from '@/components/ui/form';
1010
import { Input } from '@/components/ui/input';
1111
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
1212
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
1313
import { useMarketData } from '@/context/market-data-context';
1414
import { useToast } from '@/hooks/use-toast';
1515
import { Loader2 } from 'lucide-react';
16-
import { buyAssetForCompany, sellAssetForCompany } from '@/lib/actions/companies';
16+
import { buyAssetForCompany, sellAssetForCompany, buyMiningRigForCompany } from '@/lib/actions/companies';
1717
import type { CompanyWithDetails } from '@/lib/actions/companies';
1818
import { useRouter } from 'next/navigation';
1919
import { cn } from '@/lib/utils';
20+
import { MINING_RIGS } from '@/lib/mining';
2021

2122
interface ManageCompanyAssetsDialogProps {
2223
company: CompanyWithDetails;
@@ -33,6 +34,12 @@ const sellFormSchema = z.object({
3334
quantity: z.coerce.number().positive({ message: 'La quantité doit être positive.' }),
3435
});
3536

37+
const formatHashRate = (mhs: number) => {
38+
if (mhs >= 1_000_000) return `${(mhs / 1_000_000).toFixed(2)} TH/s`;
39+
if (mhs >= 1_000) return `${(mhs / 1_000).toFixed(2)} GH/s`;
40+
return `${mhs.toFixed(0)} MH/s`;
41+
};
42+
3643
export function ManageCompanyAssetsDialog({ company, children }: ManageCompanyAssetsDialogProps) {
3744
const [open, setOpen] = useState(false);
3845
const [activeTab, setActiveTab] = useState("buy");
@@ -52,6 +59,8 @@ export function ManageCompanyAssetsDialog({ company, children }: ManageCompanyAs
5259
mode: 'onChange',
5360
});
5461

62+
const [isBuyingMiner, setIsBuyingMiner] = useState<string | null>(null);
63+
5564
const selectedBuyTicker = buyForm.watch('ticker');
5665
const buyQuantity = buyForm.watch('quantity') || 0;
5766
const selectedBuyAsset = selectedBuyTicker ? getAssetByTicker(selectedBuyTicker) : null;
@@ -98,9 +107,23 @@ export function ManageCompanyAssetsDialog({ company, children }: ManageCompanyAs
98107
}
99108
}
100109

110+
async function onBuyMiner(rigId: string) {
111+
setIsBuyingMiner(rigId);
112+
const result = await buyMiningRigForCompany(company.id, rigId);
113+
if (result.error) {
114+
toast({ variant: 'destructive', title: 'Erreur', description: result.error });
115+
} else if (result.success) {
116+
toast({ title: 'Succès', description: result.success });
117+
router.refresh();
118+
setOpen(false);
119+
}
120+
setIsBuyingMiner(null);
121+
}
122+
101123
const resetForms = () => {
102124
buyForm.reset();
103125
sellForm.reset();
126+
setIsBuyingMiner(null);
104127
}
105128

106129
return (
@@ -118,9 +141,10 @@ export function ManageCompanyAssetsDialog({ company, children }: ManageCompanyAs
118141
</DialogHeader>
119142

120143
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
121-
<TabsList className="grid w-full grid-cols-2">
122-
<TabsTrigger value="buy">Acheter</TabsTrigger>
123-
<TabsTrigger value="sell">Vendre</TabsTrigger>
144+
<TabsList className="grid w-full grid-cols-3">
145+
<TabsTrigger value="buy">Acheter Actifs</TabsTrigger>
146+
<TabsTrigger value="sell">Vendre Actifs</TabsTrigger>
147+
<TabsTrigger value="miners">Acheter Matériel</TabsTrigger>
124148
</TabsList>
125149
<TabsContent value="buy">
126150
<Form {...buyForm}>
@@ -210,6 +234,11 @@ export function ManageCompanyAssetsDialog({ company, children }: ManageCompanyAs
210234
</FormItem>
211235
)}
212236
/>
237+
{selectedHolding && (
238+
<FormDescription>
239+
Vous possédez : {selectedHolding.quantity.toLocaleString(undefined, { maximumFractionDigits: 8 })} unités.
240+
</FormDescription>
241+
)}
213242
<FormField
214243
control={sellForm.control}
215244
name="quantity"
@@ -252,6 +281,28 @@ export function ManageCompanyAssetsDialog({ company, children }: ManageCompanyAs
252281
</form>
253282
</Form>
254283
</TabsContent>
284+
<TabsContent value="miners">
285+
<div className="space-y-4 pt-4">
286+
{MINING_RIGS.map((rig) => (
287+
<div key={rig.id} className="flex items-center justify-between rounded-lg border p-3">
288+
<div>
289+
<p className="font-semibold">{rig.name}</p>
290+
<p className="text-sm text-muted-foreground">
291+
Coût: ${rig.price.toLocaleString()} • Puissance: {formatHashRate(rig.hashRateMhs)}
292+
</p>
293+
</div>
294+
<Button
295+
size="sm"
296+
onClick={() => onBuyMiner(rig.id)}
297+
disabled={isBuyingMiner !== null || company.cash < rig.price}
298+
>
299+
{isBuyingMiner === rig.id && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
300+
Acheter
301+
</Button>
302+
</div>
303+
))}
304+
</div>
305+
</TabsContent>
255306
</Tabs>
256307
</DialogContent>
257308
</Dialog>

src/context/market-data-context.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const MarketDataProvider = ({ children }: { children: ReactNode }) => {
8484
if (!initialPrice) continue;
8585

8686
// Simulate price change
87-
const volatility = asset.type === 'Crypto' ? 0.015 : 0.005;
87+
const volatility = asset.type === 'Crypto' || asset.type === 'Forex' ? 0.015 : 0.005;
8888
const changeFactor = 1 + (Math.random() - 0.5) * 2 * volatility;
8989
asset.price *= changeFactor;
9090
updatedTickers[ticker] = asset.price;

src/lib/actions/companies.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import { z } from 'zod';
44
import { db } from '@/lib/db';
5-
import { companies, companyMembers, users, companyShares, companyHoldings, assets as assetsSchema } from '@/lib/db/schema';
5+
import { companies, companyMembers, users, companyShares, companyHoldings, assets as assetsSchema, companyMiningRigs } from '@/lib/db/schema';
66
import { getSession } from '../session';
77
import { revalidatePath } from 'next/cache';
88
import { eq, and, desc } from 'drizzle-orm';
99
import { updatePriceFromTrade } from './assets';
10+
import { getRigById } from '@/lib/mining';
1011

1112
const createCompanySchema = z.object({
1213
name: z.string().min(3, "Le nom doit faire au moins 3 caractères.").max(50),
@@ -176,7 +177,8 @@ export async function getCompanyById(companyId: number) {
176177
},
177178
holdings: {
178179
orderBy: (companyHoldings, { desc }) => [desc(companyHoldings.updatedAt)],
179-
}
180+
},
181+
miningRigs: true,
180182
}
181183
});
182184

@@ -212,7 +214,8 @@ export async function getCompanyById(companyId: number) {
212214
...h,
213215
quantity: parseFloat(h.quantity),
214216
avgCost: parseFloat(h.avgCost),
215-
}))
217+
})),
218+
miningRigs: company.miningRigs,
216219
};
217220

218221
} catch (error) {
@@ -432,3 +435,52 @@ export async function addCashToCompany(companyId: number, amount: number): Promi
432435
return { error: error.message || "Une erreur est survenue." };
433436
}
434437
}
438+
439+
export async function buyMiningRigForCompany(companyId: number, rigId: string): Promise<{ success?: string; error?: string }> {
440+
const session = await getSession();
441+
if (!session?.id) return { error: "Non autorisé." };
442+
443+
const rigToBuy = getRigById(rigId);
444+
if (!rigToBuy) return { error: "Matériel de minage non valide." };
445+
446+
try {
447+
const result = await db.transaction(async (tx) => {
448+
const member = await tx.query.companyMembers.findFirst({ where: and(eq(companyMembers.companyId, companyId), eq(companyMembers.userId, session.id)) });
449+
if (!member || member.role !== 'ceo') throw new Error("Seul le PDG peut acheter du matériel pour l'entreprise.");
450+
451+
const company = await tx.query.companies.findFirst({ where: eq(companies.id, companyId), columns: { cash: true } });
452+
if (!company) throw new Error("Entreprise non trouvée.");
453+
454+
const companyCash = parseFloat(company.cash);
455+
if (companyCash < rigToBuy.price) throw new Error("Trésorerie de l'entreprise insuffisante.");
456+
457+
const newCash = companyCash - rigToBuy.price;
458+
await tx.update(companies).set({ cash: newCash.toFixed(2) }).where(eq(companies.id, companyId));
459+
460+
const existingRig = await tx.query.companyMiningRigs.findFirst({
461+
where: and(eq(companyMiningRigs.companyId, companyId), eq(companyMiningRigs.rigId, rigId)),
462+
});
463+
464+
if (existingRig) {
465+
await tx.update(companyMiningRigs)
466+
.set({ quantity: existingRig.quantity + 1 })
467+
.where(eq(companyMiningRigs.id, existingRig.id));
468+
} else {
469+
await tx.insert(companyMiningRigs).values({
470+
companyId: companyId,
471+
rigId: rigId,
472+
quantity: 1,
473+
});
474+
}
475+
476+
return { success: `L'entreprise a acheté un ${rigToBuy.name}.` };
477+
});
478+
479+
revalidatePath(`/companies/${companyId}`);
480+
return result;
481+
482+
} catch (error: any) {
483+
console.error("Error buying mining rig for company:", error);
484+
return { error: error.message || "Une erreur est survenue lors de l'achat." };
485+
}
486+
}

src/lib/db/schema.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export const companiesRelations = relations(companies, ({ one, many }) => ({
200200
members: many(companyMembers),
201201
shares: many(companyShares),
202202
holdings: many(companyHoldings),
203+
miningRigs: many(companyMiningRigs),
203204
}));
204205

205206
export const companyMembers = pgTable('company_members', {
@@ -268,3 +269,22 @@ export const companyHoldingsRelations = relations(companyHoldings, ({ one }) =>
268269
references: [companies.id],
269270
}),
270271
}));
272+
273+
export const companyMiningRigs = pgTable('company_mining_rigs', {
274+
id: serial('id').primaryKey(),
275+
companyId: integer('company_id').notNull().references(() => companies.id, { onDelete: 'cascade' }),
276+
rigId: varchar('rig_id', { length: 50 }).notNull(),
277+
quantity: integer('quantity').notNull().default(1),
278+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
279+
}, (table) => {
280+
return {
281+
companyRigIdx: uniqueIndex('company_rig_idx').on(table.companyId, table.rigId),
282+
}
283+
});
284+
285+
export const companyMiningRigsRelations = relations(companyMiningRigs, ({ one }) => ({
286+
company: one(companies, {
287+
fields: [companyMiningRigs.companyId],
288+
references: [companies.id],
289+
}),
290+
}));

0 commit comments

Comments
 (0)