import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { Loader2, Package, GitBranch, MessageSquare, Plus, User, CheckCircle, XCircle, ArrowRight, CornerDownRight, LogIn, Upload, List, Trash2, LogOut, Divide, AlertTriangle, Edit, UserX, UserCheck, LayoutDashboard, CornerLeftUp, Download } from 'lucide-react'; // --- GLOBAL VARIABLES & SUPABASE SETUP (Mandatory) --- // NOTE: These variables are provided by the canvas environment. const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-system-distributor-app'; const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}'); const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : 'c2d09bc0-1679-4797-80c0-2ae12e5d3bc7';  // Supabase configuration const SUPABASE_URL = 'https://yfsmflvrgqwongywmagc.supabase.co'; const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inlmc21mbHZyZ3F3b25neXdtYWdjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA1OTM4OTcsImV4cCI6MjA3NjE2OTg5N30.8lmYd5Zvzq7iPEUL5u1Ai0gjhZzVcgz_PpD3ofP-3C8';  // Global variable to hold the initialized Supabase client let supabaseGlobal = null; // Global flag for XLSX loading let isXLSXReady = false; // --- CONSTANTS & CORE UTILITIES --- const STATUSES = { PENDING: 'Pending', OK: 'Collected Done', PARTIAL: 'Partial Collected', NOT_FOUND: 'Not Found', REDIRECTED: 'Redirected', }; const COLLECTED_QTY_KEY = 'collectedQuantity'; // Key for the collected quantity in item object const formatTime = (timestamp) => { if (!timestamp) return '...'; const date = typeof timestamp === 'string' ? new Date(timestamp) : (timestamp.toDate ? timestamp.toDate() : new Date(timestamp)); return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); }; // Loading component const LoadingSpinner = () => (
Loading Data...
); // Supabase hook for fetching collection data - MODIFIED FOR SMOOTH UPSERT UPDATES const useSupabaseCollection = (supabaseClient, tableName, dependencies = []) => { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); // New internal function to handle local state update smoothly const handleLocalUpdate = useCallback((newRecord, type) => { setData(prevData => { const index = prevData.findIndex(item => item.id === newRecord.id); if (type === 'INSERT') { if (index === -1) { // Add new record to the start (assuming descending order by time) return [newRecord, ...prevData]; } // If the record exists (e.g., from a quick local update), update it return prevData.map(item => item.id === newRecord.id ? newRecord : item); } else if (type === 'UPDATE') { if (index !== -1) { // Update existing record in place return prevData.map(item => item.id === newRecord.id ? newRecord : item); } } else if (type === 'DELETE') { return prevData.filter(item => item.id !== newRecord.id); } return prevData; }); }, []); const fetchData = useCallback(async () => { if (!supabaseClient) return; setLoading(true); const { data: initialData, error } = await supabaseClient .from(tableName) .select('*') .order('createdAt', { ascending: false }); if (error) { console.error(`Error fetching initial ${tableName}:`, error); } else { setData(initialData || []); } setLoading(false); }, [supabaseClient, tableName]); useEffect(() => { fetchData(); const handleRealtimePayload = (payload) => { console.log(`DEBUG: Real-time ${payload.eventType} detected in ${tableName}.`); // Use the smooth local update handler handleLocalUpdate(payload.new || payload.old, payload.eventType); }; // Setup real-time listener for ALL changes const channel = supabaseClient?.channel(`public:${tableName}-changes`).on( 'postgres_changes', { event: '*', schema: 'public', table: tableName }, handleRealtimePayload ).subscribe((status) => { if (status === 'SUBSCRIBED') { console.log(`DEBUG: Subscribed to Real-time channel for ${tableName}. RLS and replication must be enabled.`); } else if (status === 'CHANNEL_ERROR') { console.error(`DEBUG: Error subscribing to Real-time channel for ${tableName}. Attempting full refetch.`); fetchData(); // Fallback to full fetch if channel errors } }); return () => { if (channel) { console.log(`DEBUG: Unsubscribing from Real-time channel for ${tableName}.`); channel.unsubscribe(); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [supabaseClient, tableName, handleLocalUpdate, ...dependencies]); // Return the local update function for external (manual update) use return { data, loading, refetch: fetchData, handleLocalUpdate }; }; // Supabase hook for fetching chat messages (Uses standard real-time insert logic which is fast) const useSupabaseChat = (supabaseClient, chatId) => { const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(true); const fetchData = useCallback(async () => { if (!supabaseClient || !chatId) return; setLoading(true); const { data: initialData, error } = await supabaseClient .from('chats') .select('messages, createdAt') .eq('chatId', chatId) .order('createdAt', { ascending: true }); if (error) { console.error(`Error fetching chat ${chatId}:`, error); setMessages([]); } else { setMessages(initialData || []); } setLoading(false); }, [supabaseClient, chatId]); useEffect(() => { fetchData(); // Setup real-time listener for this specific chat thread const channel = supabaseClient?.channel(`chat_${chatId}`).on( 'postgres_changes', // Listen specifically for INSERTS on the 'chats' table where chatId matches { event: 'INSERT', schema: 'public', table: 'chats', filter: `chatId=eq.${chatId}` }, (payload) => { // Manually append the new message object for instant UI update setMessages(prev => [...prev, { messages: payload.new.messages, createdAt: payload.new.createdAt }]); } ).subscribe(); return () => { if (channel) channel.unsubscribe(); }; }, [supabaseClient, chatId, fetchData]); return { messages, loading, setMessages }; // Return setMessages for local update }; // --- AUTHENTICATION COMPONENT (Minimal) --- const LoginModal = ({ onLoginSuccess, isSupabaseReady }) => { const [name, setName] = useState(''); const [status, setStatus] = useState(null); // null, 'loading', 'error' const handleLogin = async (e) => { e.preventDefault(); const trimmedName = name.trim(); if (!trimmedName || typeof supabaseGlobal === 'undefined') return; setStatus('loading'); try { const { data: userData, error: fetchError } = await supabaseGlobal .from('users') .select('*') .eq('name', trimmedName) .single(); let profile; if (fetchError && fetchError.code === 'PGRST116') { setStatus('error'); return; } else if (fetchError) { throw fetchError; } else { profile = userData; } if (profile.role !== 'admin') { setStatus('error'); return; } onLoginSuccess({ userId: profile.id, userName: profile.name, role: profile.role, branchId: profile.branchId, }); setStatus(null); } catch (error) { console.error("Login Error:", error); setStatus('error'); } }; if (!isSupabaseReady) { return (

