Remove quantity field; normalize missing prices to 0,00 €

Each collection row is a unique card (own condition/notes), so quantity
was noise: dropped the field in Directus and removed it from the add
dialog, collection/grid/table views, dashboard stats and sort options.
Cards without Cardmarket data now consistently show 0,00 € (base price
row is always displayed on the card page).
This commit is contained in:
2026-07-29 00:07:53 +02:00
parent 1cfbf1db49
commit 9d3261718f
8 changed files with 12 additions and 66 deletions
-3
View File
@@ -67,9 +67,6 @@ export function initCollectionFilter() {
case "total-desc":
return (a: HTMLElement, b: HTMLElement) =>
num(b, "total") - num(a, "total");
case "qty-desc":
return (a: HTMLElement, b: HTMLElement) =>
num(b, "qty") - num(a, "qty");
default:
// "Ajout récent" — original server-rendered order
return (a: HTMLElement, b: HTMLElement) =>
+4 -4
View File
@@ -20,9 +20,10 @@ export const variantPriceMap: Record<string, string> = {
};
/**
* Resolve the Cardmarket unit price and line total for each collection card.
* Resolve the Cardmarket unit price for each collection card.
* Card details come from the 24h in-memory cache, so repeated cards and
* repeated page loads don't hit the TCGdex API again.
* repeated page loads don't hit the TCGdex API again. Cards without a
* Cardmarket price get 0 for consistent display (0,00 €).
*/
export async function priceCollectionCards(
cards: MyCard[],
@@ -36,8 +37,7 @@ export async function priceCollectionCards(
const priceKey = variantPriceMap[card.variant ?? ""] ?? "avg";
price = cardmarket[priceKey] ?? cardmarket.avg ?? 0;
}
const quantity = card.quantity ?? 1;
return { ...card, price, totalPrice: price * quantity };
return { ...card, price, totalPrice: price };
}),
);
}
-1
View File
@@ -32,7 +32,6 @@ export interface MyCard {
card_name: string;
card_image: string | null;
variant: string | null;
quantity: number | null;
condition: string | null;
notes: string | null;
set_name: string | null;
-2
View File
@@ -15,7 +15,6 @@ export const POST: APIRoute = async ({ request, locals }) => {
card_name,
card_image,
variant,
quantity,
condition,
notes,
set_name,
@@ -36,7 +35,6 @@ export const POST: APIRoute = async ({ request, locals }) => {
card_name,
card_image: card_image || null,
variant: variant || "Base",
quantity: Number(quantity) || 1,
condition: condition || null,
notes: notes || null,
set_name: set_name || null,
+4 -22
View File
@@ -53,19 +53,15 @@ const availableVariants = card.variants
const variantPrices: { label: string; price: number }[] = [];
// Always include the base avg price (no variant suffix)
if (cardmarket?.avg != null) {
variantPrices.push({ label: "Base", price: cardmarket.avg });
}
// Always include the base avg price (0 when no Cardmarket data)
variantPrices.push({ label: "Base", price: cardmarket?.avg ?? 0 });
// Add prices for each available variant
for (const variant of availableVariants) {
if (variant === "normal") continue; // normal uses the base avg, already added
const priceKey = variantPriceMap[variant];
const price = cardmarket?.[priceKey];
if (price != null) {
variantPrices.push({ label: variantLabels[variant] ?? variant, price });
}
const price = cardmarket?.[priceKey] ?? 0;
variantPrices.push({ label: variantLabels[variant] ?? variant, price });
}
// Determine which tabs have content
@@ -527,19 +523,6 @@ const primaryColor = getPrimaryColor(typeNames);
</select>
</div>
<div role="group" class="field">
<label for="quantity">Quantité</label>
<input
type="number"
name="quantity"
id="quantity"
class="input"
value="1"
min="1"
required
/>
</div>
<div role="group" class="field">
<label for="condition">État</label>
<select
@@ -650,7 +633,6 @@ const primaryColor = getPrimaryColor(typeNames);
card_image: formData.get("card_image"),
set_name: formData.get("set_name"),
variant: formData.get("variant"),
quantity: Number(formData.get("quantity")),
condition: formData.get("condition"),
notes: formData.get("notes"),
};
+1 -9
View File
@@ -88,7 +88,7 @@ const myCards = (await directus!.request(
const cardsWithPrice = await priceCollectionCards(myCards);
const totalValue = cardsWithPrice.reduce((s, c) => s + c.totalPrice, 0);
const totalCards = cardsWithPrice.reduce((s, c) => s + (c.quantity ?? 0), 0);
const totalCards = cardsWithPrice.length;
---
<BaseLayout>
@@ -182,9 +182,6 @@ const totalCards = cardsWithPrice.reduce((s, c) => s + (c.quantity ?? 0), 0);
>
{card.variant}
</span>
<span class="font-semibold text-sm">
×{card.quantity}
</span>
</div>
{card.condition && (
<span
@@ -204,11 +201,6 @@ const totalCards = cardsWithPrice.reduce((s, c) => s + (c.quantity ?? 0), 0);
<span class="font-bold text-base">
{card.price.toFixed(2)} €
</span>
{card.quantity > 1 && (
<span class="text-xs opacity-70">
= {card.totalPrice.toFixed(2)} €
</span>
)}
</div>
</div>
</a>
+2 -2
View File
@@ -27,14 +27,14 @@ const myCards = (await directus!.request(
const cardsWithPrice = await priceCollectionCards(myCards);
// Insights
const totalCards = cardsWithPrice.reduce((s, c) => s + (c.quantity ?? 0), 0);
const totalCards = cardsWithPrice.length;
const totalValue = cardsWithPrice.reduce((s, c) => s + c.totalPrice, 0);
const bySet = new Map<string, { count: number; value: number }>();
for (const c of cardsWithPrice) {
const key = c.set_name || "Inconnu";
const entry = bySet.get(key) ?? { count: 0, value: 0 };
entry.count += c.quantity;
entry.count += 1;
entry.value += c.totalPrice;
bySet.set(key, entry);
}
+1 -23
View File
@@ -29,10 +29,7 @@ const totalValue = cardsWithPrice.reduce(
(sum, card) => sum + card.totalPrice,
0,
);
const totalCards = cardsWithPrice.reduce(
(sum, card) => sum + (card.quantity ?? 0),
0,
);
const totalCards = cardsWithPrice.length;
// Distinct values present in the collection, for the filter dropdowns
const distinctSets = [
@@ -161,7 +158,6 @@ const distinctConditions = [
<option value="total-desc">
Valeur totale ↓
</option>
<option value="qty-desc">Quantité ↓</option>
</select>
</label>
<button type="button" id="cf-reset" class="pill">
@@ -244,7 +240,6 @@ const distinctConditions = [
data-condition={card.condition}
data-price={card.price}
data-total={card.totalPrice}
data-qty={card.quantity}
>
<a
href={`/card/${card.card_id}`}
@@ -278,9 +273,6 @@ const distinctConditions = [
<span class="badge" data-variant="secondary">
{card.variant}
</span>
<span class="font-semibold text-sm">
×{card.quantity}
</span>
</div>
{card.condition && (
<span class="badge" data-variant={card.condition === "Neuf" ? "primary" : card.condition === "Joué" ? "destructive" : "outline"}>
@@ -291,11 +283,6 @@ const distinctConditions = [
<span class="font-bold text-base">
{card.price.toFixed(2)} €
</span>
{card.quantity > 1 && (
<span class="text-xs opacity-70">
= {card.totalPrice.toFixed(2)} €
</span>
)}
</div>
</div>
</a>
@@ -327,9 +314,7 @@ const distinctConditions = [
<th>Set</th>
<th>Variant</th>
<th>État</th>
<th class="text-end">Qté</th>
<th class="text-end">Prix</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>
@@ -341,7 +326,6 @@ const distinctConditions = [
data-condition={card.condition}
data-price={card.price}
data-total={card.totalPrice}
data-qty={card.quantity}
>
<td>
<a href={`/card/${card.card_id}`}>
@@ -379,15 +363,9 @@ const distinctConditions = [
<span class="badge" data-variant={card.condition === "Neuf" ? "primary" : card.condition === "Joué" ? "destructive" : "outline"}>{card.condition}</span>
)}
</td>
<td class="text-end tabular-nums">
×{card.quantity}
</td>
<td class="text-end tabular-nums font-semibold">
{card.price.toFixed(2)} €
</td>
<td class="text-end tabular-nums font-bold">
{card.totalPrice.toFixed(2)} €
</td>
</tr>
))}
</tbody>