import React, { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import axios from "axios"; import { LayoutDashboard, Package, Wrench, ShoppingCart, Users, BarChart3, Settings, AlertTriangle, TrendingUp, DollarSign, Calendar, Download, Plus, Edit, Trash2, Eye, ChevronRight, ChevronLeft, Search, Filter, RefreshCw, FileText, FileSpreadsheet, Info, Image, Printer, CheckCircle, Clock, } from "lucide-react"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Badge } from "../components/ui/badge"; import { Separator } from "../components/ui/separator"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "../components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, } from "../components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "../components/ui/alert-dialog"; import { Label } from "../components/ui/label"; import { Textarea } from "../components/ui/textarea"; import { useAuth } from "../context/AuthContext"; import { toast } from "sonner"; import RichTextEditor from "../components/RichTextEditor"; import ImageUploadManager from "../components/ImageUploadManager"; import MediaManager from "../components/MediaManager"; import { clearCache } from "../utils/apiCache"; const API = `${process.env.REACT_APP_BACKEND_URL}/api`; const statusColors = { pending: "bg-yellow-500", processing: "bg-blue-500", layaway: "bg-purple-500", shipped: "bg-cyan-500", delivered: "bg-green-500", cancelled: "bg-red-500", refunded: "bg-orange-500", on_hold: "bg-gray-500", }; const AdminDashboard = () => { const { user, token, isAuthenticated } = useAuth(); const navigate = useNavigate(); const [activeTab, setActiveTab] = useState("dashboard"); const [loading, setLoading] = useState(true); // Dashboard data const [dashboardData, setDashboardData] = useState(null); // Products state const [products, setProducts] = useState([]); const [productDialog, setProductDialog] = useState(false); const [editingProduct, setEditingProduct] = useState(null); const [productForm, setProductForm] = useState({ name: "", description: "", price: "", category: "", image_url: "", stock: "", brand: "", images: [], }); // Services state const [services, setServices] = useState([]); const [serviceDialog, setServiceDialog] = useState(false); const [editingService, setEditingService] = useState(null); const [serviceForm, setServiceForm] = useState({ name: "", description: "", price: "", duration: "", image_url: "", category: "", images: [], }); // Orders state const [orders, setOrders] = useState([]); const [orderStatusFilter, setOrderStatusFilter] = useState("all"); const [selectedOrder, setSelectedOrder] = useState(null); const [newStatus, setNewStatus] = useState(""); const [statusNotes, setStatusNotes] = useState(""); const [trackingNumber, setTrackingNumber] = useState(""); // Inventory state const [inventory, setInventory] = useState([]); const [adjustDialog, setAdjustDialog] = useState(false); const [adjustProduct, setAdjustProduct] = useState(null); const [adjustQty, setAdjustQty] = useState(0); const [adjustNotes, setAdjustNotes] = useState(""); const [inventorySearch, setInventorySearch] = useState(""); const [inventoryCategory, setInventoryCategory] = useState("all"); const [inventoryItemsPerPage, setInventoryItemsPerPage] = useState(20); const [inventoryCurrentPage, setInventoryCurrentPage] = useState(1); // Reports state const [reportPeriod, setReportPeriod] = useState("monthly"); const [salesReport, setSalesReport] = useState(null); // Bookings state const [bookings, setBookings] = useState([]); const [bookingCompleteDialog, setBookingCompleteDialog] = useState(false); const [selectedBooking, setSelectedBooking] = useState(null); const [bookingCompleteForm, setBookingCompleteForm] = useState({ diagnosis: "", work_performed: "", technician_notes: "", service_cost: "", paid: true, device_model: "", serial_number: "", product_number: "", screen_size: "", }); const [receiptData, setReceiptData] = useState(null); const [showReceiptDialog, setShowReceiptDialog] = useState(false); const [isEditMode, setIsEditMode] = useState(false); const receiptRef = useRef(null); // Categories state const [categories, setCategories] = useState([]); const [categoryDialog, setCategoryDialog] = useState(false); const [editingCategory, setEditingCategory] = useState(null); const [categoryForm, setCategoryForm] = useState({ name: "", description: "", }); // Users state const [users, setUsers] = useState([]); const [usersTotal, setUsersTotal] = useState(0); const [userDialog, setUserDialog] = useState(false); const [editingUser, setEditingUser] = useState(null); const [userForm, setUserForm] = useState({ name: "", email: "", password: "", role: "user", is_active: true, }); const [userSearch, setUserSearch] = useState(""); const [userRoleFilter, setUserRoleFilter] = useState(""); const [userStatusFilter, setUserStatusFilter] = useState(""); const [usersPerPage, setUsersPerPage] = useState(20); const [currentUsersPage, setCurrentUsersPage] = useState(1); // Delete confirmation state (generic for all delete operations) const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [productToDelete, setProductToDelete] = useState(null); const [confirmDialog, setConfirmDialog] = useState({ open: false, title: "Confirm Action", message: "Are you sure you want to proceed?", onConfirm: null, }); // About Page state const [aboutContent, setAboutContent] = useState([]); const [teamMembers, setTeamMembers] = useState([]); const [companyValues, setCompanyValues] = useState([]); const [aboutDialog, setAboutDialog] = useState(false); const [aboutEditType, setAboutEditType] = useState(""); // "content", "team", "value" const [editingAboutItem, setEditingAboutItem] = useState(null); const [aboutForm, setAboutForm] = useState({ section: "", title: "", subtitle: "", content: "", image_url: "", data: null, display_order: 0, is_active: true, name: "", role: "", bio: "", email: "", linkedin: "", description: "", icon: "", }); // Helper function to show confirmation dialog const showConfirmDialog = (title, message, onConfirm) => { setConfirmDialog({ open: true, title, message, onConfirm, }); }; const handleConfirmAction = () => { if (confirmDialog.onConfirm) { confirmDialog.onConfirm(); } setConfirmDialog({ ...confirmDialog, open: false, onConfirm: null }); }; const handleCancelConfirm = () => { setConfirmDialog({ ...confirmDialog, open: false, onConfirm: null }); }; useEffect(() => { // Only fetch if properly authenticated as admin if (isAuthenticated && user?.role === "admin" && token) { fetchDashboardData(); fetchCategories(); // Fetch categories on mount for product form dropdown } else if (isAuthenticated && user && user.role !== "admin") { // User is authenticated but not admin toast.error("Admin access required"); navigate("/"); } }, [isAuthenticated, user, token, navigate]); useEffect(() => { if (activeTab === "products") fetchProducts(); if (activeTab === "services") fetchServices(); if (activeTab === "orders") fetchOrders(); if (activeTab === "inventory") fetchInventory(); if (activeTab === "reports") fetchReports(); if (activeTab === "bookings") fetchBookings(); if (activeTab === "categories") fetchCategories(); if (activeTab === "users") fetchUsers(); if (activeTab === "about") fetchAboutData(); }, [activeTab]); // Fetch users when filters or pagination change useEffect(() => { if (activeTab === "users") { fetchUsers(); } }, [ currentUsersPage, usersPerPage, userSearch, userRoleFilter, userStatusFilter, ]); const fetchDashboardData = async () => { try { // Validate token exists if (!token) { console.warn("No authentication token available"); toast.error("Authentication required"); navigate("/login"); return; } const response = await axios.get(`${API}/admin/dashboard`, { headers: { Authorization: `Bearer ${token}` }, timeout: 10000, // 10 second timeout }); // Validate response structure if (response.data && response.data.stats) { setDashboardData(response.data); // Show warning if partial data if (response.data.error) { toast.warning(response.data.error); } } else { console.error("Invalid dashboard response structure:", response.data); toast.error("Received invalid dashboard data"); } } catch (error) { console.error("Failed to fetch dashboard:", error); // Detailed error handling if (error.response) { // Server responded with error if (error.response.status === 401) { toast.error("Session expired. Please login again."); navigate("/login"); } else if (error.response.status === 403) { toast.error("Admin access required"); navigate("/"); } else if (error.response.status === 500) { toast.error("Server error. Please try again later."); } else { toast.error( `Failed to load dashboard data (Error ${error.response.status})`, ); } } else if (error.request) { // Request made but no response toast.error("Cannot connect to server. Please check your connection."); } else { // Other errors toast.error("Failed to load dashboard data"); } } finally { setLoading(false); } }; const fetchProducts = async () => { try { const response = await axios.get( `${API}/admin/products?include_inactive=true`, { headers: { Authorization: `Bearer ${token}` }, }, ); setProducts(response.data); } catch (error) { toast.error("Failed to load products"); } }; const fetchServices = async () => { try { const response = await axios.get( `${API}/admin/services?include_inactive=true`, { headers: { Authorization: `Bearer ${token}` }, }, ); setServices(response.data); } catch (error) { toast.error("Failed to load services"); } }; const fetchOrders = async () => { try { const params = orderStatusFilter !== "all" ? `?status=${orderStatusFilter}` : ""; const response = await axios.get(`${API}/admin/orders${params}`, { headers: { Authorization: `Bearer ${token}` }, }); setOrders(response.data); } catch (error) { toast.error("Failed to load orders"); } }; const fetchInventory = async () => { try { const response = await axios.get(`${API}/admin/inventory`, { headers: { Authorization: `Bearer ${token}` }, }); setInventory(response.data); } catch (error) { toast.error("Failed to load inventory"); } }; const fetchReports = async () => { try { const response = await axios.get( `${API}/admin/reports/sales?period=${reportPeriod}`, { headers: { Authorization: `Bearer ${token}` }, }, ); setSalesReport(response.data); } catch (error) { toast.error("Failed to load reports"); } }; const fetchBookings = async () => { try { const response = await axios.get(`${API}/admin/bookings`, { headers: { Authorization: `Bearer ${token}` }, }); setBookings(response.data); } catch (error) { toast.error("Failed to load bookings"); } }; const fetchCategories = async () => { try { const response = await axios.get(`${API}/admin/categories`, { headers: { Authorization: `Bearer ${token}` }, }); setCategories(response.data); } catch (error) { toast.error("Failed to load categories"); } }; const fetchUsers = async () => { try { const skip = (currentUsersPage - 1) * usersPerPage; const response = await axios.get(`${API}/admin/users`, { headers: { Authorization: `Bearer ${token}` }, params: { skip, limit: usersPerPage, search: userSearch, role: userRoleFilter, status: userStatusFilter, }, }); setUsers(response.data.users); setUsersTotal(response.data.total); } catch (error) { toast.error("Failed to load users"); } }; const handleUserSubmit = async () => { try { const data = { ...userForm, role: userForm.role.toLowerCase(), }; if (editingUser) { // Update user - only include password if it's not empty if (!data.password || data.password.trim() === "") { delete data.password; // Don't send empty password } await axios.put(`${API}/admin/users/${editingUser.id}`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("User updated successfully"); } else { // Create new user if (!data.password) { toast.error("Password is required for new users"); return; } await axios.post(`${API}/admin/users`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("User created"); } setUserDialog(false); setEditingUser(null); setUserForm({ name: "", email: "", password: "", role: "user", is_active: true, }); fetchUsers(); } catch (error) { toast.error(error.response?.data?.detail || "Failed to save user"); } }; const handleToggleUserActive = async (userId) => { try { await axios.put( `${API}/admin/users/${userId}/toggle-active`, {}, { headers: { Authorization: `Bearer ${token}` }, }, ); toast.success("User status updated"); fetchUsers(); } catch (error) { toast.error( error.response?.data?.detail || "Failed to update user status", ); } }; const handleDeleteUser = async (userId) => { try { await axios.delete(`${API}/admin/users/${userId}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("User deleted"); fetchUsers(); } catch (error) { toast.error(error.response?.data?.detail || "Failed to delete user"); } }; // About Page Functions const fetchAboutData = async () => { try { const [contentRes, teamRes, valuesRes] = await Promise.all([ axios.get(`${API}/admin/about/content`, { headers: { Authorization: `Bearer ${token}` }, }), axios.get(`${API}/admin/about/team`, { headers: { Authorization: `Bearer ${token}` }, }), axios.get(`${API}/admin/about/values`, { headers: { Authorization: `Bearer ${token}` }, }), ]); setAboutContent(contentRes.data); setTeamMembers(teamRes.data); setCompanyValues(valuesRes.data); } catch (error) { toast.error("Failed to fetch about page data"); } }; const handleSaveAboutItem = async () => { try { if (aboutEditType === "content") { const data = { section: aboutForm.section, title: aboutForm.title || null, subtitle: aboutForm.subtitle || null, content: aboutForm.content || null, image_url: aboutForm.image_url || null, data: aboutForm.data, display_order: aboutForm.display_order, is_active: aboutForm.is_active, }; if (editingAboutItem) { await axios.put( `${API}/admin/about/content/${editingAboutItem.id}`, data, { headers: { Authorization: `Bearer ${token}` } }, ); toast.success("Content updated"); } else { await axios.post(`${API}/admin/about/content`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Content created"); } } else if (aboutEditType === "team") { const data = { name: aboutForm.name, role: aboutForm.role, bio: aboutForm.bio || null, image_url: aboutForm.image_url || null, email: aboutForm.email || null, linkedin: aboutForm.linkedin || null, display_order: aboutForm.display_order, is_active: aboutForm.is_active, }; if (editingAboutItem) { await axios.put( `${API}/admin/about/team/${editingAboutItem.id}`, data, { headers: { Authorization: `Bearer ${token}` } }, ); toast.success("Team member updated"); } else { await axios.post(`${API}/admin/about/team`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Team member created"); } } else if (aboutEditType === "value") { const data = { title: aboutForm.title, description: aboutForm.description, icon: aboutForm.icon, display_order: aboutForm.display_order, is_active: aboutForm.is_active, }; if (editingAboutItem) { await axios.put( `${API}/admin/about/values/${editingAboutItem.id}`, data, { headers: { Authorization: `Bearer ${token}` } }, ); toast.success("Company value updated"); } else { await axios.post(`${API}/admin/about/values`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Company value created"); } } setAboutDialog(false); setEditingAboutItem(null); fetchAboutData(); } catch (error) { toast.error( error.response?.data?.detail || "Failed to save about page item", ); } }; const handleDeleteAboutItem = (id, type) => { const itemName = type === "content" ? "content section" : type === "team" ? "team member" : "company value"; showConfirmDialog( "Delete Item", `Are you sure you want to delete this ${itemName}? This action cannot be undone.`, async () => { try { if (type === "content") { await axios.delete(`${API}/admin/about/content/${id}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Content deleted"); } else if (type === "team") { await axios.delete(`${API}/admin/about/team/${id}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Team member deleted"); } else if (type === "value") { await axios.delete(`${API}/admin/about/values/${id}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Company value deleted"); } fetchAboutData(); } catch (error) { toast.error( error.response?.data?.detail || "Failed to delete about page item", ); } }, ); }; // Product CRUD const handleProductSubmit = async () => { try { const price = parseFloat(productForm.price); if (isNaN(price) || price <= 0) { toast.error("Price must be greater than 0"); return; } const data = { ...productForm, price: price, stock: parseInt(productForm.stock) || 10, }; if (editingProduct) { await axios.put(`${API}/admin/products/${editingProduct.id}`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Product updated"); } else { await axios.post(`${API}/admin/products`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Product created"); } clearCache("products-"); setProductDialog(false); setEditingProduct(null); setProductForm({ name: "", description: "", price: "", category: "", image_url: "", stock: "", brand: "", images: [], }); fetchProducts(); } catch (error) { toast.error("Failed to save product"); } }; const handleDeleteProduct = async () => { if (!productToDelete) return; try { await axios.delete(`${API}/admin/products/${productToDelete.id}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success(`"${productToDelete.name}" has been deleted`); clearCache("products-"); setDeleteConfirmOpen(false); setProductToDelete(null); fetchProducts(); } catch (error) { toast.error("Failed to delete product"); setDeleteConfirmOpen(false); setProductToDelete(null); } }; // Service CRUD const handleServiceSubmit = async () => { try { const price = parseFloat(serviceForm.price) || 0; if (isNaN(price) || price < 0) { toast.error("Price cannot be negative"); return; } const data = { ...serviceForm, price: price, }; if (editingService) { await axios.put(`${API}/admin/services/${editingService.id}`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Service updated"); } else { await axios.post(`${API}/admin/services`, data, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Service created"); } clearCache("services-"); setServiceDialog(false); setEditingService(null); setServiceForm({ name: "", description: "", price: "", duration: "", image_url: "", category: "", images: [], }); fetchServices(); } catch (error) { toast.error("Failed to save service"); } }; const handleDeleteService = (id) => { showConfirmDialog( "Delete Service", "Are you sure you want to delete this service? This action cannot be undone.", async () => { try { await axios.delete(`${API}/admin/services/${id}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Service deleted"); clearCache("services-"); fetchServices(); } catch (error) { toast.error("Failed to delete service"); } }, ); }; // Booking completion const handleOpenCompleteDialog = (booking) => { setSelectedBooking(booking); setBookingCompleteForm({ diagnosis: booking.diagnosis || "", work_performed: booking.work_performed || "", technician_notes: booking.technician_notes || "", service_cost: booking.service_cost || "", paid: booking.paid !== undefined ? booking.paid : true, device_model: booking.device_model || "", serial_number: booking.serial_number || "", product_number: booking.product_number || "", screen_size: booking.screen_size || "", }); setIsEditMode(false); setBookingCompleteDialog(true); }; const handleDeleteBooking = (booking) => { showConfirmDialog( "Delete Booking", `Are you sure you want to delete the booking for "${booking.name}"? This action cannot be undone.`, async () => { try { await axios.delete(`${API}/admin/bookings/${booking.id}`, { headers: { Authorization: `Bearer ${token}` }, }); toast.success("Booking deleted successfully"); fetchBookings(); } catch (error) { toast.error("Failed to delete booking"); } }, ); }; const handleCompleteBooking = async () => { if (!selectedBooking) return; try { const data = { ...bookingCompleteForm, service_cost: bookingCompleteForm.service_cost ? parseFloat(bookingCompleteForm.service_cost) : null, }; await axios.put( `${API}/admin/bookings/${selectedBooking.id}/complete`, data, { headers: { Authorization: `Bearer ${token}` } }, ); toast.success("Service marked as completed!"); setBookingCompleteDialog(false); setSelectedBooking(null); fetchBookings(); } catch (error) { toast.error("Failed to complete booking"); } }; const handleUpdateBookingStatus = async (bookingId, status) => { try { await axios.put( `${API}/admin/bookings/${bookingId}/status?status=${status}`, {}, { headers: { Authorization: `Bearer ${token}` } }, ); toast.success(`Booking status updated to ${status}`); fetchBookings(); } catch (error) { toast.error("Failed to update status"); } }; const handlePrintReceipt = async (booking) => { try { const response = await axios.get( `${API}/admin/bookings/${booking.id}/receipt`, { headers: { Authorization: `Bearer ${token}` } }, ); setReceiptData(response.data); setShowReceiptDialog(true); } catch (error) { toast.error("Failed to load receipt data"); } }; const printReceipt = () => { if (!receiptData) return; const printWindow = window.open("", "_blank"); printWindow.document.write(`
PromptTech Solutions
${receiptData.company?.address || "Belmopan City, Cayo District, Belize"}
Phone: ${receiptData.company?.phone || "+501 638-6318"} | Email: ${receiptData.company?.email || "prompttechbz@gmail.com"}
Diagnosis:
${receiptData.diagnosis}
Work Performed:
${receiptData.work_performed}
${receiptData.notes}
You need admin privileges to access this page.
Manage your store
${dashboardData.stats.total_revenue.toFixed(2)}
{dashboardData.stats.total_orders}
{dashboardData.stats.total_products}
{dashboardData.stats.total_users}
Orders
{dashboardData.stats.today_orders}
Revenue
${(dashboardData.stats.today_revenue || 0).toFixed(2)}
Revenue
$ {(dashboardData.stats.monthly_revenue || 0).toFixed( 2, )}
Pending Bookings
{dashboardData.stats.pending_bookings}
{order.id.slice(0, 8)}...
{new Date(order.created_at).toLocaleString()}
| Product | Category | Price | Stock | Status | Actions |
|---|---|---|---|---|---|
|
|
{product.category} | ${product.price.toFixed(2)} | {product.stock} |
|
|
| Service | Category | Duration | Price | Status | Actions |
|---|---|---|---|---|---|
|
|
{service.category} | {service.duration} | ${service.price.toFixed(2)} |
|
|
Order ID
{order.id.slice(0, 8)}...
Customer
{order.user_name}
Date
{new Date(order.created_at).toLocaleDateString()}
Total
${order.total.toFixed(2)}
| Product | Category | Price | Stock | Threshold | Active | Actions |
|---|---|---|---|---|---|---|
| {product.name} | {product.category} | ${product.price?.toFixed(2) || "0.00"} | {product.stock} | {product.low_stock_threshold} |
No bookings yet
) : ( bookings.map((booking) => (Service
{booking.service_name}
Customer
{booking.name}
{booking.email}
Phone
{booking.phone}
Preferred Date
{booking.preferred_date}
Completed
{new Date( booking.completed_at, ).toLocaleDateString()}
Notes:{" "} {booking.notes}
)} {booking.diagnosis && (Diagnosis:{" "} {booking.diagnosis}
)} {booking.work_performed && (Work Done:{" "} {booking.work_performed}
)}| Name | Description | Actions |
|---|---|---|
| {category.name} | {category.description || "No description"} |
|
| Name | Role | Status | Created | Actions | |
|---|---|---|---|---|---|
| {user.name} | {user.email} |
|
|
{new Date(user.created_at).toLocaleDateString()} |
|
Total Orders
{salesReport.summary.total_orders}
Product Revenue
${salesReport.summary.total_revenue.toFixed(2)}
Products Sold
{salesReport.summary.total_products_sold}
Avg Order Value
${salesReport.summary.average_order_value.toFixed(2)}
Services Booked
{salesReport.summary.total_services_booked || 0}
Services Paid
{salesReport.summary.total_services_paid || 0}
Service Revenue
$ {( salesReport.summary.total_service_revenue || 0 ).toFixed(2)}
Combined Revenue
$ {(salesReport.summary.combined_revenue || 0).toFixed( 2, )}
| Period | Orders | Product Rev | Services | Paid | Service Rev |
|---|---|---|---|---|---|
| {row.period} | {row.orders} | ${row.revenue.toFixed(2)} | {row.services_booked || 0} | {row.services_paid || 0} | ${(row.service_revenue || 0).toFixed(2)} |
{item.subtitle}
)}No content sections yet. Add your first section!
)}{member.role}
No team members yet. Add your first team member!
)}{value.description}
No company values yet. Add your first value!
)}