Connecting to Supabase...

); } return (

Order Management Login

Enter your **Admin Username** to access the panel.

setName(e.target.value)} placeholder="Admin Username (e.g., Elwakeel Admin)" className="w-full p-3 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" required disabled={status === 'loading'} />
{status === 'error' && (

Login failed. Invalid username or user is not an admin.

)}

**Admin Test Username: Elwakeel Admin**

); }; // --- CORE ORDER MANAGEMENT COMPONENTS --- const OrderChatModal = ({ order, item, onClose, currentUser }) => { const { messages: chatMessages, loading: loadingChat, setMessages } = useSupabaseChat(supabaseGlobal, item.chatId); const [newMessage, setNewMessage] = useState(''); const [sendingStatus, setSendingStatus] = useState(null); // 'sending', 'error' const messagesEndRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(scrollToBottom, [chatMessages]); const getSenderInfo = (messageText) => { const text = String(messageText); if (text.startsWith('Branch:')) { return { name: 'Branch', isSelf: false, color: 'text-gray-800', bgColor: 'bg-gray-200' }; } return { name: currentUser.userName || 'Admin', isSelf: true, color: 'text-white', bgColor: 'bg-indigo-500' }; } const handleSend = async (e) => { e.preventDefault(); const text = newMessage.trim(); if (text === '' || typeof supabaseGlobal === 'undefined' || sendingStatus === 'sending') return; setSendingStatus('sending'); try { const messagePayload = { chatId: String(item.chatId), messages: `Admin: ${text}`, orderId: String(order.id), itemId: String(item.itemId), createdAt: new Date().toISOString(), }; const { error } = await supabaseGlobal.from('chats').insert([messagePayload]); if (error) throw error; setMessages(prev => [...prev, { messages: messagePayload.messages, createdAt: messagePayload.createdAt }]); setNewMessage(''); setSendingStatus(null); } catch (error) { console.error("Error sending message:", error); console.error("DEBUG: Failed payload:", { chatId: String(item.chatId), orderId: String(order.id), itemId: String(item.itemId) }); setSendingStatus('error'); setTimeout(() => setSendingStatus(null), 2000); } }; return (

