"use client"; import React, { useState, useEffect, useCallback, useRef } from "react"; import { Package, ShoppingBag, Info, ExternalLink, Loader2, AlertTriangle } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { getCookie } from "@/lib/api"; import axios from "axios"; import { useRouter } from "next/navigation"; import { cacheUtils } from "@/lib/api-client"; interface Order { _id: string; orderId: number; status: string; totalPrice: number; orderDate: string; products: Array<{ productId: string; quantity: number; pricePerUnit: number; totalItemPrice: number; }>; underpaid?: boolean; underpaymentAmount?: number; lastBalanceReceived?: number; cryptoTotal?: number; } interface BuyerOrderInfoProps { buyerId: string; chatId: string; } export default function BuyerOrderInfo({ buyerId, chatId }: BuyerOrderInfoProps) { const router = useRouter(); const [loading, setLoading] = useState(false); const [orders, setOrders] = useState([]); const [hasOrders, setHasOrders] = useState(null); const [isTooltipOpen, setIsTooltipOpen] = useState(false); const lastFetchedRef = useRef(0); const isFetchingRef = useRef(false); const tooltipDelayRef = useRef(null); const [refreshTrigger, setRefreshTrigger] = useState(0); // Add order refresh subscription useEffect(() => { const unsubscribe = cacheUtils.onOrderRefresh(() => { console.log("Order refresh triggered in BuyerOrderInfo"); setRefreshTrigger(prev => prev + 1); }); return unsubscribe; }, []); // Fetch data without unnecessary dependencies to reduce render cycles const fetchBuyerOrders = useCallback(async (force = false) => { // Prevent multiple simultaneous fetches if (isFetchingRef.current) return; // Don't fetch if we already know there are no orders if (hasOrders === false && !force) return; // Don't fetch if we already have orders and data was fetched less than 10 seconds ago const now = Date.now(); if (!force && !refreshTrigger && orders.length > 0 && now - lastFetchedRef.current < 10000) return; // Only continue if we have a chatId if (!chatId) return; isFetchingRef.current = true; setLoading(true); try { const authToken = getCookie("Authorization"); if (!authToken) { console.error("No auth token found for buyer orders"); setHasOrders(false); return; } const authAxios = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL, headers: { Authorization: `Bearer ${authToken}` } }); // Use the new endpoint that works with sub-users const response = await authAxios.get(`/chats/${chatId}/orders?limit=10`); // Limit to fewer orders for faster response if (response.data && response.data.orders) { setOrders(response.data.orders); setHasOrders(response.data.orders.length > 0); } else { setHasOrders(false); } lastFetchedRef.current = Date.now(); } catch (error: any) { console.error("Error fetching buyer orders:", error); if (error.response?.status === 404) { console.log("No orders found for this buyer"); setOrders([]); setHasOrders(false); } else { console.error("API error:", error.response?.data || error.message); setHasOrders(null); } } finally { setLoading(false); isFetchingRef.current = false; } }, [chatId, refreshTrigger]); // Add refreshTrigger as dependency // Start fetching immediately when component mounts useEffect(() => { if (chatId) { // Immediately attempt to fetch in the background fetchBuyerOrders(); } return () => { // Clean up any pending timeouts if (tooltipDelayRef.current) { clearTimeout(tooltipDelayRef.current); } }; }, [chatId, fetchBuyerOrders]); const handleViewOrder = (orderId: string) => { router.push(`/dashboard/orders/${orderId}`); }; // Handle hover with immediate tooltip opening const handleButtonMouseEnter = () => { // Start fetching data, but don't wait for it to complete if (!isFetchingRef.current) { queueMicrotask(() => { fetchBuyerOrders(); }); } }; // Handle tooltip state change const handleTooltipOpenChange = (open: boolean) => { setIsTooltipOpen(open); if (open && !isFetchingRef.current) { queueMicrotask(() => { fetchBuyerOrders(); }); } }; // Format the price as currency const formatPrice = (price: number) => { return `£${price.toFixed(2)}`; }; // Helper function to check if order is underpaid (improved) const isOrderUnderpaid = (order: Order) => { return order.underpaid === true && order.underpaymentAmount && order.underpaymentAmount > 0 && order.status !== "paid" && order.status !== "completed" && order.status !== "shipped" && order.status !== "cancelled"; }; // Helper function to get underpaid percentage const getUnderpaidPercentage = (order: Order) => { if (!isOrderUnderpaid(order)) return 0; const received = order.lastBalanceReceived || 0; const required = order.cryptoTotal || 0; return required > 0 ? ((received / required) * 100) : 0; }; // If we know there are no orders, don't show the component at all if (hasOrders === false) { return null; } // Precompute product count for button display (only if we have orders) const productCount = orders.length > 0 ? orders.reduce((total, order) => { return total + order.products.reduce((sum, product) => sum + product.quantity, 0); }, 0) : 0; return ( {loading ? (
Loading orders...
) : orders.length === 0 ? (
No orders found
) : ( <>

Customer Orders

{orders.length} {orders.length === 1 ? 'order' : 'orders'} ÔÇó Total: {formatPrice( orders.reduce((sum, order) => sum + order.totalPrice, 0) )}

{orders.map((order) => (
handleViewOrder(order._id)} >
Order #{order.orderId} {isOrderUnderpaid(order) && ( )}
{order.status.toUpperCase()} {isOrderUnderpaid(order) && ( {getUnderpaidPercentage(order).toFixed(0)}% PAID )}
{order.products.length} {order.products.length === 1 ? 'product' : 'products'} {formatPrice(order.totalPrice)} {isOrderUnderpaid(order) && ( <> Underpaid )}
{new Date(order.orderDate).toLocaleDateString()}
))}
)}
); }