diff --git a/apps/web/src/components/molecules/InventoryWorkflowStepper.scss b/apps/web/src/components/molecules/InventoryWorkflowStepper.scss
new file mode 100644
index 0000000..9c92804
--- /dev/null
+++ b/apps/web/src/components/molecules/InventoryWorkflowStepper.scss
@@ -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);
+}
diff --git a/apps/web/src/components/molecules/InventoryWorkflowStepper.stories.js b/apps/web/src/components/molecules/InventoryWorkflowStepper.stories.js
new file mode 100644
index 0000000..81f3469
--- /dev/null
+++ b/apps/web/src/components/molecules/InventoryWorkflowStepper.stories.js
@@ -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' },
+ ],
+ },
+};
diff --git a/apps/web/src/components/molecules/InventoryWorkflowStepper.vue b/apps/web/src/components/molecules/InventoryWorkflowStepper.vue
new file mode 100644
index 0000000..c444667
--- /dev/null
+++ b/apps/web/src/components/molecules/InventoryWorkflowStepper.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/apps/web/src/pages/AdminHomePage.vue b/apps/web/src/pages/AdminHomePage.vue
index 9323166..e33cdef 100644
--- a/apps/web/src/pages/AdminHomePage.vue
+++ b/apps/web/src/pages/AdminHomePage.vue
@@ -4,6 +4,7 @@ import { RouterLink } from "vue-router";
import { useAuth } from "@/composables/useAuth.js";
import AdminListingStatus from "@/components/molecules/AdminListingStatus.vue";
+import InventoryWorkflowStepper from "@/components/molecules/InventoryWorkflowStepper.vue";
import {
createAdminInventory,
createAdminProduct,
@@ -62,6 +63,35 @@ const acquisitions = ref([]);
const dashboard = ref(null);
const valuations = ref({});
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 conditions = ref([]);
const isLoading = ref(true);
@@ -642,8 +672,10 @@ onMounted(load);
+