@@ -135,7 +135,13 @@ export function createListingRepository(db) {
|
|||||||
|
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
const sortOrder =
|
const sortOrder =
|
||||||
sort === "price_low"
|
sort === "updated"
|
||||||
|
? desc(listings.updatedAt)
|
||||||
|
: sort === "name_asc"
|
||||||
|
? products.name
|
||||||
|
: sort === "name_desc"
|
||||||
|
? desc(products.name)
|
||||||
|
: sort === "price_low"
|
||||||
? listings.askingPriceMinor
|
? listings.askingPriceMinor
|
||||||
: sort === "price_high"
|
: sort === "price_high"
|
||||||
? desc(listings.askingPriceMinor)
|
? desc(listings.askingPriceMinor)
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ export function createSearchQuery({
|
|||||||
throw new Error("minPriceMinor must be <= maxPriceMinor");
|
throw new Error("minPriceMinor must be <= maxPriceMinor");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!["recent", "price_low", "price_high"].includes(sort)) {
|
if (!["recent", "updated", "name_asc", "name_desc", "price_low", "price_high"].includes(sort)) {
|
||||||
throw new Error("sort must be recent, price_low, or price_high");
|
throw new Error("sort must be recent, updated, name_asc, name_desc, price_low, or price_high");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ test("builds a public query filter set that hides sold items by default", () =>
|
|||||||
|
|
||||||
test("accepts only the supported public search sort values", () => {
|
test("accepts only the supported public search sort values", () => {
|
||||||
assert.equal(createSearchQuery({ sort: "price_low" }).sort, "price_low");
|
assert.equal(createSearchQuery({ sort: "price_low" }).sort, "price_low");
|
||||||
|
assert.equal(createSearchQuery({ sort: "updated" }).sort, "updated");
|
||||||
|
assert.equal(createSearchQuery({ sort: "name_asc" }).sort, "name_asc");
|
||||||
|
assert.equal(createSearchQuery({ sort: "name_desc" }).sort, "name_desc");
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => createSearchQuery({ sort: "random" }),
|
() => createSearchQuery({ sort: "random" }),
|
||||||
/sort must be recent/,
|
/sort must be recent/,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.catalog-sort-select {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-sort-select span {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-sort-select select {
|
||||||
|
min-height: 44px;
|
||||||
|
border: var(--border-thin) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import CatalogSortSelect from './CatalogSortSelect.vue';
|
||||||
|
|
||||||
|
export default { title: 'Atoms/CatalogSortSelect', component: CatalogSortSelect };
|
||||||
|
|
||||||
|
export const RecentlyAdded = { args: { modelValue: 'recent' } };
|
||||||
|
export const NameAscending = { args: { modelValue: 'name_asc' } };
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
modelValue: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(['update:modelValue']);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<label class="catalog-sort-select">
|
||||||
|
<span>Sort</span>
|
||||||
|
<select
|
||||||
|
:value="modelValue"
|
||||||
|
@change="$emit('update:modelValue', $event.target.value)"
|
||||||
|
>
|
||||||
|
<option value="recent">Recently added</option>
|
||||||
|
<option value="updated">Recently updated</option>
|
||||||
|
<option value="name_asc">Name A-Z</option>
|
||||||
|
<option value="name_desc">Name Z-A</option>
|
||||||
|
<option value="price_low">Price: low to high</option>
|
||||||
|
<option value="price_high">Price: high to low</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use './CatalogSortSelect.scss';
|
||||||
|
</style>
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
listPublicConditions,
|
listPublicConditions,
|
||||||
} from "@/services/catalog-service.js";
|
} from "@/services/catalog-service.js";
|
||||||
import ListingCard from "@/components/molecules/ListingCard.vue";
|
import ListingCard from "@/components/molecules/ListingCard.vue";
|
||||||
|
import CatalogSortSelect from "@/components/atoms/CatalogSortSelect.vue";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -124,14 +125,7 @@ watch(
|
|||||||
>Max price (cents)
|
>Max price (cents)
|
||||||
<input v-model.number="maxPriceMinor" type="number" min="0"
|
<input v-model.number="maxPriceMinor" type="number" min="0"
|
||||||
/></label>
|
/></label>
|
||||||
<label
|
<CatalogSortSelect v-model="sort" />
|
||||||
>Sort
|
|
||||||
<select v-model="sort">
|
|
||||||
<option value="recent">Recently added</option>
|
|
||||||
<option value="price_low">Price: low to high</option>
|
|
||||||
<option value="price_high">Price: high to low</option>
|
|
||||||
</select></label
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ Remaining roadmap areas are approved external provider credentials/adapters, bro
|
|||||||
- `npm audit --omit=dev` currently reports zero production vulnerabilities. Re-run the audit after dependency changes and review development-dependency findings separately before production releases.
|
- `npm audit --omit=dev` currently reports zero production vulnerabilities. Re-run the audit after dependency changes and review development-dependency findings separately before production releases.
|
||||||
- Authenticated E2E coverage now verifies both the core catalog workflow and the Settings, Saved Views, and export-controls workflow.
|
- Authenticated E2E coverage now verifies both the core catalog workflow and the Settings, Saved Views, and export-controls workflow.
|
||||||
- Production dependency audit currently reports zero vulnerabilities with `npm audit --omit=dev`.
|
- Production dependency audit currently reports zero vulnerabilities with `npm audit --omit=dev`.
|
||||||
|
- Public catalog sorting now supports recently added, recently updated, name A-Z, name Z-A, price low-to-high, and price high-to-low through a URL-synchronized atomic sort control. Broader inventory filter parity and market-value sorting remain.
|
||||||
|
|
||||||
## Local Commands
|
## Local Commands
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user