feat: add inventory workflow stepper
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-10 03:07:51 -04:00
parent ffb86ce21a
commit 8ff547feaa
5 changed files with 171 additions and 5 deletions
@@ -0,0 +1,75 @@
.inventory-workflow {
margin: var(--space-6) 0 var(--space-8);
border: var(--border-thin) solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-surface);
overflow-x: auto;
}
.inventory-workflow__steps {
display: grid;
grid-template-columns: repeat(6, minmax(10rem, 1fr));
min-width: 680px;
gap: 0;
margin: 0;
padding: 0;
list-style: none;
}
.inventory-workflow__step {
border-right: var(--border-thin) solid var(--color-border);
}
.inventory-workflow__step:last-child {
border-right: 0;
}
.inventory-workflow__step a {
display: flex;
align-items: flex-start;
gap: var(--space-2);
min-height: 76px;
padding: var(--space-3);
color: var(--color-text-subtle);
text-decoration: none;
}
.inventory-workflow__step a:hover,
.inventory-workflow__step a:focus-visible {
background: var(--color-surface-muted);
}
.inventory-workflow__index {
display: grid;
flex: 0 0 28px;
place-items: center;
width: 28px;
height: 28px;
border: var(--border-thin) solid var(--color-border-strong);
border-radius: var(--radius-full);
color: var(--color-text-muted);
font-size: var(--text-sm);
font-weight: var(--weight-bold);
}
.inventory-workflow__step strong,
.inventory-workflow__step small {
display: block;
}
.inventory-workflow__step small {
margin-top: var(--space-1);
color: var(--color-text-muted);
font-size: var(--text-xs);
}
.inventory-workflow__step--complete .inventory-workflow__index {
border-color: var(--color-success-500);
color: var(--color-success-text);
}
.inventory-workflow__step--current .inventory-workflow__index {
border-color: var(--color-interactive);
color: var(--color-interactive-text);
box-shadow: var(--glow-selection);
}
@@ -0,0 +1,29 @@
import InventoryWorkflowStepper from './InventoryWorkflowStepper.vue';
export default { title: 'Molecules/InventoryWorkflowStepper', component: InventoryWorkflowStepper };
export const InProgress = {
args: {
steps: [
{ id: 'workflow-product', label: 'Product', description: 'Identify item', status: 'complete' },
{ id: 'workflow-mapping', label: 'Sources', description: 'Link markets', status: 'current' },
{ id: 'workflow-inventory', label: 'Copy', description: 'Add physical copy', status: 'pending' },
{ id: 'workflow-images', label: 'Images', description: 'Add photos', status: 'pending' },
{ id: 'workflow-pricing', label: 'Pricing', description: 'Check value', status: 'pending' },
{ id: 'workflow-listing', label: 'List', description: 'Publish offer', status: 'pending' },
],
},
};
export const Complete = {
args: {
steps: [
{ id: 'workflow-product', label: 'Product', description: 'Identify item', status: 'complete' },
{ id: 'workflow-mapping', label: 'Sources', description: 'Link markets', status: 'complete' },
{ id: 'workflow-inventory', label: 'Copy', description: 'Add physical copy', status: 'complete' },
{ id: 'workflow-images', label: 'Images', description: 'Add photos', status: 'complete' },
{ id: 'workflow-pricing', label: 'Pricing', description: 'Check value', status: 'complete' },
{ id: 'workflow-listing', label: 'List', description: 'Publish offer', status: 'complete' },
],
},
};
@@ -0,0 +1,28 @@
<script setup>
defineProps({
steps: {
type: Array,
required: true,
},
});
</script>
<template>
<nav class="inventory-workflow" aria-label="Inventory creation workflow">
<ol class="inventory-workflow__steps">
<li v-for="(step, index) in steps" :key="step.id" :class="['inventory-workflow__step', `inventory-workflow__step--${step.status}`]">
<a :href="`#${step.id}`">
<span class="inventory-workflow__index">{{ index + 1 }}</span>
<span>
<strong>{{ step.label }}</strong>
<small>{{ step.description }}</small>
</span>
</a>
</li>
</ol>
</nav>
</template>
<style scoped lang="scss">
@use './InventoryWorkflowStepper.scss';
</style>
+38 -5
View File
@@ -4,6 +4,7 @@ import { RouterLink } from "vue-router";
import { useAuth } from "@/composables/useAuth.js"; import { useAuth } from "@/composables/useAuth.js";
import AdminListingStatus from "@/components/molecules/AdminListingStatus.vue"; import AdminListingStatus from "@/components/molecules/AdminListingStatus.vue";
import InventoryWorkflowStepper from "@/components/molecules/InventoryWorkflowStepper.vue";
import { import {
createAdminInventory, createAdminInventory,
createAdminProduct, createAdminProduct,
@@ -62,6 +63,35 @@ const acquisitions = ref([]);
const dashboard = ref(null); const dashboard = ref(null);
const valuations = ref({}); const valuations = ref({});
const hasValuations = computed(() => Object.keys(valuations.value).length > 0); const hasValuations = computed(() => Object.keys(valuations.value).length > 0);
const workflowSteps = computed(() => {
const completed = [
products.value.length > 0,
providerMappings.value.length > 0,
inventory.value.length > 0,
Boolean(dashboard.value && inventory.value.length && dashboard.value.counts.missingImages === 0),
hasValuations.value,
listings.value.some(({ status }) => status === "AVAILABLE"),
];
const currentIndex = completed.findIndex((isComplete) => !isComplete);
const steps = [
["workflow-product", "Product", "Identify item"],
["workflow-mapping", "Sources", "Link markets"],
["workflow-inventory", "Copy", "Add physical copy"],
["workflow-images", "Images", "Add photos"],
["workflow-pricing", "Pricing", "Check value"],
["workflow-listing", "List", "Publish offer"],
];
return steps.map(([id, label, description], index) => ({
id,
label,
description,
status: completed[index]
? "complete"
: index === currentIndex
? "current"
: "pending",
}));
});
const categories = ref([]); const categories = ref([]);
const conditions = ref([]); const conditions = ref([]);
const isLoading = ref(true); const isLoading = ref(true);
@@ -642,8 +672,10 @@ onMounted(load);
</dl> </dl>
</section> </section>
<InventoryWorkflowStepper :steps="workflowSteps" />
<div class="admin-workspace-grid"> <div class="admin-workspace-grid">
<form class="showcase-panel" @submit.prevent="createProduct"> <form id="workflow-product" class="showcase-panel" @submit.prevent="createProduct">
<h2>Add product</h2> <h2>Add product</h2>
<label class="fixture-label" for="product-name">Name</label> <label class="fixture-label" for="product-name">Name</label>
<input <input
@@ -676,7 +708,7 @@ onMounted(load);
Create product Create product
</button> </button>
</form> </form>
<form class="showcase-panel" @submit.prevent="createProviderMapping"> <form id="workflow-mapping" class="showcase-panel" @submit.prevent="createProviderMapping">
<h2>Link provider mapping</h2> <h2>Link provider mapping</h2>
<label class="fixture-label" for="mapping-product">Product</label> <label class="fixture-label" for="mapping-product">Product</label>
<select <select
@@ -730,7 +762,7 @@ onMounted(load);
Save provider mapping Save provider mapping
</button> </button>
</form> </form>
<form class="showcase-panel" @submit.prevent="createInventory"> <form id="workflow-inventory" class="showcase-panel" @submit.prevent="createInventory">
<h2>Add inventory copy</h2> <h2>Add inventory copy</h2>
<label class="fixture-label" for="inventory-product">Product</label> <label class="fixture-label" for="inventory-product">Product</label>
<select <select
@@ -772,7 +804,7 @@ onMounted(load);
Create copy Create copy
</button> </button>
</form> </form>
<form class="showcase-panel" @submit.prevent="createListing"> <form id="workflow-listing" class="showcase-panel" @submit.prevent="createListing">
<h2>Create listing draft</h2> <h2>Create listing draft</h2>
<label class="fixture-label" for="listing-inventory" <label class="fixture-label" for="listing-inventory"
>Inventory copy</label >Inventory copy</label
@@ -929,7 +961,7 @@ onMounted(load);
Record acquisition Record acquisition
</button> </button>
</form> </form>
<form class="showcase-panel" @submit.prevent="calculatePricing"> <form id="workflow-pricing" class="showcase-panel" @submit.prevent="calculatePricing">
<h2 id="pricing-title">Calculate market value</h2> <h2 id="pricing-title">Calculate market value</h2>
<label class="fixture-label" for="pricing-inventory" <label class="fixture-label" for="pricing-inventory"
>Inventory copy</label >Inventory copy</label
@@ -1047,6 +1079,7 @@ onMounted(load);
class="admin-list" class="admin-list"
aria-labelledby="inventory-title" aria-labelledby="inventory-title"
> >
<span id="workflow-images" aria-hidden="true"></span>
<h2 id="inventory-title">Physical inventory</h2> <h2 id="inventory-title">Physical inventory</h2>
<div class="admin-list__actions"> <div class="admin-list__actions">
<button <button
+1
View File
@@ -166,6 +166,7 @@ Remaining roadmap areas are approved external provider credentials/adapters, bro
- 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. - 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.
- Public catalog filtering now supports URL-synchronized set, event, and tag text filters through a reusable atomic text-filter control and the shared search API. - Public catalog filtering now supports URL-synchronized set, event, and tag text filters through a reusable atomic text-filter control and the shared search API.
- Public catalog filtering now supports explicit Available, Pending, Sold, and All status modes plus a 30-day Newly added toggle through the shared search API and atomic controls. - Public catalog filtering now supports explicit Available, Pending, Sold, and All status modes plus a 30-day Newly added toggle through the shared search API and atomic controls.
- The admin workspace now includes a responsive guided inventory workflow stepper linking Product, Sources, Copy, Images, Pricing, and List stages, with progress derived from loaded admin state.
- Public catalog sorting now supports latest manual/calculated market value low-to-high and high-to-low, with null values placed last. - Public catalog sorting now supports latest manual/calculated market value low-to-high and high-to-low, with null values placed last.
- Public catalog filters now collapse behind an accessible mobile disclosure, show an active-filter count, and provide a reset action while preserving URL synchronization. - Public catalog filters now collapse behind an accessible mobile disclosure, show an active-filter count, and provide a reset action while preserving URL synchronization.