From 8ff547feaa88eee35abdb62ed5cb7dff98505a9e Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Thu, 10 Sep 2026 03:07:51 -0400 Subject: [PATCH] feat: add inventory workflow stepper --- .../molecules/InventoryWorkflowStepper.scss | 75 +++++++++++++++++++ .../InventoryWorkflowStepper.stories.js | 29 +++++++ .../molecules/InventoryWorkflowStepper.vue | 28 +++++++ apps/web/src/pages/AdminHomePage.vue | 43 +++++++++-- docs/IMPLEMENTATION-STATUS.md | 1 + 5 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/components/molecules/InventoryWorkflowStepper.scss create mode 100644 apps/web/src/components/molecules/InventoryWorkflowStepper.stories.js create mode 100644 apps/web/src/components/molecules/InventoryWorkflowStepper.vue 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); + +
-
+

Add product

-
+

Link provider mapping

-
+

Create listing draft

-
+

Calculate market value

+

Physical inventory