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(() => {
Missing images
- {{ dashboard.counts.missingImages }} + {{ dashboard.counts.missingImages }}
Missing pricing
- {{ dashboard.counts.missingPricing }} + {{ dashboard.counts.missingPricing }}
Stale pricing
- {{ dashboard.counts.stalePricing }} + {{ dashboard.counts.stalePricing }}
Provider failures
- {{ + {{ dashboard.counts.failedPricingJobs }}
@@ -717,103 +638,8 @@ onMounted(() => { aria-labelledby="inventory-title" >

Physical inventory

-
- - - - - -
-

- No physical copies yet. -

- - - +

{{ 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 @@ + + + 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