diff --git a/app/dashboard/admin/page.tsx b/app/dashboard/admin/page.tsx index d4548cf..85e4661 100644 --- a/app/dashboard/admin/page.tsx +++ b/app/dashboard/admin/page.tsx @@ -37,7 +37,7 @@ class ErrorBoundary extends Component { componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error(`Error loading ${this.props.componentName || 'component'}:`, error, errorInfo); - + // Log to error tracking service if available if (typeof window !== 'undefined' && (window as any).gtag) { (window as any).gtag('event', 'exception', { @@ -105,31 +105,31 @@ class ErrorBoundary extends Component { } // Lazy load admin components with error handling -const AdminAnalytics = lazy(() => +const AdminAnalytics = lazy(() => import("@/components/admin/AdminAnalytics").catch((err) => { console.error("Failed to load AdminAnalytics:", err); throw err; }) ); -const InviteVendorCard = lazy(() => +const InviteVendorCard = lazy(() => import("@/components/admin/InviteVendorCard").catch((err) => { console.error("Failed to load InviteVendorCard:", err); throw err; }) ); -const BanUserCard = lazy(() => +const BanUserCard = lazy(() => import("@/components/admin/BanUserCard").catch((err) => { console.error("Failed to load BanUserCard:", err); throw err; }) ); -const InvitationsListCard = lazy(() => +const InvitationsListCard = lazy(() => import("@/components/admin/InvitationsListCard").catch((err) => { console.error("Failed to load InvitationsListCard:", err); throw err; }) ); -const VendorsCard = lazy(() => +const VendorsCard = lazy(() => import("@/components/admin/VendorsCard").catch((err) => { console.error("Failed to load VendorsCard:", err); throw err; @@ -139,7 +139,7 @@ const VendorsCard = lazy(() => // Loading skeleton with timeout warning function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?: boolean }) { return ( -
-
- + {/* Header skeleton */}
@@ -181,9 +181,9 @@ function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?: {/* Metric cards grid skeleton */}
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => ( - -
{[1, 2, 3, 4].map((i) => ( - {[1, 2, 3].map((j) => ( -
{ // Avoid prefetching if already done if (prefetchedTabs.has(tab)) return; - + const startTime = performance.now(); - + if (tab === "analytics") { // Prefetch analytics component import("@/components/admin/AdminAnalytics") @@ -355,7 +355,7 @@ export default function AdminPage() { const loadTime = performance.now() - startTime; console.log(`[Performance] AdminAnalytics prefetched in ${loadTime.toFixed(2)}ms`); }) - .catch(() => {}); + .catch(() => { }); } else if (tab === "management") { // Prefetch management components Promise.all([ @@ -368,9 +368,9 @@ export default function AdminPage() { const loadTime = performance.now() - startTime; console.log(`[Performance] Management components prefetched in ${loadTime.toFixed(2)}ms`); }) - .catch(() => {}); + .catch(() => { }); } - + setPrefetchedTabs(prev => new Set(prev).add(tab)); }; @@ -392,26 +392,26 @@ export default function AdminPage() { return (
-
+
-

Admin Dashboard

+

Admin Dashboard

Platform analytics and vendor management

-
- handleTabHover("analytics")} onFocus={() => handleTabFocus("analytics")} > Analytics - handleTabHover("management")} onFocus={() => handleTabFocus("management")} @@ -422,7 +422,7 @@ export default function AdminPage() { - } timeout={5000} timeoutFallback={} @@ -436,7 +436,7 @@ export default function AdminPage() { - } timeout={5000} timeoutFallback={} diff --git a/app/dashboard/admin/users/page.tsx b/app/dashboard/admin/users/page.tsx index 0304732..9b6ab95 100644 --- a/app/dashboard/admin/users/page.tsx +++ b/app/dashboard/admin/users/page.tsx @@ -7,9 +7,10 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import { Search, Ban, UserCheck, Package, DollarSign, Loader2, Repeat } from "lucide-react"; +import { Search, Ban, UserCheck, Package, DollarSign, Loader2, Repeat, Users, ShoppingBag, CreditCard, UserX } from "lucide-react"; import { fetchClient } from "@/lib/api-client"; import { useToast } from "@/hooks/use-toast"; +import { motion, AnimatePresence } from "framer-motion"; interface TelegramUser { telegramUserId: string; @@ -88,123 +89,86 @@ export default function AdminUsersPage() { const totalSpent = users.reduce((sum, u) => sum + u.totalSpent, 0); const totalOrders = users.reduce((sum, u) => sum + u.totalOrders, 0); + const stats = [ + { + title: "Total Users", + value: users.length, + description: "Registered users", + icon: Users, + }, + { + title: "Users with Orders", + value: usersWithOrders.length, + description: `${users.length > 0 ? Math.round((usersWithOrders.length / users.length) * 100) : 0}% conversion rate`, + icon: ShoppingBag, + }, + { + title: "Total Revenue", + value: formatCurrency(totalSpent), + description: `${totalOrders} total orders`, + icon: DollarSign, + }, + { + title: "Returning", + value: returningCustomers.length, + description: `${usersWithOrders.length > 0 ? Math.round((returningCustomers.length / usersWithOrders.length) * 100) : 0}% of customers`, + icon: Repeat, + }, + { + title: "Blocked", + value: blockedUsers.length, + description: `${users.length > 0 ? Math.round((blockedUsers.length / users.length) * 100) : 0}% blocked rate`, + icon: UserX, + }, + ]; + return ( -
-
-

Telegram Users

-

Manage Telegram user accounts and view statistics

+
+
+
+

Telegram Users

+

Manage Telegram user accounts and view statistics

+
{/* Stats Cards */}
- - - Total Users - - - {loading ? ( -
- -
- ) : ( - <> -
{users.length}
-

Registered users

- - )} -
-
- - - Users with Orders - - - {loading ? ( -
- -
- ) : ( - <> -
{usersWithOrders.length}
-

- {users.length > 0 ? Math.round((usersWithOrders.length / users.length) * 100) : 0}% conversion rate -

- - )} -
-
- - - Total Revenue - - - {loading ? ( -
- -
- ) : ( - <> -
{formatCurrency(totalSpent)}
-

{totalOrders} total orders

- - )} -
-
- - - Returning Customers - - - - {loading ? ( -
- -
- ) : ( - <> -
{returningCustomers.length}
-

- {usersWithOrders.length > 0 ? Math.round((returningCustomers.length / usersWithOrders.length) * 100) : 0}% of customers -

- - )} -
-
- - - Blocked Users - - - {loading ? ( -
- -
- ) : ( - <> -
{blockedUsers.length}
-

- {users.length > 0 ? Math.round((blockedUsers.length / users.length) * 100) : 0}% blocked rate -

- - )} -
-
+ {stats.map((stat, i) => ( + + + {stat.title} + + + + {loading ? ( +
+ +
+ ) : ( +
+
{stat.value}
+

{stat.description}

+
+ )} +
+
+ ))}
{/* Search and Filters */} - - -
+ + +
- User Management + User Management View and manage all Telegram user accounts
- - + { setSearchQuery(e.target.value); @@ -216,19 +180,11 @@ export default function AdminUsersPage() {
- {loading ? ( -
- -
- ) : users.length === 0 ? ( -
- {searchQuery ? "No users found matching your search" : "No users found"} -
- ) : ( +
- - - User ID + + + User ID Username Orders Total Spent @@ -239,88 +195,118 @@ export default function AdminUsersPage() { - {users.map((user) => ( - - -
{user.telegramUserId}
-
- -
- {user.telegramUsername !== "Unknown" ? `@${user.telegramUsername}` : "Unknown"} -
-
- -
- - {user.totalOrders} - {user.completedOrders > 0 && ( - - {user.completedOrders} completed - - )} -
-
- -
- - {formatCurrency(user.totalSpent)} -
-
- - {user.isBlocked ? ( - - - - - - Blocked - - - {user.blockedReason && ( - -

{user.blockedReason}

-
+ + {loading ? ( + + +
+ +

Loading users...

+
+
+
+ ) : users.length === 0 ? ( + + + {searchQuery ? "No users found matching your search" : "No users found"} + + + ) : ( + users.map((user, index) => ( + + +
{user.telegramUserId}
+
+ +
+ {user.telegramUsername !== "Unknown" ? ( + <> + @ + {user.telegramUsername} + + ) : ( + Unknown )} - - - ) : user.totalOrders > 0 ? ( - Active - ) : ( - No Orders - )} - - - {user.firstOrderDate - ? new Date(user.firstOrderDate).toLocaleDateString() - : 'N/A'} - - - {user.lastOrderDate - ? new Date(user.lastOrderDate).toLocaleDateString() - : 'N/A'} - - -
- {!user.isBlocked ? ( - - ) : ( - - )} -
-
- - ))} +
+
+ +
+ {user.totalOrders} + {user.completedOrders > 0 && ( + + {user.completedOrders} done + + )} +
+
+ + {formatCurrency(user.totalSpent)} + + + {user.isBlocked ? ( + + + + + + Blocked + + + {user.blockedReason && ( + +

{user.blockedReason}

+
+ )} +
+
+ ) : user.totalOrders > 0 ? ( + Active + ) : ( + No Orders + )} +
+ + {user.firstOrderDate + ? new Date(user.firstOrderDate).toLocaleDateString() + : '-'} + + + {user.lastOrderDate + ? new Date(user.lastOrderDate).toLocaleDateString() + : '-'} + + +
+ {!user.isBlocked ? ( + + ) : ( + + )} +
+
+
+ )) + )} +
- )} +
+ {pagination && pagination.totalPages > 1 && ( -
+
- Showing page {pagination.page} of {pagination.totalPages} ({pagination.total} total users) + Showing page {pagination.page} of {pagination.totalPages}
@@ -336,6 +323,7 @@ export default function AdminUsersPage() { size="sm" onClick={() => setPage(p => p + 1)} disabled={!pagination.hasNextPage} + className="h-8" > Next diff --git a/app/dashboard/admin/vendors/page.tsx b/app/dashboard/admin/vendors/page.tsx index e864bbf..47a97f9 100644 --- a/app/dashboard/admin/vendors/page.tsx +++ b/app/dashboard/admin/vendors/page.tsx @@ -6,9 +6,10 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Search, MoreHorizontal, UserCheck, UserX, Mail, Loader2 } from "lucide-react"; +import { Search, MoreHorizontal, UserCheck, UserX, Mail, Loader2, Store, Shield, ShieldAlert, Clock, Calendar } from "lucide-react"; import { fetchClient } from "@/lib/api-client"; import { useToast } from "@/hooks/use-toast"; +import { motion, AnimatePresence } from "framer-motion"; interface Vendor { _id: string; @@ -68,10 +69,10 @@ export default function AdminVendorsPage() { }, [fetchVendors]); const filteredVendors = searchQuery.trim() - ? vendors.filter(v => - v.username.toLowerCase().includes(searchQuery.toLowerCase()) || - (v.storeId && v.storeId.toString().toLowerCase().includes(searchQuery.toLowerCase())) - ) + ? vendors.filter(v => + v.username.toLowerCase().includes(searchQuery.toLowerCase()) || + (v.storeId && v.storeId.toString().toLowerCase().includes(searchQuery.toLowerCase())) + ) : vendors; const activeVendors = vendors.filter(v => v.isActive); @@ -79,174 +80,214 @@ export default function AdminVendorsPage() { const adminVendors = vendors.filter(v => v.isAdmin); const totalVendors = pagination?.total || vendors.length; + const stats = [ + { + title: "Total Vendors", + value: totalVendors, + description: "Registered vendors", + icon: Store, + }, + { + title: "Active Vendors", + value: activeVendors.length, + description: `${vendors.length > 0 ? Math.round((activeVendors.length / vendors.length) * 100) : 0}% active rate`, + icon: UserCheck, + }, + { + title: "Suspended", + value: suspendedVendors.length, + description: `${vendors.length > 0 ? Math.round((suspendedVendors.length / vendors.length) * 100) : 0}% suspension rate`, + icon: UserX, + }, + { + title: "Admin Users", + value: adminVendors.length, + description: "Administrative access", + icon: ShieldAlert, + }, + ]; + return ( -
-
-

All Vendors

-

Manage vendor accounts and permissions

+
+
+
+

All Vendors

+

Manage vendor accounts and permissions

+
{/* Stats Cards */}
- - - Total Vendors - - -
{totalVendors}
-

Registered vendors

-
-
- - - Active Vendors - - -
{activeVendors.length}
-

- {vendors.length > 0 ? Math.round((activeVendors.length / vendors.length) * 100) : 0}% active rate -

-
-
- - - Suspended - - -
{suspendedVendors.length}
-

- {vendors.length > 0 ? Math.round((suspendedVendors.length / vendors.length) * 100) : 0}% suspension rate -

-
-
- - - Admin Users - - -
{adminVendors.length}
-

Administrative access

-
-
+ {stats.map((stat, i) => ( + + + {stat.title} + + + +
+
{stat.value}
+

{stat.description}

+
+
+
+ ))}
{/* Search and Filters */} - - -
+ + +
- Vendor Management + Vendor Management View and manage all vendor accounts
- - + setSearchQuery(e.target.value)} />
-
- {loading ? ( -
- -
- ) : filteredVendors.length === 0 ? ( -
- {searchQuery.trim() ? "No vendors found matching your search" : "No vendors found"} -
- ) : ( - <> - - - - Vendor - Store - Status - Join Date - Last Login - Actions - - - - {filteredVendors.map((vendor) => ( - - -
{vendor.username}
-
- {vendor.storeId || 'No store'} - -
- - {vendor.isActive ? "active" : "suspended"} - - {vendor.isAdmin && ( - - Admin - - )} -
-
- - {vendor.createdAt ? new Date(vendor.createdAt).toLocaleDateString() : 'N/A'} - - - {vendor.lastLogin ? new Date(vendor.lastLogin).toLocaleDateString() : 'Never'} - - -
- - - +
+
+ + + Vendor + Store + Status + Join Date + Last Login + Actions + + + + + {loading ? ( + + +
+ +

Loading vendors...

- ))} -
-
- {pagination && pagination.totalPages > 1 && ( -
- - Page {pagination.page} of {pagination.totalPages} ({pagination.total} total) - -
- - -
-
- )} - + ) : filteredVendors.length === 0 ? ( + + + {searchQuery.trim() ? "No vendors found matching your search" : "No vendors found"} + + + ) : ( + filteredVendors.map((vendor, index) => ( + + +
+
+ {vendor.username.substring(0, 2).toUpperCase()} +
+ {vendor.username} +
+
+ + {vendor.storeId ? ( + {vendor.storeId} + ) : ( + No store + )} + + +
+ + {vendor.isActive ? "Active" : "Suspended"} + + {vendor.isAdmin && ( + + + Admin + + )} +
+
+ +
+ + {vendor.createdAt ? new Date(vendor.createdAt).toLocaleDateString() : 'N/A'} +
+
+ +
+ + {vendor.lastLogin ? new Date(vendor.lastLogin).toLocaleDateString() : 'Never'} +
+
+ +
+ + + +
+
+
+ )) + )} + + + +
+ {pagination && pagination.totalPages > 1 && ( +
+ + Page {pagination.page} of {pagination.totalPages} + +
+ + +
+
)}
diff --git a/app/dashboard/shipping/page.tsx b/app/dashboard/shipping/page.tsx index 82917ba..6b58cf4 100644 --- a/app/dashboard/shipping/page.tsx +++ b/app/dashboard/shipping/page.tsx @@ -54,10 +54,10 @@ const ShippingTable = dynamic(() => import("@/components/tables/shipping-table") // Loading skeleton for shipping table function ShippingTableSkeleton() { return ( - + {/* Subtle loading indicator */}
-
- +
@@ -77,8 +77,8 @@ function ShippingTableSkeleton() {
{['Method Name', 'Price', 'Actions'].map((header, i) => ( -
- + {[...Array(4)].map((_, i) => ( -
row.startsWith("Authorization=")) ?.split("=")[1]; - + if (!authToken) { console.error("No auth token found"); return; } - + console.log("Sending request to add shipping method:", newShipping); const response = await addShippingMethod( newShipping, @@ -196,10 +196,10 @@ export default function ShippingPage() { // Close modal and reset form before refreshing to avoid UI delays setModalOpen(false); setNewShipping({ name: "", price: 0 }); - + // Refresh the list after adding refreshShippingMethods(); - + console.log("Shipping method added successfully"); } catch (error) { console.error("Error adding shipping method:", error); @@ -218,12 +218,12 @@ export default function ShippingPage() { .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; - + if (!authToken) { console.error("No auth token found"); return; } - + await updateShippingMethod( newShipping._id, newShipping, @@ -234,10 +234,10 @@ export default function ShippingPage() { setModalOpen(false); setNewShipping({ name: "", price: 0 }); setEditing(false); - + // Refresh the list after updating refreshShippingMethods(); - + console.log("Shipping method updated successfully"); } catch (error) { console.error("Error updating shipping method:", error); diff --git a/app/dashboard/stock/page.tsx b/app/dashboard/stock/page.tsx index 9eb0075..1abeb46 100644 --- a/app/dashboard/stock/page.tsx +++ b/app/dashboard/stock/page.tsx @@ -7,18 +7,22 @@ import { Button } from "@/components/ui/button"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuLabel, + DropdownMenuSeparator } from "@/components/ui/dropdown-menu"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { +import { AlertDialog, AlertDialogAction, AlertDialogCancel, @@ -30,12 +34,13 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Product } from "@/models/products"; -import { Package, RefreshCw, ChevronDown, CheckSquare, XSquare, Boxes, Download, Calendar } from "lucide-react"; +import { Package, RefreshCw, ChevronDown, CheckSquare, XSquare, Boxes, Download, Calendar, Search, Filter, Save, X, Edit2 } from "lucide-react"; import { clientFetch } from "@/lib/api"; import { toast } from "sonner"; import { DatePicker, DateRangePicker, DateRangeDisplay, MonthPicker } from "@/components/ui/date-picker"; import { DateRange } from "react-day-picker"; import { addDays, startOfDay, endOfDay, format, isSameDay } from "date-fns"; +import { motion, AnimatePresence } from "framer-motion"; interface StockData { currentStock: number; @@ -55,7 +60,7 @@ export default function StockManagementPage() { const [selectedProducts, setSelectedProducts] = useState([]); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | null>(null); - + // Export state const [exportDate, setExportDate] = useState(new Date().toISOString().split('T')[0]); const [exportDateRange, setExportDateRange] = useState({ @@ -82,7 +87,7 @@ export default function StockManagementPage() { const response = await clientFetch('api/products'); const fetchedProducts = response || []; setProducts(fetchedProducts); - + // Initialize stock values const initialStockValues: Record = {}; fetchedProducts.forEach((product: Product) => { @@ -91,7 +96,7 @@ export default function StockManagementPage() { } }); setStockValues(initialStockValues); - + setLoading(false); } catch (error) { console.error("Error fetching products:", error); @@ -114,7 +119,7 @@ export default function StockManagementPage() { try { const newStockValue = stockValues[product._id] || 0; - + const stockData: StockData = { currentStock: newStockValue, stockTracking: product.stockTracking || false, @@ -165,14 +170,14 @@ export default function StockManagementPage() { try { // Toggle the stock tracking status const newTrackingStatus = !product.stockTracking; - + // For enabling tracking, we need to ensure there's a stock value const stockData: StockData = { stockTracking: newTrackingStatus, currentStock: product.currentStock || 0, lowStockThreshold: product.lowStockThreshold || 10, }; - + // Update stock tracking status await clientFetch(`api/stock/${product._id}`, { method: 'PUT', @@ -212,7 +217,7 @@ export default function StockManagementPage() { try { const productsToUpdate = products.filter(p => selectedProducts.includes(p._id || '')); - + await Promise.all(productsToUpdate.map(async (product) => { if (!product._id) return; @@ -254,7 +259,7 @@ export default function StockManagementPage() { }; const toggleSelectProduct = (productId: string) => { - setSelectedProducts(prev => + setSelectedProducts(prev => prev.includes(productId) ? prev.filter(id => id !== productId) : [...prev, productId] @@ -262,7 +267,7 @@ export default function StockManagementPage() { }; const toggleSelectAll = () => { - setSelectedProducts(prev => + setSelectedProducts(prev => prev.length === products.length ? [] : products.map(p => p._id || '') @@ -280,7 +285,7 @@ export default function StockManagementPage() { response = await clientFetch(`/api/analytics/daily-stock-report?date=${exportDate}`); filename = `daily-stock-report-${exportDate}.csv`; break; - + case 'weekly': if (!exportDateRange?.from) { toast.error('Please select a date range for weekly report'); @@ -290,14 +295,14 @@ export default function StockManagementPage() { response = await clientFetch(`/api/analytics/weekly-stock-report?weekStart=${weekStart}`); filename = `weekly-stock-report-${weekStart}.csv`; break; - + case 'monthly': const year = selectedMonth.getFullYear(); const month = selectedMonth.getMonth() + 1; response = await clientFetch(`/api/analytics/monthly-stock-report?year=${year}&month=${month}`); filename = `monthly-stock-report-${year}-${month.toString().padStart(2, '0')}.csv`; break; - + case 'custom': if (!exportDateRange?.from || !exportDateRange?.to) { toast.error('Please select a date range for custom report'); @@ -308,12 +313,12 @@ export default function StockManagementPage() { response = await clientFetch(`/api/analytics/daily-stock-report?startDate=${startDate}&endDate=${endDate}`); filename = `custom-stock-report-${startDate}-to-${endDate}.csv`; break; - + default: toast.error('Invalid report type'); return; } - + if (!response || !response.products) { throw new Error('No data received from server'); } @@ -348,19 +353,19 @@ export default function StockManagementPage() { const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); - + link.setAttribute('href', url); link.setAttribute('download', filename); link.style.visibility = 'hidden'; - + document.body.appendChild(link); link.click(); document.body.removeChild(link); const periodText = reportType === 'daily' ? exportDate : - reportType === 'weekly' ? `week starting ${format(exportDateRange?.from || new Date(), 'MMM dd')}` : - reportType === 'monthly' ? `${response.monthName || 'current month'}` : - `${format(exportDateRange?.from || new Date(), 'MMM dd')} to ${format(exportDateRange?.to || new Date(), 'MMM dd')}`; + reportType === 'weekly' ? `week starting ${format(exportDateRange?.from || new Date(), 'MMM dd')}` : + reportType === 'monthly' ? `${response.monthName || 'current month'}` : + `${format(exportDateRange?.from || new Date(), 'MMM dd')} to ${format(exportDateRange?.to || new Date(), 'MMM dd')}`; toast.success(`${reportType.charAt(0).toUpperCase() + reportType.slice(1)} stock report for ${periodText} exported successfully`); } catch (error) { @@ -379,9 +384,29 @@ export default function StockManagementPage() { return 'In stock'; }; + const getStatusBadgeVariant = (status: string) => { + switch (status) { + case 'Out of stock': return 'destructive'; + case 'Low stock': return 'warning'; // Custom variant or use secondary/outline + case 'In stock': return 'default'; // often maps to primary which might be blue/black + default: return 'secondary'; + } + }; + + // Helper for badging - if your Badge component doesn't support 'warning' directly, use className overrides + const StatusBadge = ({ status }: { status: string }) => { + let styles = "font-medium border-transparent shadow-none"; + if (status === 'Out of stock') styles += " bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400"; + else if (status === 'Low stock') styles += " bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400"; + else if (status === 'In stock') styles += " bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-400"; + else styles += " bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400"; + + return {status}; + }; + const filteredProducts = products.filter(product => { if (!searchTerm) return true; - + const searchLower = searchTerm.toLowerCase(); return ( product.name.toLowerCase().includes(searchLower) || @@ -392,31 +417,39 @@ export default function StockManagementPage() { return ( -
-
-

- - Stock Management -

-
- setSearchTerm(e.target.value)} - /> - - {/* Report Type Selector */} +
+
+
+

+ + Stock Management +

+

+ Track inventory levels and manage stock status +

+
+ +
+
+ + setSearchTerm(e.target.value)} + /> +
+ - - + + Filter Reports + setReportType('daily')}> Daily Report @@ -433,51 +466,53 @@ export default function StockManagementPage() { {/* Date Selection based on report type */} - {reportType === 'daily' && ( - setExportDate(date ? date.toISOString().split('T')[0] : '')} - placeholder="Select export date" - className="w-auto" - /> - )} +
+ {reportType === 'daily' && ( + setExportDate(date ? date.toISOString().split('T')[0] : '')} + placeholder="Select export date" + className="w-auto border-border/50 bg-background/50" + /> + )} - {(reportType === 'weekly' || reportType === 'custom') && ( - - )} + {(reportType === 'weekly' || reportType === 'custom') && ( + + )} - {reportType === 'monthly' && ( - setSelectedMonth(date || new Date())} - placeholder="Select month" - className="w-auto" - /> - )} + {reportType === 'monthly' && ( + setSelectedMonth(date || new Date())} + placeholder="Select month" + className="w-auto border-border/50 bg-background/50" + /> + )} +
{selectedProducts.length > 0 && ( -
-
- - - - - - - Product - Stock Status - Current Stock - Track Stock - Actions - - - - {loading ? ( - - - - Loading products... - - - ) : filteredProducts.length === 0 ? ( - - - No products found - - - ) : ( - filteredProducts.map((product) => ( - - + + +
+ Inventory Data + Manage stock levels and tracking for {products.length} products +
+
+ {filteredProducts.length} items +
+
+ +
+
+ + + toggleSelectProduct(product._id || '')} - className="rounded border-gray-300" + checked={selectedProducts.length === products.length && products.length > 0} + onChange={toggleSelectAll} + className="rounded border-gray-300 dark:border-zinc-700 bg-background focus:ring-primary/20" /> - - {product.name} - {getStockStatus(product)} - - {editingStock[product._id || ''] ? ( -
- handleStockChange(product._id || '', parseInt(e.target.value) || 0)} - className="w-24" - /> - -
- ) : ( - {product.currentStock || 0} - )} -
- - handleToggleStockTracking(product)} - /> - - - {!editingStock[product._id || ''] && ( - - )} - +
+ Product + Status + Current Stock + Tracking + Actions
- )) - )} - -
-
+ + + + {loading ? ( + + +
+ +

Loading products...

+
+
+
+ ) : filteredProducts.length === 0 ? ( + + +
+ +

No products found matching your search

+
+
+
+ ) : ( + filteredProducts.map((product, index) => ( + + + toggleSelectProduct(product._id || '')} + className="rounded border-gray-300 dark:border-zinc-700 bg-background focus:ring-primary/20" + /> + + {product.name} + + + + + {editingStock[product._id || ''] ? ( +
+ handleStockChange(product._id || '', parseInt(e.target.value) || 0)} + className="w-20 h-8 font-mono bg-background" + /> +
+ ) : ( + {product.currentStock || 0} + )} +
+ + handleToggleStockTracking(product)} + className="data-[state=checked]:bg-primary" + /> + + +
+ {editingStock[product._id || ''] ? ( + <> + + + + ) : ( + + )} +
+
+
+ )) + )} +
+
+ +
+ +
@@ -589,12 +674,14 @@ export default function StockManagementPage() { Confirm Bulk Action - Are you sure you want to {bulkAction} stock tracking for {selectedProducts.length} selected products? + Are you sure you want to {bulkAction} stock tracking for {selectedProducts.length} selected products? setBulkAction(null)}>Cancel - Continue + + Continue + diff --git a/components/tables/shipping-table.tsx b/components/tables/shipping-table.tsx index 5b21336..63acdcf 100644 --- a/components/tables/shipping-table.tsx +++ b/components/tables/shipping-table.tsx @@ -8,8 +8,9 @@ import { TableRow, } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; -import { Edit, Trash } from "lucide-react"; - +import { Edit, Trash, Truck, PackageX } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { motion, AnimatePresence } from "framer-motion"; import { ShippingMethod } from "@/lib/types"; interface ShippingTableProps { @@ -26,61 +27,90 @@ export const ShippingTable: React.FC = ({ onDeleteShipping, }) => { return ( -
- - - - Name - Price - Actions - - - - {loading ? ( - - - - - - ) : shippingMethods.length > 0 ? ( - shippingMethods.map((method) => ( - - {method.name} - £{method.price} - -
- - -
-
+ + + + + Available Methods + + + +
+
+ + + Method Name + Price + Actions - )) - ) : ( - - - No shipping methods found - - - )} - -
-
+ + + + {loading ? ( + + +
+ + Loading methods... +
+
+
+ ) : shippingMethods.length > 0 ? ( + shippingMethods.map((method, index) => ( + + +
+
+ +
+ {method.name} +
+
+ £{method.price} + +
+ + +
+
+
+ )) + ) : ( + + +
+ +

No shipping methods found

+
+
+
+ )} +
+
+ +
+ + ); };