- Express + EJS server-rendered app - Supabase PostgreSQL database - Auth: username/password with bcrypt - Dashboard with business stats - Load CRUD with filters - WhatsApp message parser - Payment tracking - Shipper & vehicle management - Reports (monthly, top shippers, routes) - Government-app aesthetic (tricolor theme) - Dark mode support - Docker + Coolify deployment ready - Seed data from existing business ledger (88 loads, 41 shippers, 70 vehicles)
132 lines
4.2 KiB
Text
132 lines
4.2 KiB
Text
<!-- Dashboard Page -->
|
|
<%- include('../partials/header', { activeMenu: 'dashboard' }) %>
|
|
|
|
<div class="page-header">
|
|
<div>
|
|
<h1 class="page-title">📊 Dashboard</h1>
|
|
<p class="page-subtitle">Welcome back, <%= user.username %>! Here's your business overview.</p>
|
|
</div>
|
|
<a href="/loads/new" class="btn btn-primary">+ New Load</a>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card stat-primary">
|
|
<div class="stat-icon">💰</div>
|
|
<div class="stat-info">
|
|
<span class="stat-value"><%= formatINR(stats.totalFreight) %></span>
|
|
<span class="stat-label">Total Freight</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card stat-success">
|
|
<div class="stat-icon">✅</div>
|
|
<div class="stat-info">
|
|
<span class="stat-value"><%= formatINR(stats.totalCommission) %></span>
|
|
<span class="stat-label">Commission Earned</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card stat-warning">
|
|
<div class="stat-icon">⏰</div>
|
|
<div class="stat-info">
|
|
<span class="stat-value"><%= formatINR(stats.totalPendingShipper) %></span>
|
|
<span class="stat-label">Pending Collection</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card stat-info">
|
|
<div class="stat-icon">🚚</div>
|
|
<div class="stat-info">
|
|
<span class="stat-value"><%= stats.totalLoads %></span>
|
|
<span class="stat-label">Total Loads (<%= stats.settledCount %> settled)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-2">
|
|
<!-- Recent Loads -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Recent Loads</h3>
|
|
<a href="/loads" class="btn btn-sm btn-outline">View All</a>
|
|
</div>
|
|
<div class="card-body">
|
|
<% if (recentLoads.length === 0) { %>
|
|
<p class="empty-state">No loads yet. <a href="/loads/new">Add your first load</a></p>
|
|
<% } else { %>
|
|
<div class="table-responsive">
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Route</th>
|
|
<th>Freight</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% for (const load of recentLoads) { %>
|
|
<tr>
|
|
<td><%= load.date || '—' %></td>
|
|
<td><%= load.from_city || '?' %> → <%= load.to_city || '?' %></td>
|
|
<td><%= formatINR(load.freight_charged) %></td>
|
|
<td><span class="badge badge-<%= getStatusColor(load.status) %>"><%= load.status %></span></td>
|
|
</tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pending Collections -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">⏰ Pending Collections</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<% if (pendingCollection.length === 0) { %>
|
|
<p class="empty-state">No pending collections. Great job!</p>
|
|
<% } else { %>
|
|
<div class="table-responsive">
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Shipper</th>
|
|
<th>Route</th>
|
|
<th>Pending</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% for (const load of pendingCollection) { %>
|
|
<tr>
|
|
<td><%= load.shipper_id || '—' %></td>
|
|
<td><%= load.from_city || '?' %> → <%= load.to_city || '?' %></td>
|
|
<td class="text-danger"><%= formatINR(load.pending_from_shipper) %></td>
|
|
</tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Breakdown -->
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Status Breakdown</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="status-grid">
|
|
<% for (const [status, count] of Object.entries(statusCounts)) { %>
|
|
<div class="status-item">
|
|
<span class="badge badge-<%= getStatusColor(status) %>"><%= count %></span>
|
|
<span class="status-label"><%= status %></span>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<%- include('../partials/footer') %>
|