import React, { useState, useEffect } from "react"; import logo from './logo.png'; import { Card, Title, Text, AreaChart, BarChart } from "@tremor/react"; import { CreditCard, Wallet, Wallet2, HandCoins, Boxes, Box, Truck, Gauge, TrendingUp, PiggyBank, ShoppingCart, ShoppingBag, BadgeCheck, DollarSign, Ship, Package, ArrowDownCircle, ArrowUpCircle, LineChart, BarChart3, FileText, Receipt, Newspaper, ScrollText, } from "lucide-react"; function SplashScreen() { return (
Logo
); } /* -------------------------- Communication Tryton -------------------------- */ function openInTryton(model, res_id, view_mode = ['tree'], domain, message = "open_tab", context = {}) { window.parent.postMessage( { type: message, payload: { model, res_id, view_mode, domain, context }, }, "*" ); } /* -------------------------- 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 draft_p = params.get("draft_p"); const val_p = params.get("val_p"); const conf_p = params.get("conf_p"); const draft_s = params.get("draft_s"); const val_s = params.get("val_s"); const conf_s = params.get("conf_s"); const shipment_d = params.get("shipment_d"); const shipment_s = params.get("shipment_s"); const shipment_r = params.get("shipment_r"); const lot_m = params.get("lot_m"); const lot_a = params.get("lot_a"); const lot_al = Number(lot_a) + Number(lot_m) const inv_p = params.get("inv_p"); const inv_p_p = params.get("inv_p_p"); const inv_p_np = params.get("inv_p_np"); const inv_s = params.get("inv_s"); const inv_s_p = params.get("inv_s_p"); const inv_s_np = params.get("inv_s_np"); const pay_posted = params.get("pay_posted"); const pay_val = params.get("pay_val"); const eurusd = params.getAll("eurusd").map(Number); const eurusd_date = params.getAll("eurusd_date"); const data = eurusd .map((value, i) => ({ date: eurusd_date[i], value })) .reverse(); const purchaseData = [ { status: "Draft", count: draft_p, color: "bg-teal-500", onClick: () => openInTryton("purchase.purchase", undefined, ['tree', 'form'],[['state', '=', 'draft']])}, { status: "Validated", count: val_p, color: "bg-gray-400", onClick: () => openInTryton("purchase.purchase", undefined, ['tree', 'form'],[['state', '=', 'quotation']]) }, { status: "Confirmed", count: conf_p, color: "bg-sky-600", onClick: () => openInTryton("purchase.purchase", undefined, ['tree', 'form'],[['state', '=', 'confirmed']]) }, ]; const saleData = [ { status: "Draft", count: draft_s, color: "bg-teal-500", onClick: () => openInTryton("sale.sale", undefined, ['tree', 'form'],[['state', '=', 'draft']])}, { status: "Validated", count: val_s, color: "bg-gray-400", onClick: () => openInTryton("sale.sale", undefined, ['tree', 'form'],[['state', '=', 'quotation']]) }, { status: "Confirmed", count: conf_s, color: "bg-sky-600", onClick: () => openInTryton("sale.sale", undefined, ['tree', 'form'],[['state', '=', 'confirmed']]) }, ]; const shipmentData = [ { status: "Draft", count: shipment_d, color: "bg-teal-500", onClick: () => openInTryton("stock.shipment.in", undefined, ['tree', 'form'],[['state', '=', 'draft']])}, { status: "Started", count: shipment_s, color: "bg-gray-400", onClick: () => openInTryton("stock.shipment.in", undefined, ['tree', 'form'],[['state', '=', 'started']]) }, { status: "Received", count: shipment_r, color: "bg-sky-600", onClick: () => openInTryton("stock.shipment.in", undefined, ['tree', 'form'],[['state', '=', 'received']]) }, ]; const lotData = [ { status: "Matched", count: lot_m, color: "bg-teal-500", onClick: () => openInTryton("lot.report", undefined, ['tree', 'form'],[["r_lot_matched", ">", 0]],'exec_window',{ purchase: null, sale: null, shipment: null, type: 'matched', state: 'all', wh: 'all', group: 'by_physic', origin: 'physic' })}, { status: "Available", count: lot_a, color: "bg-gray-400", onClick: () => openInTryton("lot.report", undefined, ['tree', 'form'],[["r_lot_matched", ">", 0]],'exec_window',{ purchase: null, sale: null, shipment: null, type: 'not matched', state: 'all', wh: 'all', group: 'by_physic', origin: 'physic' })}, { status: "All", count: lot_al, color: "bg-sky-600", onClick: () => openInTryton("lot.report", undefined, ['tree', 'form'],[["r_lot_matched", ">", 0]],'exec_window',{ purchase: null, sale: null, shipment: null, type: 'all', state: 'all', wh: 'all', group: 'by_physic', origin: 'physic' })}, ]; const kpis = [ { title: "PNL ($)", value: defaultPnlAmount, trend: "+12% vs last month", trendValue: 4.4, icon: BarChart3, action: () => openInTryton("pnl.bi", [1], ['form']) }, { title: "Exposure (Mt)", value: exposure, trend: "-3% this month", trendValue: -3, icon: Gauge, action: () => openInTryton("open.position.report", undefined, ['tree']) }, { title: "Amount to pay ($)", value: topay, trend: "+5% this month", trendValue: 5, icon: DollarSign, action: () => openInTryton("account.invoice", undefined, ['tree','form'],[['type', '=', 'in']]) }, { title: "Amount to receive ($)", value: toreceive, trend: "-1% this month", trendValue: -1, icon: HandCoins, action: () => openInTryton("account.invoice", undefined, ['tree','form'],[['type', '=', 'out']]) }, ]; const latestValue = data.length > 4 ? data[4].value : null; const prevValue = data.length > 3 ? data[3].value : null; const latestDate = data.length > 4 ? data[4].date : null; let trendValue = null; if (latestValue && prevValue) { trendValue = ((latestValue - prevValue) / prevValue) * 100; } const news = [ { type: "Forex", pair: "EUR/USD", value: latestValue, // nombre date: latestDate, icon: TrendingUp, trendValue, }, // { // type: "Logistic", // label: "INTHIRA NAREE loaded", // date: "08-10-2025", // color: "#1E3A8A", // icon: Newspaper, // }, ]; const cards = [ // { // id: "sales_pending", // title: "Sales Pending", // value: 7, // trend: "+3% this week", // icon: Receipt, // action: () => openInTryton("sale.sale", undefined, "tree") // }, { id: "purchase_invoices", title: "Purchase Invoices", value: inv_p, trend: "All invoices", icon: Receipt, amountInvoiced: 100523, amount30Days: 456147, amount60Days: 847512, action: () => openInTryton("account.invoice", undefined, ['tree','form'],[['type', '=', 'in']]) }, { id: "sale_invoices", title: "Sale Invoices", value: inv_s, trend: "All invoices", icon: FileText, amountInvoiced: 100523, amount30Days: 456147, amount60Days: 847512, action: () => openInTryton("account.invoice", undefined, ['tree','form'],[['type', '=', 'out']]) }, // { // id: "payments_to_validate", // title: "Payments To Validate", // value: move_cash, // trend: "All payments", // icon: HandCoins, // amountInvoiced: 100523, // amount30Days: 456147, // amount60Days: 847512, // action: () => openInTryton("account.move", undefined, ['tree','form'],[['journal', '=', 3]]) // }, // { // id: "prepayments", // title: "Prepayments To Validate", // value: 9, // trend: "+5% this week", // icon: PiggyBank, // action: () => openInTryton("account.move", undefined, "tree") // }, ]; /* -------------------------- 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 ; const saleId = defaultSaleId; const total_p = purchaseData.reduce((sum, item) => sum + Number(item.count), 0); const total_s = saleData.reduce((sum, item) => sum + Number(item.count), 0); const total_sh = shipmentData.reduce((sum, item) => sum + Number(item.count), 0); return (
{/* KPIs en tête */}
{kpis.map((kpi) => { const Icon = kpi.icon; const isPositive = kpi.trendValue >= 0; return ( {kpi.title} {kpi.value}
{isPositive ? "+" : ""} {kpi.trendValue}% vs last month
); })}
{/* Purchase */}
Purchases
{total_p} Across all statuses
{purchaseData.map((item, i) => (
))}
{purchaseData.map((item) => (
{item.status} ({item.count} / {((item.count / total_p) * 100).toFixed(1)}%)
))}
{/* Sale */}
Sales
{total_s} Across all statuses
{saleData.map((item, i) => (
))}
{saleData.map((item) => (
{item.status} ({item.count} / {((item.count / total_s) * 100).toFixed(1)}%)
))}
{/* Shipment */}
Shipments
{total_sh} Across all statuses
{shipmentData.map((item, i) => (
))}
{shipmentData.map((item) => (
{item.status} ({item.count} / {((item.count / total_sh) * 100).toFixed(1)}%)
))}
{/* Lot */}
Lots
{lot_al} Across all statuses
{lotData.map((item, i) => (
))}
{lotData.map((item) => (
{item.status} ({item.count} / {((item.count / lot_al) * 100).toFixed(1)}%)
))}
{/* Analytics Card (Rows written style) */} {/*

Rows written

-3.9%
83,197 from 86,580
16/04/2024 16/05/2024
*/} {/* Card Last News (taille standard card) */}
Forex
{news.map((n, idx) => { return (
openInTryton("currency.currency", 2, ["form"])} >
{/* LIGNE PRINCIPALE */}
{/* PAIRE */} {n.pair} {/* VALEUR */} {n.value?.toFixed(4)} {/* DATE */} {n.date}
{/* BADGE CENTRÉ */} {n.trendValue !== null && (
= 0 ? "bg-green-50 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-400 dark:border-green-800" : "bg-red-50 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800" } `} > {n.trendValue >= 0 ? "+" : ""} {n.trendValue.toFixed(2)}%
)}
); })}
{/* SEPARATOR */}
d.value)) * 0.99} maxValue={Math.max(...data.map(d => d.value)) * 1.01} />
{/* {cards.map((card) => { const Icon = card.icon; const bar_data = [ { label: "Invoiced", invoiced: card.amountInvoiced }, { label: "30 days", pay30: card.amount30Days }, { label: "60 days", pay60: card.amount60Days }, ]; return (
{card.title}
{card.value} {card.trend}
); })} */}
Purchase Invoices
Sale Invoices
Payments
{/* TO VALIDATE */} {/* POSTED */}
); }