Chat: {item.itemId} (Order ID: {String(order.id).substring(0, 8)}...)

{/* FINAL WARNING/INSTRUCTION FOR THE USER */}
**IMPORTANT WARNING:** If "duplicate key value" error appears on sending, you must **REMOVE the Unique Constraint** on the `chatId` column in the Supabase `chats` table.
{/* Messages Area */}
{loadingChat ? ( ) : chatMessages.length === 0 ? (
No previous messages. Start the conversation.
) : ( chatMessages.map((msg, index) => { const senderInfo = getSenderInfo(msg.messages); const displayedText = String(msg.messages).replace(/^(Admin|Branch): /i, ''); return (

{senderInfo.name}

{displayedText}

{formatTime(msg.createdAt)}
); }) )}
{/* Input Area */}
setNewMessage(e.target.value)} placeholder="Type your message..." className="flex-1 p-2 border rounded-lg focus:ring-indigo-500 focus:border-indigo-500" required disabled={sendingStatus === 'sending'} />
{sendingStatus === 'error' &&

Error occurred during sending. **Please check column types in Chats table**.

}
); }; // New sub-component for handling collected quantity input const QuantityInputModal = ({ item, maxQty, onConfirm, onCancel }) => { const [collected, setCollected] = useState(item[COLLECTED_QTY_KEY] || maxQty); const handleConfirm = () => { const qty = parseInt(collected, 10); if (qty >= 0 && qty <= maxQty) { onConfirm(qty); } }; return (

Enter Collected Quantity

Item: {item.itemId} (Requested: {maxQty})

