AndesOps-AI / frontend /components /MarketMonitor.tsx
Álvaro Valenzuela Valdes
Optimize mobile UI, fix layout overlaps and add tender questions link
bd7895c
"use client";
import { useEffect, useState } from "react";
import { fetchPurchaseOrders } from "../lib/api";
import { PurchaseOrder } from "../lib/types";
import BrandLoader from "./BrandLoader";
export default function MarketMonitor() {
const [ocs, setOcs] = useState<PurchaseOrder[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [filter, setFilter] = useState("todos");
const [page, setPage] = useState(1);
const itemsPerPage = 50;
useEffect(() => {
loadOcs();
setPage(1); // Reset page on filter change
}, [filter]);
async function loadOcs() {
setIsLoading(true);
setError(null);
try {
const data = await fetchPurchaseOrders(undefined, filter);
if (!data || data.length === 0) {
setError("No purchase orders found for today. Try again later or check your API connection.");
setOcs([]);
} else {
// Sort by code descending (usually higher codes are newer)
const sorted = [...data].sort((a, b) => b.code.localeCompare(a.code));
setOcs(sorted);
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : "Failed to load purchase orders. Check your backend connection.";
console.error("OC Load Error:", e);
setError(errorMsg);
setOcs([]);
} finally {
setIsLoading(false);
}
}
const formatCurrency = (amount: number | null, currency: string | null) => {
if (!amount || amount === 0) return <span className="text-slate-600 italic">Pending...</span>;
return new Intl.NumberFormat("es-CL", {
style: "currency",
currency: currency || "CLP",
maximumFractionDigits: 0
}).format(amount);
};
const paginatedOcs = ocs.slice((page - 1) * itemsPerPage, page * itemsPerPage);
const totalPages = Math.ceil(ocs.length / itemsPerPage);
return (
<div className="space-y-8 animate-in fade-in duration-700">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<p className="text-[10px] uppercase tracking-[0.4em] text-cyan/60 font-black mb-2">Real-Time Intelligence</p>
<h2 className="text-4xl font-black text-white tracking-tight">Market Monitor</h2>
<div className="flex items-center gap-3 mt-2">
<span className="flex h-2 w-2 rounded-full bg-green-500 animate-pulse" />
<p className="text-slate-400 text-sm">
Monitoring <span className="text-white font-bold">{ocs.length.toLocaleString()}</span> active orders from today.
</p>
</div>
</div>
<div className="flex bg-slate-900/50 p-1 rounded-2xl border border-white/5 backdrop-blur-xl">
{["todos", "aceptada", "enviadaproveedor"].map((f) => (
<button
key={f}
onClick={() => setFilter(f)}
className={`px-6 py-2.5 rounded-xl text-[10px] uppercase font-black tracking-widest transition-all ${
filter === f ? "bg-cyan text-slate-950 shadow-lg shadow-cyan/20" : "text-slate-500 hover:text-white"
}`}
>
{f === "todos" ? "Live Stream" : f}
</button>
))}
</div>
</div>
<div className="grid gap-6">
{isLoading ? (
<div className="py-20">
<BrandLoader />
</div>
) : error ? (
<div className="glass-card rounded-[2rem] p-8 border border-red-500/20 bg-red-500/5">
<div className="flex items-start gap-4">
<div className="text-2xl">⚠️</div>
<div className="flex-1">
<h3 className="text-white font-bold mb-2">Connection Error</h3>
<p className="text-slate-300 text-sm mb-4">{error}</p>
<div className="flex gap-3">
<button
onClick={loadOcs}
className="px-6 py-2 bg-cyan text-slate-950 font-bold rounded-lg hover:bg-cyan/90 transition-all"
>
🔄 Retry
</button>
<a
href="#"
className="px-6 py-2 bg-white/5 border border-white/10 text-white font-bold rounded-lg hover:bg-white/10 transition-all"
>
Troubleshoot
</a>
</div>
</div>
</div>
</div>
) : ocs.length > 0 ? (
<>
<div className="glass-card rounded-[2rem] overflow-hidden border border-white/5 shadow-2xl shadow-black/50">
<div className="overflow-x-auto custom-scrollbar max-h-[600px]">
<table className="w-full text-left text-xs border-collapse sticky-header">
<thead className="sticky top-0 z-10">
<tr className="bg-slate-900/95 backdrop-blur-md text-slate-500 uppercase font-black tracking-tighter border-b border-white/5">
<th className="px-4 sm:px-6 py-5">Order ID / Description</th>
<th className="px-6 py-5 hidden md:table-cell">Buyer</th>
<th className="px-6 py-5 hidden lg:table-cell">Vendor</th>
<th className="px-4 sm:px-6 py-5 text-right">Total</th>
</tr>
</thead>
<tbody className="divide-y divide-white/5">
{paginatedOcs.map((oc) => (
<tr key={oc.code} className="hover:bg-white/[0.03] transition-colors group">
<td className="px-4 sm:px-6 py-5 max-w-md">
<div className="flex items-center gap-2 mb-1">
<span className="text-cyan font-bold font-mono text-[9px] bg-cyan/5 px-2 py-0.5 rounded border border-cyan/10">
{oc.code}
</span>
</div>
<div className="text-white font-bold line-clamp-1 group-hover:line-clamp-none transition-all cursor-help text-xs sm:text-sm" title={oc.name}>
{oc.name || "Orden de Compra"}
</div>
<div className="md:hidden text-[10px] text-slate-500 mt-1 truncate max-w-[200px]">
{oc.buyer}
</div>
</td>
<td className="px-6 py-5 hidden md:table-cell">
<div className="text-slate-300 font-medium truncate max-w-[150px] text-[11px]">
{oc.buyer !== "Unknown" ? oc.buyer : <span className="opacity-30">...</span>}
</div>
</td>
<td className="px-6 py-5 hidden lg:table-cell">
<div className="text-sky-400 font-bold truncate max-w-[150px] text-[11px]">
{oc.provider !== "Unknown" ? oc.provider : <span className="opacity-30">...</span>}
</div>
</td>
<td className="px-4 sm:px-6 py-5 text-right">
<div className="text-white font-black text-xs sm:text-sm">
{formatCurrency(oc.total_amount, oc.currency)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Pagination Controls */}
<div className="flex items-center justify-between px-4">
<div className="text-[10px] text-slate-500 font-bold uppercase tracking-widest">
Showing {((page - 1) * itemsPerPage) + 1} to {Math.min(page * itemsPerPage, ocs.length)} of {ocs.length}
</div>
<div className="flex gap-2">
<button
disabled={page === 1}
onClick={() => setPage(p => p - 1)}
className="px-4 py-2 rounded-lg bg-white/5 border border-white/10 text-xs font-bold disabled:opacity-30 hover:bg-white/10"
>
Previous
</button>
<button
disabled={page === totalPages}
onClick={() => setPage(p => p + 1)}
className="px-4 py-2 rounded-lg bg-white/5 border border-white/10 text-xs font-bold disabled:opacity-30 hover:bg-white/10"
>
Next
</button>
</div>
</div>
</>
) : (
<div className="py-40 text-center glass-card rounded-[2rem] border border-white/5">
<div className="text-4xl mb-4">🛒</div>
<p className="text-slate-500 font-medium italic">No purchase orders detected in the last hour.</p>
</div>
)}
</div>
</div>
);
}