Expose public condition labels and safe market values on listing details, with Storybook coverage for market value presentation.
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import { and, asc, count, desc, eq, ilike, inArray, or, sql } from 'drizzle-orm';
|
import { and, asc, count, desc, eq, ilike, inArray, or, sql } from 'drizzle-orm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
conditions,
|
||||||
inventoryItems,
|
inventoryItems,
|
||||||
listings,
|
listings,
|
||||||
|
marketValueOverrides,
|
||||||
|
marketValuations,
|
||||||
products,
|
products,
|
||||||
} from '../../../infrastructure/database/schema.js';
|
} from '../../../infrastructure/database/schema.js';
|
||||||
|
|
||||||
@@ -134,17 +137,31 @@ export function createListingRepository(db) {
|
|||||||
|
|
||||||
async findPublicById(listingId) {
|
async findPublicById(listingId) {
|
||||||
const [result] = await db
|
const [result] = await db
|
||||||
.select({ listing: listings })
|
.select({ listing: listings, condition: conditions })
|
||||||
.from(listings)
|
.from(listings)
|
||||||
.innerJoin(inventoryItems, eq(inventoryItems.id, listings.inventoryItemId))
|
.innerJoin(inventoryItems, eq(inventoryItems.id, listings.inventoryItemId))
|
||||||
.innerJoin(products, eq(products.id, inventoryItems.productId))
|
.innerJoin(products, eq(products.id, inventoryItems.productId))
|
||||||
|
.innerJoin(conditions, eq(conditions.id, inventoryItems.conditionId))
|
||||||
.where(and(
|
.where(and(
|
||||||
eq(listings.id, listingId),
|
eq(listings.id, listingId),
|
||||||
inArray(listings.status, ['AVAILABLE', 'PENDING']),
|
inArray(listings.status, ['AVAILABLE', 'PENDING']),
|
||||||
eq(inventoryItems.status, 'ACTIVE'),
|
eq(inventoryItems.status, 'ACTIVE'),
|
||||||
));
|
));
|
||||||
|
|
||||||
return result?.listing ?? null;
|
if (!result) return null;
|
||||||
|
const [override] = await db.select().from(marketValueOverrides)
|
||||||
|
.where(eq(marketValueOverrides.inventoryItemId, result.listing.inventoryItemId))
|
||||||
|
.orderBy(desc(marketValueOverrides.createdAt)).limit(1);
|
||||||
|
const [valuation] = await db.select().from(marketValuations)
|
||||||
|
.where(eq(marketValuations.inventoryItemId, result.listing.inventoryItemId))
|
||||||
|
.orderBy(desc(marketValuations.calculatedAt)).limit(1);
|
||||||
|
return {
|
||||||
|
...result.listing,
|
||||||
|
conditionName: result.condition.name,
|
||||||
|
conditionCode: result.condition.code,
|
||||||
|
marketValueMinor: override?.valueMinor ?? valuation?.valueMinor ?? null,
|
||||||
|
marketValueCurrency: override?.currency ?? valuation?.currency ?? null,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateStatus(listingId, status) {
|
async updateStatus(listingId, status) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const ListingDetail = {
|
|||||||
title: 'Charizard ex #199/165',
|
title: 'Charizard ex #199/165',
|
||||||
status: 'AVAILABLE',
|
status: 'AVAILABLE',
|
||||||
priceMinor: 11500,
|
priceMinor: 11500,
|
||||||
|
marketValueMinor: 12000,
|
||||||
condition: 'Near Mint',
|
condition: 'Near Mint',
|
||||||
description: 'A clean collector copy with a clear front and back.',
|
description: 'A clean collector copy with a clear front and back.',
|
||||||
facts: [
|
facts: [
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ defineProps({
|
|||||||
title: { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
status: { type: String, default: '' },
|
status: { type: String, default: '' },
|
||||||
priceMinor: { type: Number, default: null },
|
priceMinor: { type: Number, default: null },
|
||||||
|
marketValueMinor: { type: Number, default: null },
|
||||||
|
marketValueCurrency: { type: String, default: 'USD' },
|
||||||
condition: { type: String, default: '' },
|
condition: { type: String, default: '' },
|
||||||
description: { type: String, default: '' },
|
description: { type: String, default: '' },
|
||||||
facts: { type: Array, default: () => [] },
|
facts: { type: Array, default: () => [] },
|
||||||
@@ -27,6 +29,7 @@ defineProps({
|
|||||||
<p v-if="description" class="detail-notes">{{ description }}</p>
|
<p v-if="description" class="detail-notes">{{ description }}</p>
|
||||||
<div class="collectible-detail__summary">
|
<div class="collectible-detail__summary">
|
||||||
<PriceDisplay v-if="priceMinor !== null" :amount-minor="priceMinor" />
|
<PriceDisplay v-if="priceMinor !== null" :amount-minor="priceMinor" />
|
||||||
|
<PriceDisplay v-if="marketValueMinor !== null" label="Market value" :amount-minor="marketValueMinor" />
|
||||||
<ConditionBadge v-if="condition" :label="condition" />
|
<ConditionBadge v-if="condition" :label="condition" />
|
||||||
</div>
|
</div>
|
||||||
<dl v-if="facts.length" class="product-facts">
|
<dl v-if="facts.length" class="product-facts">
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ onMounted(async () => {
|
|||||||
:title="listing.productName"
|
:title="listing.productName"
|
||||||
:status="listing.status"
|
:status="listing.status"
|
||||||
:price-minor="listing.askingPriceMinor"
|
:price-minor="listing.askingPriceMinor"
|
||||||
:condition="listing.conditionId"
|
:market-value-minor="listing.marketValueMinor"
|
||||||
|
:market-value-currency="listing.marketValueCurrency"
|
||||||
|
:condition="listing.conditionName ? `${listing.conditionName} (${listing.conditionCode})` : listing.conditionId"
|
||||||
:description="listing.notes"
|
:description="listing.notes"
|
||||||
:images="listing.images"
|
:images="listing.images"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ export function toPublicListingDto(listing) {
|
|||||||
productName: listing.productName,
|
productName: listing.productName,
|
||||||
categoryId: listing.categoryId,
|
categoryId: listing.categoryId,
|
||||||
conditionId: listing.conditionId,
|
conditionId: listing.conditionId,
|
||||||
|
conditionName: listing.conditionName ?? null,
|
||||||
|
conditionCode: listing.conditionCode ?? null,
|
||||||
askingPriceMinor: listing.askingPriceMinor,
|
askingPriceMinor: listing.askingPriceMinor,
|
||||||
|
marketValueMinor: listing.marketValueMinor ?? null,
|
||||||
|
marketValueCurrency: listing.marketValueCurrency ?? null,
|
||||||
status: listing.status,
|
status: listing.status,
|
||||||
notes: listing.notes ?? null,
|
notes: listing.notes ?? null,
|
||||||
images: listing.images ?? [],
|
images: listing.images ?? [],
|
||||||
|
|||||||
Reference in New Issue
Block a user