setCollected(e.target.value)} min="0" max={maxQty} placeholder="Collected Quantity" className="w-full p-3 border rounded-lg focus:ring-indigo-500 focus:border-indigo-500" required />
); }; const OrderDetailsModal = ({ order, branches, onClose, onUpdateItemStatus, currentUser, onRefetchOrder }) => { const branch = branches.find(b => b.id === order.branchId); const [status, setStatus] = useState(null); // 'loading', 'success', 'error' const [chatTarget, setChatTarget] = useState(null); // { order, item } const [qtyModalTarget, setQtyModalTarget] = useState(null); // { itemIndex, newStatus, maxQty } const openQtyModal = useCallback((itemIndex, newStatus, maxQty) => { setQtyModalTarget({ itemIndex, newStatus, maxQty }); }, []); const handleQtyConfirm = useCallback((collectedQty) => { const { itemIndex, newStatus, maxQty } = qtyModalTarget; setQtyModalTarget(null); // Close modal // Handle logic for status based on collected quantity let finalStatus = newStatus; if (collectedQty > 0 && collectedQty < maxQty) { finalStatus = STATUSES.PARTIAL; } else if (collectedQty === maxQty) { finalStatus = STATUSES.OK; } else if (collectedQty === 0) { finalStatus = STATUSES.NOT_FOUND; } updateSingleItemStatus(itemIndex, finalStatus, collectedQty); }, [qtyModalTarget]); const updateSingleItemStatus = async (itemIndex, newStatus, collectedQty = 0) => { const newItems = [...order.items]; // Augment item with collected quantity based on status const itemToUpdate = newItems[itemIndex]; if (newStatus === STATUSES.OK) { itemToUpdate.status = newStatus; itemToUpdate[COLLECTED_QTY_KEY] = itemToUpdate.quantity; } else if (newStatus === STATUSES.PENDING || newStatus === STATUSES.REDIRECTED || newStatus === STATUSES.NOT_FOUND) { itemToUpdate.status = newStatus; itemToUpdate[COLLECTED_QTY_KEY] = collectedQty; // Should be 0 for these, but uses passed value } else if (newStatus === STATUSES.PARTIAL) { itemToUpdate.status = newStatus; itemToUpdate[COLLECTED_QTY_KEY] = collectedQty; } // Override status logic if quantity was provided via modal if (collectedQty !== undefined) { // Logic to set final status based on collectedQty, ensuring consistency if (collectedQty === itemToUpdate.quantity) itemToUpdate.status = STATUSES.OK; else if (collectedQty > 0) itemToUpdate.status = STATUSES.PARTIAL; else if (collectedQty === 0) itemToUpdate.status = STATUSES.NOT_FOUND; } setStatus('loading'); try { // Step 1: Prepare the update object and send to DB const allOk = newItems.every(item => item.status === STATUSES.OK); const hasMixed = newItems.some(item => item.status !== STATUSES.OK && item.status !== STATUSES.PENDING); // Allow editing status at any time, but use item statuses to calculate overall status const orderStatus = allOk ? 'Closed' : hasMixed ? 'Partial' : 'Open'; const updatePayload = { items: newItems, status: orderStatus, // Do not update order.status here, allow the user to manage it via the select menu }; const { data: updatedOrders, error } = await supabaseGlobal.from('orders').update(updatePayload).eq('id', order.id).select().single(); if (error) throw error; // Step 2 (Defensive Update): Manually update the local state in the main component onUpdateItemStatus(order.id, updatedOrders); setStatus('success'); setTimeout(() => setStatus(null), 1000); } catch (error) { console.error("Update Item Status Error:", error); setStatus('error'); } }; // Handler for overall order status update (top of modal) const handleOverallStatusUpdate = async (newStatus) => { setStatus('loading'); try { const { data: updatedOrders, error } = await supabaseGlobal.from('orders').update({ status: newStatus }).eq('id', order.id).select().single(); if (error) throw error; onUpdateItemStatus(order.id, updatedOrders); setStatus('success'); setTimeout(() => setStatus(null), 1000); } catch (error) { console.error("Update Overall Status Error:", error); setStatus('error'); } } return (

Order Details (ID: {String(order.id).substring(0, 8)}...)

{/* Order Status Edit (Requested Feature) */}

Branch: {branch?.name || 'N/A'} | Created Date: {new Date(order.createdAt).toLocaleDateString()}

Overall Status:
{order.items.map((item, index) => { const maxQty = item.quantity; const collected = item[COLLECTED_QTY_KEY] || 0; const remaining = maxQty - collected; return (
{item.itemId} Req: {maxQty} | Collected: {collected} | Rem: {remaining}
{item.description}
{/* Status Update Dropdown */} {/* Chat Button */}
); })}
{status === 'success' &&

Item status updated successfully!

} {status === 'error' &&

Error updating item status.

}
{qtyModalTarget && ( setQtyModalTarget(null)} /> )}
); }; // Component for Multi-Select Dropdown const MultiSelectDropdown = ({ options, selectedValues, onSelectChange, label }) => { const [isOpen, setIsOpen] = useState(false); const wrapperRef = useRef(null); const handleToggle = (value) => { onSelectChange(prev => { if (prev.includes(value)) { return prev.filter(v => v !== value); } else { return [...prev, value]; } }); }; // Close dropdown on outside click useEffect(() => { function handleClickOutside(event) { if (wrapperRef.current && !wrapperRef.current.contains(event.target)) { setIsOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [wrapperRef]); const displayLabel = selectedValues.length === 0 ? label : selectedValues.length === options.length ? 'All' : `${selectedValues.length} Selected`; return (
{isOpen && (
{options.map(option => ( ))}
)}
); }; // Main dashboard component const OrderManager = ({ orders, branches, currentUser, onUpdateOrderStatus, onUpdateItemStatus, refetchOrders }) => { const [selectedBranches, setSelectedBranches] = useState([]); const [statusFilter, setStatusFilter] = useState('all'); // FIX: Added useState for editingOrder const [editingOrder, setEditingOrder] = useState(null); // --- Data Processing --- // Ensure XLSX is available for export const isXLSXModuleReady = useMemo(() => typeof window.XLSX !== 'undefined', []); // Load XLSX if not ready (Defensive measure) useEffect(() => { if (!isXLSXModuleReady) { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js'; script.onload = () => { isXLSXReady = true; }; // Set global flag document.head.appendChild(script); } }, [isXLSXModuleReady]); // Filtered orders list const filteredOrders = useMemo(() => { return orders.filter(order => { const branchMatch = selectedBranches.length === 0 || selectedBranches.includes(String(order.branchId)); const statusMatch = statusFilter === 'all' || order.status === statusFilter; return branchMatch && statusMatch; }).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); }, [orders, selectedBranches, statusFilter]); // Summary Calculation const summary = useMemo(() => { let totalQty = 0; let collectedQty = 0; filteredOrders.forEach(order => { order.items?.forEach(item => { totalQty += item.quantity || 0; collectedQty += item[COLLECTED_QTY_KEY] || 0; }); }); return { totalQty, collectedQty, remainingQty: totalQty - collectedQty, }; }, [filteredOrders]); const branchOptions = useMemo(() => { return branches.map(b => ({ value: String(b.id), label: b.name })); }, [branches]); const statusOptions = ['all', 'Open', 'Partial', 'Closed']; // --- Export to Excel Function --- const handleExport = () => { if (!window.XLSX) { console.error("XLSX module is not loaded."); return; } const data = filteredOrders.flatMap(order => { const branchName = branches.find(b => b.id === order.branchId)?.name || 'Unknown'; return order.items.map(item => { const requested = item.quantity || 0; const collected = item[COLLECTED_QTY_KEY] || 0; const percentage = requested > 0 ? (collected / requested) : 0; return { 'Order ID': String(order.id).substring(0, 8), 'Branch': branchName, 'Order Status': order.status, 'Created Date': new Date(order.createdAt).toLocaleDateString(), 'Item ID': item.itemId, 'Description': item.description, 'Requested QTY': requested, 'Collected QTY': collected, 'Remaining QTY': requested - collected, 'Collected %': (percentage * 100).toFixed(2) + '%', 'Item Status': item.status, 'Chat ID': item.chatId, }; }); }); if (data.length === 0) { console.warn("No data to export."); return; } const worksheet = window.XLSX.utils.json_to_sheet(data); const workbook = window.XLSX.utils.book_new(); window.XLSX.utils.book_append_sheet(workbook, worksheet, "Orders Report"); window.XLSX.writeFile(workbook, `Order_Management_Report_${new Date().toISOString().substring(0, 10)}.xlsx`); }; // --- End Export Function --- // Render return (

