feat: add admin dashboard summary
Quality / test (push) Canceled after 0s

Add seller-scoped dashboard aggregation and API, private financial summary DTOs, responsive admin metrics, and authenticated E2E coverage.
This commit is contained in:
2026-09-09 01:00:28 -04:00
parent 1d13c79a04
commit 455971431f
11 changed files with 177 additions and 1 deletions
+26 -1
View File
@@ -8,6 +8,7 @@ import { createAdminInventory, createAdminProduct, listAdminCategories, listAdmi
import { createAdminListing, listAdminListings, markListingPending, publishAdminListing } from '@/services/admin-listing-service.js';
import { createAdminSale, listAdminSales, voidAdminSale } from '@/services/admin-sale-service.js';
import { createAdminAcquisition, listAdminAcquisitions } from '@/services/admin-acquisition-service.js';
import { getAdminDashboard } from '@/services/admin-dashboard-service.js';
const { principal } = useAuth();
const products = ref([]);
@@ -15,6 +16,7 @@ const inventory = ref([]);
const listings = ref([]);
const sales = ref([]);
const acquisitions = ref([]);
const dashboard = ref(null);
const categories = ref([]);
const conditions = ref([]);
const isLoading = ref(true);
@@ -45,7 +47,7 @@ function csrfToken() {
async function load() {
isLoading.value = true;
try {
[products.value, inventory.value, categories.value, conditions.value, listings.value, sales.value, acquisitions.value] = await Promise.all([listAdminProducts(), listAdminInventory(), listAdminCategories(), listAdminConditions(), listAdminListings(), listAdminSales(), listAdminAcquisitions()]);
[products.value, inventory.value, categories.value, conditions.value, listings.value, sales.value, acquisitions.value, dashboard.value] = await Promise.all([listAdminProducts(), listAdminInventory(), listAdminCategories(), listAdminConditions(), listAdminListings(), listAdminSales(), listAdminAcquisitions(), getAdminDashboard()]);
} catch (requestError) { error.value = requestError; }
finally { isLoading.value = false; }
}
@@ -154,6 +156,20 @@ onMounted(load);
<p v-if="isLoading" class="state state--loading" role="status">Loading inventory...</p>
<p v-if="error" class="state state--error" role="alert">{{ error.message }}</p>
<section v-if="dashboard" class="dashboard-summary" aria-labelledby="dashboard-summary-title">
<h2 id="dashboard-summary-title">At a glance</h2>
<dl class="dashboard-summary__grid">
<div><dt>Active inventory</dt><dd>{{ dashboard.counts.activeInventory }}</dd></div>
<div><dt>Available listings</dt><dd>{{ dashboard.counts.availableListings }}</dd></div>
<div><dt>Sold inventory</dt><dd>{{ dashboard.counts.soldInventory }}</dd></div>
<div><dt>Missing images</dt><dd>{{ dashboard.counts.missingImages }}</dd></div>
<div><dt>Acquisition cost</dt><dd>${{ (dashboard.financials.totalAcquisitionCostMinor / 100).toFixed(2) }}</dd></div>
<div><dt>Asking value</dt><dd>${{ (dashboard.financials.totalAskingValueMinor / 100).toFixed(2) }}</dd></div>
<div><dt>Net proceeds</dt><dd>${{ (dashboard.financials.totalNetSaleProceedsMinor / 100).toFixed(2) }}</dd></div>
<div><dt>Profit</dt><dd>${{ (dashboard.financials.totalProfitMinor / 100).toFixed(2) }}</dd></div>
</dl>
</section>
<div class="admin-workspace-grid">
<form class="showcase-panel" @submit.prevent="createProduct">
<h2>Add product</h2>
@@ -275,3 +291,12 @@ onMounted(load);
</section>
</section>
</template>
<style scoped lang="scss">
.dashboard-summary { margin: var(--space-6) 0; }
.dashboard-summary h2 { margin-bottom: var(--space-3); }
.dashboard-summary__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-3); margin: 0; }
.dashboard-summary__grid div { border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-3); background: var(--color-surface); }
.dashboard-summary__grid dt { color: var(--color-text-muted); font-size: var(--text-sm); }
.dashboard-summary__grid dd { margin: var(--space-2) 0 0; font-size: var(--text-xl); font-weight: 700; }
</style>
@@ -0,0 +1,8 @@
const apiBaseUrl = import.meta.env?.VITE_API_BASE_URL ?? '/api/v1';
export async function getAdminDashboard() {
const response = await fetch(`${apiBaseUrl}/admin/dashboard`, { credentials: 'include' });
const payload = await response.json().catch(() => null);
if (!response.ok) throw new Error(payload?.error?.message ?? 'Dashboard request failed.');
return payload?.data;
}
@@ -15,6 +15,8 @@ test('signs in and publishes a listing after adding an inventory image', async (
await expect(page).toHaveURL(/\/admin$/);
await expect(page.getByRole('heading', { name: 'Collector workspace' })).toBeVisible();
await expect(page.getByRole('status', { name: 'Loading inventory...' })).toHaveCount(0);
await expect(page.getByRole('heading', { name: 'At a glance' })).toBeVisible();
await expect(page.getByText('Active inventory')).toBeVisible();
const category = page.locator('#product-category option').nth(1);
await expect(category).toBeAttached();