Add seller-scoped dashboard aggregation and API, private financial summary DTOs, responsive admin metrics, and authenticated E2E coverage.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user