import React, { useState, useEffect } 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, } 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 { 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([]); // 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 const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [productToDelete, setProductToDelete] = useState(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: "", }); 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 = async (id, type) => { if (!window.confirm("Are you sure you want to delete this item?")) return; 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 data = { ...productForm, price: parseFloat(productForm.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 data = { ...serviceForm, price: parseFloat(serviceForm.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 = async (id) => { if (!window.confirm("Are you sure you want to delete this service?")) return; 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"); } }; // Order status update const handleUpdateOrderStatus = async () => { if (!selectedOrder || !newStatus) return; try { await axios.put( `${API}/admin/orders/${selectedOrder}/status`, { status: newStatus, notes: statusNotes, tracking_number: trackingNumber || null, }, { headers: { Authorization: `Bearer ${token}` }, } ); toast.success("Order status updated"); setSelectedOrder(null); setNewStatus(""); setStatusNotes(""); setTrackingNumber(""); fetchOrders(); } catch (error) { toast.error("Failed to update order"); } }; // Inventory adjustment const handleAdjustInventory = async () => { if (!adjustProduct) return; try { await axios.post( `${API}/admin/inventory/${adjustProduct.id}/adjust`, { quantity_change: adjustQty, notes: adjustNotes, }, { headers: { Authorization: `Bearer ${token}` }, } ); toast.success("Inventory adjusted"); setAdjustDialog(false); setAdjustProduct(null); setAdjustQty(0); setAdjustNotes(""); fetchInventory(); } catch (error) { toast.error("Failed to adjust inventory"); } }; // Toggle product active status const handleToggleActive = async (product) => { try { await axios.put( `${API}/admin/products/${product.id}`, { is_active: !product.is_active }, { headers: { Authorization: `Bearer ${token}` } } ); toast.success( `Product ${!product.is_active ? "activated" : "deactivated"}` ); fetchInventory(); } catch (error) { toast.error("Failed to update product status"); } }; // Export reports const handleExportCSV = async (type) => { try { const response = await axios.get( `${API}/admin/reports/export/csv?report_type=${type}&period=${reportPeriod}`, { headers: { Authorization: `Bearer ${token}` }, responseType: "blob", } ); const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement("a"); link.href = url; link.setAttribute("download", `${type}_report.csv`); document.body.appendChild(link); link.click(); link.remove(); toast.success("CSV exported"); } catch (error) { toast.error("Failed to export CSV"); } }; const handleExportPDF = async (type) => { try { const response = await axios.get( `${API}/admin/reports/export/pdf?report_type=${type}&period=${reportPeriod}`, { headers: { Authorization: `Bearer ${token}` }, responseType: "blob", } ); const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement("a"); link.href = url; link.setAttribute("download", `${type}_report.pdf`); document.body.appendChild(link); link.click(); link.remove(); toast.success("PDF exported"); } catch (error) { toast.error("Failed to export PDF"); } }; if (!isAuthenticated || user?.role !== "admin") { return (
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} |
Service
{booking.service_name}
Customer
{booking.name}
{booking.email}
Phone
{booking.phone}
Preferred Date
{booking.preferred_date}
Notes: {booking.notes}
)}| 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}
Total Revenue
${salesReport.summary.total_revenue.toFixed(2)}
Products Sold
{salesReport.summary.total_products_sold}
Avg Order Value
${salesReport.summary.average_order_value.toFixed(2)}
| Period | Orders | Revenue | Products Sold | Services Booked |
|---|---|---|---|---|
| {row.period} | {row.orders} | ${row.revenue.toFixed(2)} | {row.products_sold} | {row.services_booked || 0} |
{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!
)}