429 lines
21 KiB
HTML
429 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Admin Dashboard - Multi-Tenant Ecommerce Platform</title>
|
||
|
||
<!-- Font Awesome -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
|
||
<!-- Styles -->
|
||
<link rel="stylesheet" href="/static/css/shared/base.css">
|
||
<link rel="stylesheet" href="/static/css/shared/components.css">
|
||
<link rel="stylesheet" href="/static/css/shared/modals.css">
|
||
<link rel="stylesheet" href="/static/css/admin/admin.css">
|
||
|
||
<style>
|
||
[x-cloak] { display: none !important; }
|
||
</style>
|
||
</head>
|
||
<body x-data="adminDashboard()" x-init="init()" x-cloak>
|
||
|
||
<!-- Admin Header (Injected from template) -->
|
||
<div x-html="adminLayoutTemplates.header()"></div>
|
||
|
||
<!-- Admin Sidebar (Injected from template) -->
|
||
<div x-html="adminLayoutTemplates.sidebar()"></div>
|
||
|
||
<!-- Main Content -->
|
||
<main class="admin-content">
|
||
<!-- Dashboard View -->
|
||
<div x-show="currentSection === 'dashboard'">
|
||
<!-- Stats Grid -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<div class="stat-title">Total Vendors</div>
|
||
<div class="stat-icon">🏪</div>
|
||
</div>
|
||
<div class="stat-value" x-text="stats.vendors?.total_vendors || 0"></div>
|
||
<div class="stat-subtitle">
|
||
<span x-text="stats.vendors?.active_vendors || 0"></span> active
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<div class="stat-title">Total Users</div>
|
||
<div class="stat-icon">👥</div>
|
||
</div>
|
||
<div class="stat-value" x-text="stats.users?.total_users || 0"></div>
|
||
<div class="stat-subtitle">
|
||
<span x-text="stats.users?.active_users || 0"></span> active
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<div class="stat-title">Verified Vendors</div>
|
||
<div class="stat-icon">✅</div>
|
||
</div>
|
||
<div class="stat-value" x-text="stats.vendors?.verified_vendors || 0"></div>
|
||
<div class="stat-subtitle">
|
||
<span x-text="Math.round(stats.vendors?.verification_rate || 0)"></span>% verification rate
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<div class="stat-title">Import Jobs</div>
|
||
<div class="stat-icon">📦</div>
|
||
</div>
|
||
<div class="stat-value" x-text="stats.imports?.total_imports || 0"></div>
|
||
<div class="stat-subtitle">
|
||
<span x-text="stats.imports?.completed_imports || 0"></span> completed
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent Vendors -->
|
||
<div class="content-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">Recent Vendors</h2>
|
||
<button class="btn-primary" @click="showSection('vendors')">View All</button>
|
||
</div>
|
||
|
||
<div x-show="loading && !recentVendors.length" class="loading">
|
||
<span class="loading-spinner loading-spinner-lg"></span>
|
||
<p class="loading-text">Loading recent vendors...</p>
|
||
</div>
|
||
|
||
<template x-if="!loading && recentVendors.length === 0">
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">🏪</div>
|
||
<h3>No Vendors Yet</h3>
|
||
<p>Create your first vendor to get started</p>
|
||
<button class="btn-primary mt-3" onclick="window.location.href='/admin/vendors.html'">
|
||
Create Vendor
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<template x-if="recentVendors.length > 0">
|
||
<div class="table-responsive">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Vendor Code</th>
|
||
<th>Name</th>
|
||
<th>Subdomain</th>
|
||
<th>Status</th>
|
||
<th>Created</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<template x-for="vendor in recentVendors" :key="vendor.id">
|
||
<tr>
|
||
<td><strong x-text="vendor.vendor_code"></strong></td>
|
||
<td x-text="vendor.name"></td>
|
||
<td x-text="vendor.subdomain"></td>
|
||
<td>
|
||
<span class="badge"
|
||
:class="vendor.is_verified ? 'badge-success' : 'badge-warning'"
|
||
x-text="vendor.is_verified ? 'Verified' : 'Pending'"></span>
|
||
<span class="badge"
|
||
:class="vendor.is_active ? 'badge-success' : 'badge-danger'"
|
||
x-text="vendor.is_active ? 'Active' : 'Inactive'"></span>
|
||
</td>
|
||
<td x-text="formatDate(vendor.created_at)"></td>
|
||
</tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Recent Import Jobs -->
|
||
<div class="content-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">Recent Import Jobs</h2>
|
||
<button class="btn-primary" @click="showSection('imports')">View All</button>
|
||
</div>
|
||
|
||
<div x-show="loading && !recentImports.length" class="loading">
|
||
<span class="loading-spinner loading-spinner-lg"></span>
|
||
<p class="loading-text">Loading recent imports...</p>
|
||
</div>
|
||
|
||
<template x-if="!loading && recentImports.length === 0">
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">📦</div>
|
||
<h3>No Import Jobs Yet</h3>
|
||
<p>Import jobs will appear here once vendors start importing products</p>
|
||
</div>
|
||
</template>
|
||
|
||
<template x-if="recentImports.length > 0">
|
||
<div class="table-responsive">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Marketplace</th>
|
||
<th>Vendor</th>
|
||
<th>Status</th>
|
||
<th>Processed</th>
|
||
<th>Created</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<template x-for="job in recentImports" :key="job.id">
|
||
<tr>
|
||
<td><strong x-text="'#' + job.id"></strong></td>
|
||
<td x-text="job.marketplace"></td>
|
||
<td x-text="job.vendor_name || '-'"></td>
|
||
<td>
|
||
<span class="badge"
|
||
:class="{
|
||
'badge-success': job.status === 'completed',
|
||
'badge-danger': job.status === 'failed',
|
||
'badge-warning': job.status !== 'completed' && job.status !== 'failed'
|
||
}"
|
||
x-text="job.status === 'completed' ? 'Completed' :
|
||
job.status === 'failed' ? 'Failed' : 'Processing'"></span>
|
||
</td>
|
||
<td x-text="job.total_processed || 0"></td>
|
||
<td x-text="formatDate(job.created_at)"></td>
|
||
</tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Vendors View -->
|
||
<div x-show="currentSection === 'vendors'">
|
||
<div class="content-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">Vendor Management</h2>
|
||
<a href="/admin/vendors.html" class="btn-primary">
|
||
➕ Create New Vendor
|
||
</a>
|
||
</div>
|
||
|
||
<div x-show="loading" class="loading">
|
||
<span class="loading-spinner loading-spinner-lg"></span>
|
||
<p class="loading-text">Loading vendors...</p>
|
||
</div>
|
||
|
||
<template x-if="!loading && vendors.length === 0">
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">🏪</div>
|
||
<h3>No Vendors Found</h3>
|
||
<p>Get started by creating your first vendor</p>
|
||
<a href="/admin/vendors.html" class="btn-primary mt-3">
|
||
Create First Vendor
|
||
</a>
|
||
</div>
|
||
</template>
|
||
|
||
<template x-if="vendors.length > 0">
|
||
<div class="table-responsive">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Vendor Code</th>
|
||
<th>Name</th>
|
||
<th>Subdomain</th>
|
||
<th>Email</th>
|
||
<th>Status</th>
|
||
<th>Created</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<template x-for="vendor in vendors" :key="vendor.id">
|
||
<tr>
|
||
<td x-text="vendor.id"></td>
|
||
<td><strong x-text="vendor.vendor_code"></strong></td>
|
||
<td x-text="vendor.name"></td>
|
||
<td x-text="vendor.subdomain"></td>
|
||
<td x-text="vendor.business_email || vendor.contact_email || '-'"></td>
|
||
<td>
|
||
<span class="badge"
|
||
:class="vendor.is_verified ? 'badge-success' : 'badge-warning'"
|
||
x-text="vendor.is_verified ? 'Verified' : 'Pending'"></span>
|
||
<span class="badge"
|
||
:class="vendor.is_active ? 'badge-success' : 'badge-danger'"
|
||
x-text="vendor.is_active ? 'Active' : 'Inactive'"></span>
|
||
</td>
|
||
<td x-text="formatDate(vendor.created_at)"></td>
|
||
<td>
|
||
<a :href="`/admin/vendor-edit.html?id=${vendor.id}`"
|
||
class="btn btn-sm btn-primary">
|
||
✏️ Edit
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Users View -->
|
||
<div x-show="currentSection === 'users'">
|
||
<div class="content-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">User Management</h2>
|
||
</div>
|
||
|
||
<div x-show="loading" class="loading">
|
||
<span class="loading-spinner loading-spinner-lg"></span>
|
||
<p class="loading-text">Loading users...</p>
|
||
</div>
|
||
|
||
<template x-if="!loading && users.length === 0">
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">👥</div>
|
||
<h3>No Users Found</h3>
|
||
<p>Users will appear here when vendors are created</p>
|
||
</div>
|
||
</template>
|
||
|
||
<template x-if="users.length > 0">
|
||
<div class="table-responsive">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Username</th>
|
||
<th>Email</th>
|
||
<th>Role</th>
|
||
<th>Status</th>
|
||
<th>Created</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<template x-for="user in users" :key="user.id">
|
||
<tr>
|
||
<td x-text="user.id"></td>
|
||
<td><strong x-text="user.username"></strong></td>
|
||
<td x-text="user.email"></td>
|
||
<td>
|
||
<span class="badge badge-primary" x-text="user.role"></span>
|
||
</td>
|
||
<td>
|
||
<span class="badge"
|
||
:class="user.is_active ? 'badge-success' : 'badge-danger'"
|
||
x-text="user.is_active ? 'Active' : 'Inactive'"></span>
|
||
</td>
|
||
<td x-text="formatDate(user.created_at)"></td>
|
||
</tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Imports View -->
|
||
<div x-show="currentSection === 'imports'">
|
||
<div class="content-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">Import Jobs</h2>
|
||
</div>
|
||
|
||
<div x-show="loading" class="loading">
|
||
<span class="loading-spinner loading-spinner-lg"></span>
|
||
<p class="loading-text">Loading import jobs...</p>
|
||
</div>
|
||
|
||
<template x-if="!loading && imports.length === 0">
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">📦</div>
|
||
<h3>No Import Jobs Found</h3>
|
||
<p>Marketplace import jobs will appear here</p>
|
||
</div>
|
||
</template>
|
||
|
||
<template x-if="imports.length > 0">
|
||
<div class="table-responsive">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Job ID</th>
|
||
<th>Marketplace</th>
|
||
<th>Vendor</th>
|
||
<th>Status</th>
|
||
<th>Processed</th>
|
||
<th>Errors</th>
|
||
<th>Created</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<template x-for="job in imports" :key="job.id">
|
||
<tr>
|
||
<td><strong x-text="'#' + (job.job_id || job.id)"></strong></td>
|
||
<td x-text="job.marketplace"></td>
|
||
<td x-text="job.vendor_name || '-'"></td>
|
||
<td>
|
||
<span class="badge"
|
||
:class="{
|
||
'badge-success': job.status === 'completed',
|
||
'badge-danger': job.status === 'failed',
|
||
'badge-warning': job.status !== 'completed' && job.status !== 'failed'
|
||
}"
|
||
x-text="job.status === 'completed' ? 'Completed' :
|
||
job.status === 'failed' ? 'Failed' : 'Processing'"></span>
|
||
</td>
|
||
<td x-text="job.total_processed || 0"></td>
|
||
<td>
|
||
<span x-text="job.error_count || 0"
|
||
:class="{ 'text-danger': (job.error_count || 0) > 0 }"></span>
|
||
</td>
|
||
<td x-text="formatDate(job.created_at)"></td>
|
||
</tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Universal Modals (Injected from shared templates) -->
|
||
<div x-html="modalTemplates.confirmModal()"></div>
|
||
<div x-html="modalTemplates.successModal()"></div>
|
||
<div x-html="modalTemplates.errorModal()"></div>
|
||
<div x-html="modalTemplates.loadingOverlay()"></div>
|
||
|
||
<!-- Scripts -->
|
||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||
<script src="/static/js/shared/api-client.js"></script>
|
||
<script src="/static/js/shared/modal-templates.js"></script>
|
||
<script src="/static/js/shared/alpine-components.js"></script>
|
||
<script src="/static/js/shared/modal-system.js"></script>
|
||
<script src="/static/js/admin/admin-layout-templates.js"></script>
|
||
<script src="/static/js/admin/dashboard.js"></script>
|
||
|
||
<script>
|
||
// Initialize table scroll detection
|
||
document.addEventListener('alpine:init', () => {
|
||
// Wait for Alpine to finish rendering
|
||
setTimeout(() => {
|
||
const tables = document.querySelectorAll('.table-responsive');
|
||
tables.forEach(table => {
|
||
table.addEventListener('scroll', function() {
|
||
if (this.scrollLeft > 0) {
|
||
this.classList.add('is-scrolled');
|
||
} else {
|
||
this.classList.remove('is-scrolled');
|
||
}
|
||
});
|
||
});
|
||
}, 100);
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html> |