feat: reconcile application vertical slices
Backfill the completed acquisitions, sales, dashboard, pricing, search, public detail, worker, migration, contracts, validation, tests, and implementation-status changes into one verified application slice.
This commit is contained in:
@@ -1,46 +1,84 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import { useCatalogSearch } from '@/composables/useCatalogSearch.js';
|
||||
import { listPublicCategories, listPublicConditions } from '@/services/catalog-service.js';
|
||||
import ListingCard from '@/components/molecules/ListingCard.vue';
|
||||
import { useCatalogSearch } from "@/composables/useCatalogSearch.js";
|
||||
import {
|
||||
listPublicCategories,
|
||||
listPublicConditions,
|
||||
} from "@/services/catalog-service.js";
|
||||
import ListingCard from "@/components/molecules/ListingCard.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const categories = ref([]);
|
||||
const conditions = ref([]);
|
||||
const { query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page, result, isLoading, error, load, goToPage } = useCatalogSearch({
|
||||
initialQuery: typeof route.query.q === 'string' ? route.query.q : '',
|
||||
initialCategory: typeof route.query.category === 'string' ? route.query.category : '',
|
||||
initialConditionId: typeof route.query.conditionId === 'string' ? route.query.conditionId : '',
|
||||
const {
|
||||
query,
|
||||
category,
|
||||
conditionId,
|
||||
minPriceMinor,
|
||||
maxPriceMinor,
|
||||
sort,
|
||||
page,
|
||||
result,
|
||||
isLoading,
|
||||
error,
|
||||
load,
|
||||
goToPage,
|
||||
} = useCatalogSearch({
|
||||
initialQuery: typeof route.query.q === "string" ? route.query.q : "",
|
||||
initialCategory:
|
||||
typeof route.query.category === "string" ? route.query.category : "",
|
||||
initialConditionId:
|
||||
typeof route.query.conditionId === "string" ? route.query.conditionId : "",
|
||||
initialMinPriceMinor: Number.parseInt(route.query.min, 10) || undefined,
|
||||
initialMaxPriceMinor: Number.parseInt(route.query.max, 10) || undefined,
|
||||
initialSort: typeof route.query.sort === 'string' ? route.query.sort : 'recent',
|
||||
initialSort:
|
||||
typeof route.query.sort === "string" ? route.query.sort : "recent",
|
||||
initialPage: Number.parseInt(route.query.page, 10) || 1,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
load(),
|
||||
listPublicCategories().then((items) => { categories.value = items; }).catch(() => {}),
|
||||
listPublicConditions().then((items) => { conditions.value = items; }).catch(() => {}),
|
||||
listPublicCategories()
|
||||
.then((items) => {
|
||||
categories.value = items;
|
||||
})
|
||||
.catch(() => {}),
|
||||
listPublicConditions()
|
||||
.then((items) => {
|
||||
conditions.value = items;
|
||||
})
|
||||
.catch(() => {}),
|
||||
]);
|
||||
});
|
||||
|
||||
watch([query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page], ([nextQuery, nextCategory, nextCondition, nextMin, nextMax, nextSort, nextPage]) => {
|
||||
router.replace({
|
||||
query: {
|
||||
...(nextQuery ? { q: nextQuery } : {}),
|
||||
...(nextCategory ? { category: nextCategory } : {}),
|
||||
...(nextCondition ? { conditionId: nextCondition } : {}),
|
||||
...(typeof nextMin === 'number' ? { min: String(nextMin) } : {}),
|
||||
...(typeof nextMax === 'number' ? { max: String(nextMax) } : {}),
|
||||
...(nextSort !== 'recent' ? { sort: nextSort } : {}),
|
||||
...(nextPage > 1 ? { page: String(nextPage) } : {}),
|
||||
},
|
||||
});
|
||||
});
|
||||
watch(
|
||||
[query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page],
|
||||
([
|
||||
nextQuery,
|
||||
nextCategory,
|
||||
nextCondition,
|
||||
nextMin,
|
||||
nextMax,
|
||||
nextSort,
|
||||
nextPage,
|
||||
]) => {
|
||||
router.replace({
|
||||
query: {
|
||||
...(nextQuery ? { q: nextQuery } : {}),
|
||||
...(nextCategory ? { category: nextCategory } : {}),
|
||||
...(nextCondition ? { conditionId: nextCondition } : {}),
|
||||
...(typeof nextMin === "number" ? { min: String(nextMin) } : {}),
|
||||
...(typeof nextMax === "number" ? { max: String(nextMax) } : {}),
|
||||
...(nextSort !== "recent" ? { sort: nextSort } : {}),
|
||||
...(nextPage > 1 ? { page: String(nextPage) } : {}),
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -48,17 +86,52 @@ watch([query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page],
|
||||
<header class="catalog-header">
|
||||
<p class="catalog-kicker">Pokemon inventory</p>
|
||||
<h1>Find the next favorite.</h1>
|
||||
<p class="catalog-intro">Browse available cards, collectibles, and carefully cataloged pieces.</p>
|
||||
<p class="catalog-intro">
|
||||
Browse available cards, collectibles, and carefully cataloged pieces.
|
||||
</p>
|
||||
<label class="search-field">
|
||||
<span>Search the catalog</span>
|
||||
<input v-model="query" type="search" placeholder="Try Charizard, 151, plush..." />
|
||||
<input
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Try Charizard, 151, plush..."
|
||||
/>
|
||||
</label>
|
||||
<div class="catalog-filters">
|
||||
<label>Category <select v-model="category"><option value="">All categories</option><option v-for="item in categories" :key="item.id" :value="item.id">{{ item.name }}</option></select></label>
|
||||
<label>Condition <select v-model="conditionId"><option value="">All conditions</option><option v-for="item in conditions" :key="item.id" :value="item.id">{{ item.name }} ({{ item.code }})</option></select></label>
|
||||
<label>Min price (cents) <input v-model.number="minPriceMinor" type="number" min="0" /></label>
|
||||
<label>Max price (cents) <input v-model.number="maxPriceMinor" type="number" min="0" /></label>
|
||||
<label>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>
|
||||
<label
|
||||
>Category
|
||||
<select v-model="category">
|
||||
<option value="">All categories</option>
|
||||
<option v-for="item in categories" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</select></label
|
||||
>
|
||||
<label
|
||||
>Condition
|
||||
<select v-model="conditionId">
|
||||
<option value="">All conditions</option>
|
||||
<option v-for="item in conditions" :key="item.id" :value="item.id">
|
||||
{{ item.name }} ({{ item.code }})
|
||||
</option>
|
||||
</select></label
|
||||
>
|
||||
<label
|
||||
>Min price (cents)
|
||||
<input v-model.number="minPriceMinor" type="number" min="0"
|
||||
/></label>
|
||||
<label
|
||||
>Max price (cents)
|
||||
<input v-model.number="maxPriceMinor" type="number" min="0"
|
||||
/></label>
|
||||
<label
|
||||
>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>
|
||||
</header>
|
||||
|
||||
@@ -69,13 +142,25 @@ watch([query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page],
|
||||
</div>
|
||||
|
||||
<p v-if="isLoading" class="catalog-state">Searching the catalog...</p>
|
||||
<p v-else-if="error" class="catalog-state catalog-state--error">{{ error.message }}</p>
|
||||
<p v-else-if="result.items.length === 0" class="catalog-state">No relevant matches found.</p>
|
||||
<p v-else-if="error" class="catalog-state catalog-state--error">
|
||||
{{ error.message }}
|
||||
</p>
|
||||
<p v-else-if="result.items.length === 0" class="catalog-state">
|
||||
No relevant matches found.
|
||||
</p>
|
||||
<div v-else class="listing-grid">
|
||||
<ListingCard v-for="item in result.items" :key="item.id" :listing="item" />
|
||||
<ListingCard
|
||||
v-for="item in result.items"
|
||||
:key="item.id"
|
||||
:listing="item"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<nav v-if="result.pagination.totalPages > 1" class="pagination" aria-label="Catalog pages">
|
||||
<nav
|
||||
v-if="result.pagination.totalPages > 1"
|
||||
class="pagination"
|
||||
aria-label="Catalog pages"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="page <= 1 || isLoading"
|
||||
@@ -83,7 +168,9 @@ watch([query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page],
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span aria-live="polite">Page {{ page }} of {{ result.pagination.totalPages }}</span>
|
||||
<span aria-live="polite"
|
||||
>Page {{ page }} of {{ result.pagination.totalPages }}</span
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="page >= result.pagination.totalPages || isLoading"
|
||||
@@ -94,4 +181,4 @@ watch([query, category, conditionId, minPriceMinor, maxPriceMinor, sort, page],
|
||||
</nav>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user