Skip to main content

Overview

The KAIU Admin Dashboard (src/pages/admin/AdminDashboard.tsx) provides a comprehensive operations interface for managing orders, inventory, and viewing real-time business metrics. Access is secured with JWT authentication.

Authentication

Login System

1

Login Page

Admin navigates to /admin/login and enters credentials.
2

Token Generation

Backend validates credentials and issues JWT token:
const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '24h' });
3

Session Storage

Frontend stores token and username:
sessionStorage.setItem('kaiu_admin_token', token);
sessionStorage.setItem('kaiu_admin_user', username);
4

Protected Routes

All admin API calls include Authorization header:
const headers = { 'Authorization': `Bearer ${token}` };
If token is missing or invalid (401 response), dashboard automatically redirects to login page.

Dashboard Layout

The dashboard uses a tabbed interface for different operational areas:
Overview TabKey performance indicators and charts for business monitoring.

Executive Overview

KPI Cards

Four key metrics displayed at top of overview tab:

Ventas Totales

Total Sales (Current Month)
<div className="text-2xl font-bold">
  ${stats.kpi.totalSales.toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
  +20.1% vs mes anterior
</p>
Shows revenue for current month with month-over-month comparison.

Ticket Promedio

Average Order Value
<div className="text-2xl font-bold">
  ${Math.round(stats.kpi.averageTicket).toLocaleString()}
</div>
Calculated as: totalSales / totalOrders

Órdenes Activas

Pending Orders
<div className="text-2xl font-bold">
  {stats.kpi.pendingOrders}
</div>
<p className="text-xs text-muted-foreground">
  Pendientes de despacho
</p>
Count of orders waiting to be fulfilled.

Total Pedidos

Total Orders
<div className="text-2xl font-bold">
  {stats.kpi.totalOrders}
</div>
Lifetime order count.
Permission-based Display: Financial data (sales, totals) only visible to user “kaiu”. Other admin users see order counts but not revenue.
const currentUser = sessionStorage.getItem('kaiu_admin_user') || 'kaiu';
const showFinancials = currentUser.toLowerCase() === 'kaiu';

Sales Chart

Daily Sales Visualization (Last 30 days)
<ResponsiveContainer width="100%" height={300}>
  <BarChart data={stats.charts.sales}>
    <CartesianGrid strokeDasharray="3 3" vertical={false} />
    <XAxis 
      dataKey="date"
      tickFormatter={(val) => new Date(val).toLocaleDateString(
        undefined, {day: '2-digit', month: '2-digit'}
      )}
    />
    <YAxis tickFormatter={(val) => `$${(val/1000)}k`} />
    <Tooltip formatter={(val) => [`$${val.toLocaleString()}`, 'Ventas']} />
    <Bar dataKey="total" fill="#4F6D7A" radius={[4, 4, 0, 0]} />
  </BarChart>
</ResponsiveContainer>
Data structure:
[
  { "date": "2024-01-01", "total": 450000 },
  { "date": "2024-01-02", "total": 320000 },
  ...
]

Order Status Distribution

Pie Chart showing current order breakdown:
<PieChart>
  <Pie
    data={stats.charts.status}
    cx="50%"
    cy="50%"
    innerRadius={60}
    outerRadius={80}
    paddingAngle={5}
    dataKey="value"
  >
    {stats.charts.status.map((entry, index) => (
      <Cell key={index} fill={COLORS[index % COLORS.length]} />
    ))}
  </Pie>
  <Tooltip />
  <Legend />
</PieChart>
Data structure:
[
  { "name": "Pendientes", "value": 15 },
  { "name": "En Tránsito", "value": 8 },
  { "name": "Entregados", "value": 127 },
  { "name": "Cancelados", "value": 3 }
]

Shipment Management

Order Lifecycle States

Orders flow through multiple states, organized into filters:
Orders needing shipping label generationStates included:
  • PENDING - Just created
  • APPROVED - Payment confirmed (online)
  • PREPARING - Being packed
  • PROCESSING - In process
  • CONFIRMED - Confirmed by customer
Action: Generate shipping label
['PENDING', 'APPROVED', 'PREPARING', 'PROCESSING', 'CONFIRMED']
  .includes(order.status)
Orders with label generated, waiting for carrier pickup requestState: READY_TO_SHIPActions:
  • Reprint label
  • Request pickup from carrier
<Button onClick={() => handleRequestPickup(orderId)}>
  <Truck className="mr-2 h-3 w-3" />
  Solicitar Recogida
</Button>
Orders in carrier’s handsStates included:
  • PICKUP_REQUESTED - Waiting for carrier to collect
  • SHIPPED - In transit
  • NOVEDAD - Delivery issue/incident
  • DISPATCHED - Out for delivery
Action: Reprint label (if needed)
Successfully completed ordersState: DELIVEREDRead-only view for confirmation.
Failed or returned ordersStates included:
  • CANCELLED
  • REJECTED
  • RETURNED
Read-only view for records.

Order Table

Columns displayed:
ColumnDescriptionSource
PINReadable order IDorder.pin or order.readableId
FechaCreation datenew Date(order.created_at).toLocaleDateString()
ClienteCustomer name + addressorder.shipping_info.first_name, order.shipping_info.last_name
ProductosLine items with quantitiesorder.line_items.map(...)
Total*Order total in COP$${order.total.toLocaleString()}
RastreoCarrier + tracking numberorder.shipments[0]
PagoPayment method badgeCOD or Online
AccionesAction buttonsStatus-dependent
*Total column only visible to users with financial permissions.
Sorting functionality:
const handleSort = (key: string) => {
  let direction: 'asc' | 'desc' = 'asc';
  if (sortConfig?.key === key && sortConfig.direction === 'asc') {
    direction = 'desc';
  }
  setSortConfig({ key, direction });
};
Click column headers to sort by:
  • PIN
  • Date (default: newest first)
  • Client name
  • Product count
  • Total amount

Payment Filter

Additional filter to segment by payment type:
Show all orders regardless of payment method

Action Buttons

Generate/Print Label

Creates shipping label with carrier
const handleGenerateLabel = async (orderId: string) => {
  setGeneratingLabel(orderId);
  
  const res = await fetch('/api/admin/generate-label', {
    method: 'POST',
    headers: { 
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ orderIds: [orderId] })
  });
  
  const data = await res.json();
  
  if (data.data) {
    // Open PDF in new window
    window.open(data.data, '_blank');
    toast({ title: "Guía Generada" });
    fetchData(); // Refresh order list
  }
};
States:
  • Primary button for PENDING/PROCESSING orders
  • Outline button (reprint) for READY_TO_SHIP/SHIPPED orders
  • Shows spinner while generating

