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.
{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 */}
{sendingStatus === 'error' &&
Error occurred during sending. **Please check column types in Chats table**.
}
Close Chat
);
};
// 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
/>
Cancel
maxQty}>Confirm
);
};
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:
handleOverallStatusUpdate(e.target.value)}
className={`p-1 text-sm rounded-lg border-2 font-semibold bg-white border-indigo-400`}
disabled={status === 'loading'}
>
{['Open', 'Partial', 'Closed'].map(s => (
{s}
))}
{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 */}
{
const newStatus = e.target.value;
if (newStatus === STATUSES.OK || newStatus === STATUSES.PARTIAL) {
// Prompt for quantity if collected or partial
openQtyModal(index, newStatus, maxQty);
} else {
// For other statuses (Pending, Not Found, Redirected), set collected to 0
updateSingleItemStatus(index, newStatus, 0);
}
}}
className={`p-1 text-sm rounded-lg border-2 font-semibold ${item.status === STATUSES.OK ? 'bg-emerald-100 text-emerald-800 border-emerald-300' :
item.status === STATUSES.PENDING ? 'bg-yellow-100 text-yellow-800 border-yellow-300' :
item.status === STATUSES.NOT_FOUND ? 'bg-red-100 text-red-800 border-red-300' :
'bg-gray-100 text-gray-700 border-gray-300'
}`}
disabled={status === 'loading'}
>
{Object.values(STATUSES).map(s => (
{s}
))}
{/* Chat Button */}
setChatTarget({ order, item })}
className="p-1 bg-indigo-100 text-indigo-700 rounded-full hover:bg-indigo-200 transition duration-150"
title="Open Chat for Item"
>
{item.unreadByAdmin > 0 && }
);
})}
{status === 'success' &&
Item status updated successfully!
}
{status === 'error' &&
Error updating item status.
}
Close Details
{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 (
setIsOpen(!isOpen)}
className="w-full p-2 border rounded-lg bg-white text-sm text-left focus:ring-indigo-500 focus:border-indigo-500 flex justify-between items-center"
>
{displayLabel}
▼
{isOpen && (
{options.map(option => (
handleToggle(option.value)}
className="mr-2 rounded text-indigo-600 focus:ring-indigo-500"
/>
{option.label}
))}
)}
);
};
// 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 */}
Filter by Branch (Multi-Select):
Filter by Status:
setStatusFilter(e.target.value)}
className="mt-1 block w-full p-2 border rounded-lg bg-white text-sm"
>
{statusOptions.map(s => (
{s}
))}
{isXLSXModuleReady ? (
<> Export to Excel ({filteredOrders.length})>
) : (
<> Loading Export Tool>
)}
{/* Order Table/List */}
Order ID
Branch
Items (Collected %)
Overall Status
Created Date
Actions
{filteredOrders.length === 0 ? (
No orders match the current criteria.
) : (
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 (
{String(order.id).substring(0, 8)}...
{branchName}
{totalItems} lines ({collectionPercentage.toFixed(1)}% collected)
{order.status}
{new Date(order.createdAt).toLocaleDateString()}
{/* Edit Details Button */}
setEditingOrder(order)}
className="text-indigo-600 hover:text-indigo-900 p-2 rounded-full hover:bg-indigo-100 transition relative"
title="View/Edit Details"
>
{hasUnread && }
{/* Status Quick Update Button */}
{order.status !== 'Closed' && (
handleQuickStatusUpdate(order.id, 'Closed')}
className="text-emerald-600 hover:text-emerald-900 p-2 rounded-full hover:bg-emerald-100 transition"
title="Mark as 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.
Logout
);
}
return (
{/* Header and Logout */}
All Orders Management (Standalone)
Hello, {currentUser.userName}
Logout
{/* Main Order Manager Component */}
This standalone interface manages orders and branches data.
);
};
export default OrderManagementStandalone;