All Orders Management

{/* Summary Dashboard */}

Total Requested QTY (Filtered)

{summary.totalQty}

Total Collected QTY (Filtered)

{summary.collectedQty}

Total Remaining QTY (Filtered)

{summary.remainingQty}

{/* Filters and Export */}
{/* Order Table/List */}
{filteredOrders.length === 0 ? ( ) : ( filteredOrders.map(order => { const branchName = branches.find(b => b.id === order.branchId)?.name || 'Unknown Branch'; const totalItems = order.items?.length || 0; const requestedQty = order.items?.reduce((sum, item) => sum + (item.quantity || 0), 0) || 0; const collectedQty = order.items?.reduce((sum, item) => sum + (item[COLLECTED_QTY_KEY] || 0), 0) || 0; const collectionPercentage = requestedQty > 0 ? (collectedQty / requestedQty) * 100 : 0; const hasUnread = order.items?.some(i => i.unreadByAdmin > 0) || false; return ( ) }) )}
Order ID Branch Items (Collected %) Overall Status Created Date Actions
No orders match the current criteria.
{String(order.id).substring(0, 8)}... {branchName} {totalItems} lines ({collectionPercentage.toFixed(1)}% collected) {order.status} {new Date(order.createdAt).toLocaleDateString()}
{/* Edit Details Button */} {/* Status Quick Update Button */} {order.status !== 'Closed' && ( )}
{editingOrder && ( setEditingOrder(null)} onUpdateItemStatus={onUpdateItemStatus} onRefetchOrder={refetchOrders} // Pass refetch function for manual sync if needed /> )}
); }; // --- MAIN STANDALONE APPLICATION COMPONENT --- const OrderManagementStandalone = () => { const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); const [isSupabaseReady, setIsSupabaseReady] = useState(false); // --- SUPABASE & XLSX CDN LOADER & INITIALIZATION --- useEffect(() => { const loadScript = (src, onloadCallback, onerrorCallback) => { const script = document.createElement('script'); script.src = src; script.onload = onloadCallback; script.onerror = onerrorCallback; document.head.appendChild(script); }; // 1. Load Supabase if (window.supabase && window.supabase.createClient) { supabaseGlobal = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); setIsSupabaseReady(true); } else { loadScript( 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2', () => { if (window.supabase && window.supabase.createClient) { supabaseGlobal = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); setIsSupabaseReady(true); } }, () => { console.error("Failed to load Supabase CDN script."); setLoading(false); } ); } }, []); // --- INITIAL ADMIN CREATION & AUTH READINESS (Ensures an admin exists) --- useEffect(() => { if (!isSupabaseReady || currentUser) return; const checkAndCreateAdmin = async () => { setLoading(false); }; checkAndCreateAdmin(); }, [isSupabaseReady, currentUser]); // --- Data Fetching --- const supabaseClient = isSupabaseReady ? supabaseGlobal : null; // Fetch all branches const { data: branches, loading: loadingBranches } = useSupabaseCollection(supabaseClient, 'branches', [isSupabaseReady]); // Fetch all orders - now includes refetch method from the hook // IMPORTANT: We need access to handleLocalUpdate from the hook for defensive updates const { data: orders, loading: loadingOrders, refetch: refetchOrders, handleLocalUpdate } = useSupabaseCollection(supabaseClient, 'orders', [isSupabaseReady]); // --- Handlers --- const handleLoginSuccess = useCallback((user) => { setCurrentUser(user); }, []); const handleLogout = useCallback(() => { setCurrentUser(null); }, []); // Handler for overall order status update (used by quick button) const handleUpdateOrderStatus = async (orderId, newFields) => { if (typeof supabaseGlobal === 'undefined') throw new Error("Supabase not initialized."); // We rely on the local update function in the hook for smoothness const { error } = await supabaseGlobal.from('orders').update(newFields).eq('id', orderId); if (error) throw error; }; // Handler for individual item status update (used by OrderDetailsModal) // This handler now receives the newly fetched single ORDER record after update const handleUpdateOrderItemStatus = useCallback((orderId, updatedOrderRecord) => { // Use the dedicated local update function from the hook handleLocalUpdate(updatedOrderRecord, 'UPDATE'); }, [handleLocalUpdate]); const LoadingCheck = loading || loadingBranches || loadingOrders || !isSupabaseReady; if (LoadingCheck) { return
; } if (!currentUser) { return ; } if (currentUser.role !== 'admin') { return (

Access Denied

Only authenticated Admin users can view this panel.

); } return (
{/* Header and Logout */}

All Orders Management (Standalone)

Hello, {currentUser.userName}
{/* Main Order Manager Component */}

This standalone interface manages orders and branches data.

); }; export default OrderManagementStandalone;