317 lines
9.3 KiB
JavaScript
317 lines
9.3 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
|
import logo from './logo.png';
|
|
import { Card, Title, Text, AreaChart } from "@tremor/react";
|
|
import {
|
|
ShoppingCart,
|
|
Receipt,
|
|
PackageSearch,
|
|
Truck,
|
|
CreditCard,
|
|
Boxes,
|
|
TrendingUp,
|
|
Coins,
|
|
LineChart,
|
|
DollarSign,
|
|
Newspaper,
|
|
} from "lucide-react";
|
|
|
|
function SplashScreen() {
|
|
return (
|
|
<div className="fixed inset-0 flex items-center justify-center bg-white dark:bg-gray-900 z-50">
|
|
<div className="animate-splash opacity-0 scale-75">
|
|
<img src={logo} alt="Logo" className="h-32 object-contain" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* --------------------------
|
|
Communication Tryton
|
|
-------------------------- */
|
|
|
|
function openInTryton(model, res_id, view_mode = "tree", domain) {
|
|
window.parent.postMessage(
|
|
{
|
|
type: "open_tab",
|
|
payload: { model, res_id, view_mode, domain },
|
|
},
|
|
"*"
|
|
);
|
|
}
|
|
|
|
/* --------------------------
|
|
Données
|
|
-------------------------- */
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
const defaultSaleId = params.get("sale_id") || 249;
|
|
const defaultPnlAmount = params.get("pnl_amount");
|
|
const exposure = params.get("exposure");
|
|
const topay = params.get("topay");
|
|
const toreceive = params.get("toreceive");
|
|
|
|
const data = [
|
|
{ date: "2025-01-01", value: 12 },
|
|
{ date: "2025-01-02", value: 14 },
|
|
{ date: "2025-01-03", value: 10 },
|
|
{ date: "2025-01-04", value: 18 },
|
|
{ date: "2025-01-05", value: 22 },
|
|
];
|
|
|
|
const statusData = [
|
|
{ status: "Draft", count: 10, color: "bg-teal-500" },
|
|
{ status: "Confirmed", count: 7, color: "bg-cyan-500" },
|
|
{ status: "Shipped", count: 4, color: "bg-blue-500" },
|
|
];
|
|
|
|
const kpis = [
|
|
{
|
|
title: "PNL",
|
|
value: defaultPnlAmount,
|
|
trend: "+12% vs last month",
|
|
icon: TrendingUp,
|
|
action: () => openInTryton("pnl.bi", [1], "form")
|
|
},
|
|
{
|
|
title: "Exposure",
|
|
value: exposure,
|
|
trend: "+8% this month",
|
|
icon: Coins,
|
|
action: () => openInTryton("open.position.report", undefined, "tree")
|
|
},
|
|
{
|
|
title: "Amount to pay",
|
|
value: topay,
|
|
trend: "+5% this month",
|
|
icon: LineChart,
|
|
action: () => openInTryton("account.invoice", undefined, "tree")
|
|
},
|
|
{
|
|
title: "Amount to receive",
|
|
value: toreceive,
|
|
trend: "+3% this month",
|
|
icon: DollarSign,
|
|
action: () => openInTryton("account.invoice", undefined, "tree")
|
|
},
|
|
];
|
|
|
|
const news = [
|
|
{
|
|
type: "Forex",
|
|
label: "EUR/USD: 1.1400",
|
|
trend: "+0.88%",
|
|
color: "#1E3A8A", // bleu foncé
|
|
date: "30-11-2025",
|
|
icon: TrendingUp, // icône tendance boursière
|
|
},
|
|
{
|
|
type: "Logistic",
|
|
label: "INTHIRA NAREE loaded",
|
|
date: "08-10-2025",
|
|
color: "#1E3A8A",
|
|
icon: Newspaper,
|
|
},
|
|
];
|
|
|
|
const cards = [
|
|
{
|
|
id: "purchases_not_confirmed",
|
|
title: "Purchases Not Confirmed",
|
|
value: 12,
|
|
trend: "+8% this week",
|
|
icon: ShoppingCart,
|
|
},
|
|
{
|
|
id: "sales_pending",
|
|
title: "Sales Pending",
|
|
value: 7,
|
|
trend: "+3% this week",
|
|
icon: Receipt,
|
|
},
|
|
{
|
|
id: "invoices_unpaid",
|
|
title: "Invoices Unpaid",
|
|
value: 15,
|
|
trend: "-2% this week",
|
|
icon: CreditCard,
|
|
},
|
|
{
|
|
id: "payments_to_validate",
|
|
title: "Payments To Validate",
|
|
value: 4,
|
|
trend: "+12% this week",
|
|
icon: PackageSearch,
|
|
},
|
|
{
|
|
id: "lots_to_produce",
|
|
title: "Lots",
|
|
value: 9,
|
|
trend: "+5% this week",
|
|
icon: Boxes,
|
|
},
|
|
{
|
|
id: "shipments_pending",
|
|
title: "Shipments Pending",
|
|
value: 6,
|
|
trend: "-1% this week",
|
|
icon: Truck,
|
|
},
|
|
];
|
|
|
|
/* --------------------------
|
|
Dashboard Component
|
|
-------------------------- */
|
|
|
|
export default function App() {
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
// durée du splash
|
|
const timer = setTimeout(() => setLoading(false), 1500);
|
|
return () => clearTimeout(timer);
|
|
}, []);
|
|
|
|
if (loading) return <SplashScreen />;
|
|
|
|
const saleId = defaultSaleId;
|
|
const total = statusData.reduce((sum, item) => sum + item.count, 0);
|
|
|
|
return (
|
|
<div className="flex min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
|
|
<main className="flex-1">
|
|
|
|
{/* KPIs en tête */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
{kpis.map((kpi) => {
|
|
const Icon = kpi.icon;
|
|
return (
|
|
<Card
|
|
key={kpi.title}
|
|
onClick={kpi.action}
|
|
className="p-4 flex flex-col gap-2 shadow-md rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700"
|
|
>
|
|
<div className="flex justify-between items-center">
|
|
<Title className="text-sm">{kpi.title}</Title>
|
|
<Icon className="w-6 h-6" />
|
|
</div>
|
|
<Text className="text-2xl font-bold">{kpi.value}</Text>
|
|
<Text className="text-xs text-gray-500">{kpi.trend}</Text>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
|
|
{/* Card Last News (taille standard card) */}
|
|
<Card className="p-4 rounded-xl border border-gray-200 dark:border-gray-700 shadow-md bg-white dark:bg-gray-800 hover:shadow-lg transition">
|
|
<Title className="text-base mb-4">Last News</Title>
|
|
|
|
<div className="space-y-3 text-sm">
|
|
{news.map((n, idx) => {
|
|
const Icon = n.icon;
|
|
return (
|
|
<div
|
|
key={idx}
|
|
className="flex justify-between items-center p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer"
|
|
>
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<Icon className="w-4 h-4" />
|
|
<span className="font-semibold">{n.type}</span>
|
|
</div>
|
|
<div className="ml-6">
|
|
<span style={{ color: n.color }}>{n.label}</span>
|
|
{n.trend && (
|
|
<span className="ml-2 text-green-600">{n.trend}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<span className="text-gray-500 text-xs">{n.date}</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Status Distribution */}
|
|
<Card className="p-4 rounded-xl border border-gray-200 dark:border-gray-700 shadow-md bg-white dark:bg-gray-800 hover:shadow-lg transition">
|
|
<Title className="text-base mb-4">Status Distribution</Title>
|
|
|
|
<Text className="text-3xl font-bold mb-1">{total}</Text>
|
|
<Text className="text-xs text-gray-500 mb-4">Across all statuses</Text>
|
|
|
|
<div className="w-full h-1 bg-gray-200 dark:bg-gray-600 mb-4"></div>
|
|
|
|
<div className="flex w-full h-3 rounded overflow-hidden mb-4 bg-gray-200 dark:bg-gray-600">
|
|
{statusData.map((item, i) => (
|
|
<div
|
|
key={i}
|
|
className={`${item.color} h-3`}
|
|
style={{
|
|
width: `${(item.count / total) * 100}%`,
|
|
marginRight: i < statusData.length - 1 ? "2px" : "0",
|
|
}}
|
|
></div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="space-y-2 text-xs">
|
|
{statusData.map((item) => (
|
|
<div
|
|
key={item.status}
|
|
className="flex items-center gap-2 cursor-pointer p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
>
|
|
<span className={`w-3 h-3 rounded ${item.color}`}></span>
|
|
<span className="flex-1">{item.status}</span>
|
|
<span className="text-gray-500">
|
|
({item.count} / {((item.count / total) * 100).toFixed(1)}%)
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Cards KPI */}
|
|
{cards.map((card) => {
|
|
const Icon = card.icon;
|
|
return (
|
|
<Card
|
|
key={card.id}
|
|
className="p-4 rounded-xl border border-gray-200 dark:border-gray-700 shadow-md bg-white dark:bg-gray-800 hover:shadow-lg transition"
|
|
>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<Title className="text-base">{card.title}</Title>
|
|
<Icon className="w-6 h-6" />
|
|
</div>
|
|
|
|
<Text className="text-3xl font-bold mb-1">{card.value}</Text>
|
|
<Text className="text-xs text-gray-500 mb-4">{card.trend}</Text>
|
|
|
|
<div className="w-full h-1 bg-gray-200 dark:bg-gray-600 mb-4"></div>
|
|
|
|
<AreaChart
|
|
data={data}
|
|
index="date"
|
|
categories={["value"]}
|
|
colors={["teal-300"]}
|
|
className="h-16"
|
|
showXAxis={false}
|
|
showYAxis={false}
|
|
showGridLines={false}
|
|
showLegend={false}
|
|
curve="monotone"
|
|
/>
|
|
|
|
<button className="mt-4 text-xs text-teal-600 underline">
|
|
View details
|
|
</button>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|