diff --git a/apps/api/src/modules/pricing/routes/admin-pricing-routes.js b/apps/api/src/modules/pricing/routes/admin-pricing-routes.js
index 904e1f7..35df7bd 100644
--- a/apps/api/src/modules/pricing/routes/admin-pricing-routes.js
+++ b/apps/api/src/modules/pricing/routes/admin-pricing-routes.js
@@ -1,5 +1,6 @@
import {
toAdminMarketValuationDto,
+ toAdminMarketValuationSummaryDto,
toAdminPricingObservationDto,
} from "@pokemon-inventory/contracts/pricing";
import {
@@ -91,6 +92,14 @@ export async function registerAdminPricingRoutes(app, options) {
return reply.code(201).send({ data: override });
});
+ app.get("/valuations", async (request) => ({
+ data: (
+ await pricingService.listValuations({
+ sellerId: request.admin.sellerId,
+ })
+ ).map(toAdminMarketValuationSummaryDto),
+ }));
+
app.post("/inventory/:id/pricing/refresh", async (request, reply) => {
await authorizeUnsafeRequest?.(
request.cookies[sessionCookieName],
diff --git a/apps/api/src/modules/pricing/services/pricing-service.js b/apps/api/src/modules/pricing/services/pricing-service.js
index 9960430..98a5140 100644
--- a/apps/api/src/modules/pricing/services/pricing-service.js
+++ b/apps/api/src/modules/pricing/services/pricing-service.js
@@ -203,6 +203,10 @@ export function createPricingService({
return pricingRefreshJobRepository?.listForSeller(sellerId) ?? [];
},
+ async listValuations({ sellerId }) {
+ return pricingRepository.listLatestValuationsForSeller(sellerId);
+ },
+
async retryJob({ sellerId, jobId }) {
const job = await pricingRefreshJobRepository?.retryForSeller(
jobId,
diff --git a/apps/api/tests/http/pricing-routes.test.js b/apps/api/tests/http/pricing-routes.test.js
index 35436b4..10f062d 100644
--- a/apps/api/tests/http/pricing-routes.test.js
+++ b/apps/api/tests/http/pricing-routes.test.js
@@ -182,3 +182,38 @@ test("lists jobs and retries a failed job with CSRF protection", async (testCont
assert.equal(retryResponse.statusCode, 202);
assert.equal(csrfChecks, 1);
});
+
+test("lists seller-scoped latest market valuations", async (testContext) => {
+ const app = buildApp({
+ authenticateAdmin: async () => ({ sellerId: "seller-1" }),
+ pricingService: {
+ async listValuations({ sellerId }) {
+ assert.equal(sellerId, "seller-1");
+ return [
+ {
+ id: "valuation-1",
+ inventoryItemId: "item-1",
+ valueMinor: 2500,
+ currency: "USD",
+ strategy: "MEDIAN",
+ confidence: "MEDIUM",
+ sourceCount: 2,
+ calculatedAt: new Date().toISOString(),
+ },
+ ];
+ },
+ },
+ });
+ testContext.after(() => app.close());
+
+ const response = await app.inject({
+ method: "GET",
+ url: "/api/v1/admin/valuations",
+ });
+
+ assert.equal(response.statusCode, 200);
+ const [valuation] = response.json().data;
+ assert.equal(valuation.inventoryItemId, "item-1");
+ assert.equal(valuation.valueMinor, 2500);
+ assert.equal(valuation.stale, false);
+});
diff --git a/apps/web/src/pages/AdminHomePage.vue b/apps/web/src/pages/AdminHomePage.vue
index 5a59321..b597219 100644
--- a/apps/web/src/pages/AdminHomePage.vue
+++ b/apps/web/src/pages/AdminHomePage.vue
@@ -12,13 +12,10 @@ import ReviewListStep from "@/components/molecules/ReviewListStep.vue";
import {
createAdminInventory,
createAdminProduct,
- archiveAdminInventory,
listAdminCategories,
listAdminConditions,
listAdminInventory,
listAdminProducts,
- refreshAdminInventoryPricing,
- updateAdminInventoryTags,
enqueueAdminInventoryExport,
getAdminInventoryExportJob,
} from "@/services/admin-catalog-service.js";
@@ -34,10 +31,6 @@ import {
getAdminPricingJob,
overrideAdminValuation,
} from "@/services/admin-pricing-service.js";
-import {
- listAdminPricingJobs,
- retryAdminPricingJob,
-} from "@/services/admin-jobs-service.js";
import {
createAdminProviderMapping,
listAdminProviderMappings,
@@ -57,8 +50,6 @@ const activeStepId = ref(WIZARD_STEP_IDS[0]);
const activeStepIndex = computed(() => WIZARD_STEP_IDS.indexOf(activeStepId.value));
const products = ref([]);
const inventory = ref([]);
-const selectedInventoryIds = ref([]);
-const inventoryTags = ref("");
const exportStatus = ref("");
const exportJobId = ref("");
const listings = ref([]);
@@ -118,7 +109,6 @@ const mappingType = ref("PRODUCT");
const mappingExternalIdentifier = ref("");
const mappingSearchQuery = ref("");
const providerMappings = ref([]);
-const pricingJobs = ref([]);
function csrfToken() {
return (
@@ -139,7 +129,6 @@ async function load() {
conditions.value,
listings.value,
dashboard.value,
- pricingJobs.value,
] = await Promise.all([
listAdminProducts(),
listAdminInventory(),
@@ -147,7 +136,6 @@ async function load() {
listAdminConditions(),
listAdminListings(),
getAdminDashboard(),
- listAdminPricingJobs(),
]);
} catch (requestError) {
error.value = requestError;
@@ -219,61 +207,6 @@ async function createInventory() {
}
}
-async function archiveSelectedInventory() {
- if (!selectedInventoryIds.value.length) return;
- isSaving.value = true;
- try {
- const archivedItems = await archiveAdminInventory(
- selectedInventoryIds.value,
- csrfToken(),
- );
- const archivedById = new Map(archivedItems.map((item) => [item.id, item]));
- inventory.value = inventory.value.map((item) => archivedById.get(item.id) ?? item);
- selectedInventoryIds.value = [];
- } catch (requestError) {
- error.value = requestError;
- } finally {
- isSaving.value = false;
- }
-}
-
-async function refreshSelectedPricing() {
- if (!selectedInventoryIds.value.length) return;
- isSaving.value = true;
- try {
- const jobs = await refreshAdminInventoryPricing(
- selectedInventoryIds.value,
- pricingProvider.value,
- csrfToken(),
- );
- pricingJobs.value = [...jobs, ...pricingJobs.value];
- selectedInventoryIds.value = [];
- } catch (requestError) {
- error.value = requestError;
- } finally {
- isSaving.value = false;
- }
-}
-
-async function updateSelectedTags(action) {
- const tags = inventoryTags.value.split(",").map((tag) => tag.trim()).filter(Boolean);
- if (!selectedInventoryIds.value.length || !tags.length) return;
- isSaving.value = true;
- try {
- await updateAdminInventoryTags(
- selectedInventoryIds.value,
- action === "add" ? tags : [],
- action === "remove" ? tags : [],
- csrfToken(),
- );
- inventoryTags.value = "";
- selectedInventoryIds.value = [];
- } catch (requestError) {
- error.value = requestError;
- } finally {
- isSaving.value = false;
- }
-}
async function enqueueInventoryExport() {
isSaving.value = true;
@@ -387,18 +320,6 @@ async function refreshPricing() {
}
}
-async function retryPricingJob(jobId) {
- isSaving.value = true;
- try {
- await retryAdminPricingJob(jobId, csrfToken());
- pricingJobs.value = await listAdminPricingJobs();
- } catch (requestError) {
- error.value = requestError;
- } finally {
- isSaving.value = false;
- }
-}
-
async function overridePricing() {
isSaving.value = true;
try {
@@ -538,25 +459,25 @@ onMounted(() => {
Provider failures
- {{
+ {{
dashboard.counts.failedPricingJobs
}}
@@ -717,103 +638,8 @@ onMounted(() => {
aria-labelledby="inventory-title"
>
Physical inventory
-
-
-
-
-
-
-
-
- No physical copies yet.
-
-
-
- -
- {{ inventoryItemId }} · Market value ${{
- (valuation.valueMinor / 100).toFixed(2)
- }}
- · {{ valuation.confidence }} confidence ·
- {{ valuation.sourceCount }} source{{
- valuation.sourceCount === 1 ? "" : "s"
- }} · STALE
-
- · OUTLIER FLAGGED
-
-
-
- -
- {{ job.id }} · {{ job.providerId }} · {{ job.status
- }} · {{ job.error }}
-
-
-
+
{{ inventory.length }} physical cop{{ inventory.length === 1 ? "y" : "ies" }} recorded.
+
Manage inventory
Listings
diff --git a/apps/web/src/pages/AdminInventoryPage.stories.js b/apps/web/src/pages/AdminInventoryPage.stories.js
new file mode 100644
index 0000000..9b59018
--- /dev/null
+++ b/apps/web/src/pages/AdminInventoryPage.stories.js
@@ -0,0 +1,5 @@
+import AdminInventoryPage from './AdminInventoryPage.vue';
+
+export default { title: 'Pages/AdminInventoryPage', component: AdminInventoryPage };
+
+export const Inventory = {};
diff --git a/apps/web/src/pages/AdminInventoryPage.vue b/apps/web/src/pages/AdminInventoryPage.vue
new file mode 100644
index 0000000..7eac22d
--- /dev/null
+++ b/apps/web/src/pages/AdminInventoryPage.vue
@@ -0,0 +1,149 @@
+
+
+
+
+ Inventory management
+ Physical inventory
+ Loading inventory...
+ {{ error }}
+
+
+
diff --git a/apps/web/src/pages/AdminShell.vue b/apps/web/src/pages/AdminShell.vue
index 9bf25f0..88ff84a 100644
--- a/apps/web/src/pages/AdminShell.vue
+++ b/apps/web/src/pages/AdminShell.vue
@@ -19,6 +19,7 @@ async function logout() {
Inventory