Request Pickup

Notifies carrier to collect package
const handleRequestPickup = async (orderId: string) => {
  const res = await fetch('/api/admin/request-pickup', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ orderIds: [orderId] })
  });
  
  if (res.ok) {
    toast({ 
      title: "Recogida Solicitada",
      description: "La transportadora ha sido notificada."
    });
  }
};
Shown only for: READY_TO_SHIP statusUpdates order status to PICKUP_REQUESTED

Sync Shipments

Real-time Status Updates Manually sync order statuses with carrier:
const handleSyncShipments = async () => {
  toast({ title: "Sincronizando...", description: "Consultando estados en Venndelo..." });
  
  const res = await fetch('/api/admin/sync-shipments', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}` }
  });
  
  const data = await res.json();
  
  if (data.success) {
    toast({
      title: "Sincronización Completa",
      description: `Procesadas: ${data.processed}. Actualizadas: ${data.updated}.`
    });
    fetchData(); // Refresh order list
  }
};
Sync button queries the logistics provider’s API for latest tracking info and updates database accordingly.

Inventory Management

See detailed documentation: Inventory Management Brief overview:
  • View all products with stock levels
  • Edit prices, descriptions, and inventory
  • Create new products
  • Toggle product visibility (active/inactive)
  • Upload product images

Status Badge System

Visual indicators for order states:
const getStatusBadge = (status: string) => {
  const map: Record<string, string> = {
    'PENDING': 'bg-yellow-500',
    'APPROVED': 'bg-green-500',
    'READY': 'bg-blue-600',
    'SHIPPED': 'bg-indigo-500',
    'DELIVERED': 'bg-green-700',
    'CANCELLED': 'bg-red-500',
    'READY_TO_SHIP': 'bg-yellow-500',
    'PICKUP_REQUESTED': 'bg-orange-500'
  };
  
  const labelMap: Record<string, string> = {
    'PENDING': 'NUEVO',
    'READY': 'ALISTADO',
    'SHIPPED': 'ENVIADO',
    'PREPARING': 'PREPARACIÓN',
    'READY_TO_SHIP': 'POR DESPACHAR',
    'PICKUP_REQUESTED': 'ESPERANDO RECOGIDA'
  };
  
  return (
    <Badge className={`${map[status] || 'bg-gray-500'} text-white`}>
      {labelMap[status] || status}
    </Badge>
  );
};

Yellow

Pending, Ready to Ship

Orange

Pickup Requested

Blue/Indigo

Ready, Shipped

Green

Approved, Delivered

Red

Cancelled, Rejected

Gray

Unknown/Other

Data Refresh

Dashboard automatically fetches latest data on load and provides manual refresh:
const fetchData = async () => {
  setLoading(true);
  try {
    const headers = { 'Authorization': `Bearer ${token}` };
    
    // Parallel fetch for performance
    const [ordersRes, statsRes] = await Promise.all([
      fetch('/api/admin/orders', { headers }),
      fetch('/api/admin/dashboard-stats', { headers })
    ]);
    
    if (ordersRes.status === 401 || statsRes.status === 401) {
      navigate('/admin/login');
      return;
    }
    
    const ordersData = await ordersRes.json();
    setOrders(ordersData.items || ordersData.results || []);
    
    if (statsRes.ok) {
      const statsData = await statsRes.json();
      setStats(statsData);
    }
  } catch (error) {
    toast({ 
      title: "Error Crítico",
      description: error.message,
      variant: "destructive"
    });
  } finally {
    setLoading(false);
  }
};

useEffect(() => {
  if (!token) {
    navigate('/admin/login');
    return;
  }
  fetchData();
}, [token, navigate]);
Refresh button:
<Button variant="outline" onClick={fetchData} disabled={loading}>
  <RefreshCw className={loading ? 'animate-spin' : ''} />
  Actualizar
</Button>

Responsive Design

Dashboard adapts to different screen sizes:
  • Desktop: Full table view with all columns
  • Tablet: Horizontal scroll for table
  • Mobile: Stacked card layout (via overflow-x-auto)
<CardContent className="p-0 overflow-x-auto">
  <Table className="min-w-[1000px]">
    {/* Table content */}
  </Table>
</CardContent>

API Endpoints Used

EndpointMethodPurpose
/api/admin/loginPOSTAuthenticate admin
/api/admin/ordersGETFetch order list
/api/admin/dashboard-statsGETGet KPIs and chart data
/api/admin/generate-labelPOSTCreate shipping label
/api/admin/request-pickupPOSTRequest carrier pickup
/api/admin/sync-shipmentsPOSTSync tracking statuses
/api/admin/inventoryGET/POST/PUTManage products

Order Management

Detailed order fulfillment workflows

Inventory

Product catalog and stock management

E-commerce

Customer-facing store features

Build docs developers (and LLMs) love