feat: expose bulk archive in admin workspace
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-09 23:48:34 -04:00
parent 12eced8b19
commit e0d231facc
4 changed files with 83 additions and 1 deletions
@@ -13,3 +13,14 @@ export const ProviderMappingWorkflow = {
}, },
}, },
}; };
export const BulkArchiveSelection = {
parameters: {
docs: {
description: {
story:
"The physical inventory section supports selecting active copies and archiving them in one authenticated operation.",
},
},
},
};
+41 -1
View File
@@ -7,6 +7,7 @@ import AdminListingStatus from "@/components/molecules/AdminListingStatus.vue";
import { import {
createAdminInventory, createAdminInventory,
createAdminProduct, createAdminProduct,
archiveAdminInventory,
listAdminCategories, listAdminCategories,
listAdminConditions, listAdminConditions,
listAdminInventory, listAdminInventory,
@@ -47,6 +48,7 @@ import {
const { principal } = useAuth(); const { principal } = useAuth();
const products = ref([]); const products = ref([]);
const inventory = ref([]); const inventory = ref([]);
const selectedInventoryIds = ref([]);
const listings = ref([]); const listings = ref([]);
const sales = ref([]); const sales = ref([]);
const acquisitions = ref([]); const acquisitions = ref([]);
@@ -193,6 +195,24 @@ async function createInventory() {
} }
} }
async function archiveSelectedInventory() {
if (!selectedInventoryIds.value.length) return;
isSaving.value = true;
try {
const archivedItems = await archiveAdminInventory(
selectedInventoryIds.value,
csrfToken(),
);
const archivedById = new Map(archivedItems.map((item) => [item.id, item]));
inventory.value = inventory.value.map((item) => archivedById.get(item.id) ?? item);
selectedInventoryIds.value = [];
} catch (requestError) {
error.value = requestError;
} finally {
isSaving.value = false;
}
}
async function createListing() { async function createListing() {
const item = inventory.value.find( const item = inventory.value.find(
(candidate) => candidate.id === listingInventoryId.value, (candidate) => candidate.id === listingInventoryId.value,
@@ -927,6 +947,16 @@ onMounted(load);
aria-labelledby="inventory-title" aria-labelledby="inventory-title"
> >
<h2 id="inventory-title">Physical inventory</h2> <h2 id="inventory-title">Physical inventory</h2>
<div class="admin-list__actions">
<button
class="button button--secondary"
type="button"
:disabled="isSaving || !selectedInventoryIds.length"
@click="archiveSelectedInventory"
>
Archive selected
</button>
</div>
<p v-if="!inventory.length && !isLoading" class="state state--empty"> <p v-if="!inventory.length && !isLoading" class="state state--empty">
No physical copies yet. No physical copies yet.
</p> </p>
@@ -936,7 +966,17 @@ onMounted(load);
:key="item.id" :key="item.id"
:data-product-id="item.productId" :data-product-id="item.productId"
> >
<span>{{ item.id }}</span> · {{ item.status }} <label v-if="item.status === 'ACTIVE'">
<input
v-model="selectedInventoryIds"
type="checkbox"
:value="item.id"
:aria-label="`Select inventory item ${item.id}`"
/>
<span>{{ item.id }}</span>
</label>
<span v-else>{{ item.id }}</span>
· {{ item.status }}
<RouterLink <RouterLink
:to="{ name: 'admin-image-manager', params: { id: item.id } }" :to="{ name: 'admin-image-manager', params: { id: item.id } }"
>Manage images</RouterLink >Manage images</RouterLink
@@ -23,3 +23,11 @@ export function createAdminProduct(input, csrfToken) {
export function createAdminInventory(input, csrfToken) { export function createAdminInventory(input, csrfToken) {
return request('/admin/inventory', { method: 'POST', headers: { 'x-csrf-token': csrfToken }, body: JSON.stringify(input) }); return request('/admin/inventory', { method: 'POST', headers: { 'x-csrf-token': csrfToken }, body: JSON.stringify(input) });
} }
export function archiveAdminInventory(inventoryItemIds, csrfToken) {
return request('/admin/bulk', {
method: 'POST',
headers: { 'x-csrf-token': csrfToken },
body: JSON.stringify({ action: 'ARCHIVE', inventoryItemIds }),
});
}
+23
View File
@@ -46,3 +46,26 @@ test('fetches a public product using its stable slug URL', async () => {
globalThis.fetch = originalFetch; globalThis.fetch = originalFetch;
} }
}); });
test('archives selected admin inventory items through the bulk API', async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options) => {
assert.equal(url, '/api/v1/admin/bulk');
assert.equal(options.method, 'POST');
assert.equal(options.headers['x-csrf-token'], 'csrf-token');
assert.deepEqual(JSON.parse(options.body), {
action: 'ARCHIVE',
inventoryItemIds: ['item-1'],
});
return { ok: true, async json() { return { data: [{ id: 'item-1', status: 'ARCHIVED' }] }; } };
};
try {
const { archiveAdminInventory } = await import('../src/services/admin-catalog-service.js');
assert.deepEqual(await archiveAdminInventory(['item-1'], 'csrf-token'), [
{ id: 'item-1', status: 'ARCHIVED' },
]);
} finally {
globalThis.fetch = originalFetch;
}
});