Align stock API with paginated backend and bump to v0.2.2
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
+80
-45
@@ -1,9 +1,51 @@
|
||||
import { apiRequest, getPath } from './client.js';
|
||||
|
||||
const DEFAULT_LIST_PAGE_LIMIT = 100;
|
||||
|
||||
function unwrapEntryPayload(payload) {
|
||||
return payload?.data || payload?.entry || payload?.item || payload;
|
||||
}
|
||||
|
||||
function unwrapListPayload(payload) {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return payload?.data || payload?.entries || payload?.items || payload?.groups || [];
|
||||
}
|
||||
|
||||
function hasExplicitPagination(filters = {}) {
|
||||
return (
|
||||
(filters.limit !== undefined && filters.limit !== null)
|
||||
|| (filters.offset !== undefined && filters.offset !== null)
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchAllListPages(store, path, baseQuery = {}) {
|
||||
const items = [];
|
||||
let offset = 0;
|
||||
|
||||
while (true) {
|
||||
const payload = await apiRequest(store, path, {
|
||||
query: {
|
||||
...baseQuery,
|
||||
limit: DEFAULT_LIST_PAGE_LIMIT,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
const pageItems = unwrapListPayload(payload);
|
||||
items.push(...pageItems);
|
||||
|
||||
if (pageItems.length < DEFAULT_LIST_PAGE_LIMIT) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset += DEFAULT_LIST_PAGE_LIMIT;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export async function searchItemDefinitions(store, query) {
|
||||
if (query.trim().length <= 2) {
|
||||
return [];
|
||||
@@ -21,59 +63,57 @@ export async function searchItemDefinitions(store, query) {
|
||||
}
|
||||
|
||||
export async function listStockEntries(store, filters = {}) {
|
||||
const query = {};
|
||||
const baseQuery = {};
|
||||
const searchName = filters.searchName || filters.search_name;
|
||||
if (searchName) {
|
||||
query.search_name = searchName;
|
||||
}
|
||||
if (filters.limit !== undefined && filters.limit !== null) {
|
||||
query.limit = filters.limit;
|
||||
}
|
||||
if (filters.offset !== undefined && filters.offset !== null) {
|
||||
query.offset = filters.offset;
|
||||
}
|
||||
if (filters.cursor) {
|
||||
query.cursor = filters.cursor;
|
||||
baseQuery.search_name = searchName;
|
||||
}
|
||||
|
||||
const payload = await apiRequest(store, getPath('items'), {
|
||||
query,
|
||||
});
|
||||
if (hasExplicitPagination(filters)) {
|
||||
const query = { ...baseQuery };
|
||||
if (filters.limit !== undefined && filters.limit !== null) {
|
||||
query.limit = filters.limit;
|
||||
}
|
||||
if (filters.offset !== undefined && filters.offset !== null) {
|
||||
query.offset = filters.offset;
|
||||
}
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
const payload = await apiRequest(store, getPath('items'), {
|
||||
query,
|
||||
});
|
||||
|
||||
return unwrapListPayload(payload);
|
||||
}
|
||||
|
||||
return payload?.data || payload?.entries || payload?.items || [];
|
||||
return fetchAllListPages(store, getPath('items'), baseQuery);
|
||||
}
|
||||
|
||||
export async function listGroupedStockEntries(store, options = {}) {
|
||||
const query = {};
|
||||
const baseQuery = {};
|
||||
const expanded = options.expanded ?? 1;
|
||||
query.expanded = expanded;
|
||||
baseQuery.expanded = expanded;
|
||||
const searchName = options.searchName || options.search_name;
|
||||
if (searchName) {
|
||||
query.search_name = searchName;
|
||||
}
|
||||
if (options.limit !== undefined && options.limit !== null) {
|
||||
query.limit = options.limit;
|
||||
}
|
||||
if (options.offset !== undefined && options.offset !== null) {
|
||||
query.offset = options.offset;
|
||||
}
|
||||
if (options.cursor) {
|
||||
query.cursor = options.cursor;
|
||||
baseQuery.search_name = searchName;
|
||||
}
|
||||
|
||||
const payload = await apiRequest(store, `${getPath('items')}/grouped`, {
|
||||
query,
|
||||
});
|
||||
if (hasExplicitPagination(options)) {
|
||||
const query = { ...baseQuery };
|
||||
if (options.limit !== undefined && options.limit !== null) {
|
||||
query.limit = options.limit;
|
||||
}
|
||||
if (options.offset !== undefined && options.offset !== null) {
|
||||
query.offset = options.offset;
|
||||
}
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
const payload = await apiRequest(store, `${getPath('items')}/grouped`, {
|
||||
query,
|
||||
});
|
||||
|
||||
return unwrapListPayload(payload);
|
||||
}
|
||||
|
||||
return payload?.data || payload?.entries || payload?.items || payload?.groups || [];
|
||||
return fetchAllListPages(store, `${getPath('items')}/grouped`, baseQuery);
|
||||
}
|
||||
|
||||
export async function getStockEntry(store, stockId) {
|
||||
@@ -183,11 +223,11 @@ export async function patchStockItem(store, uuidB64, body) {
|
||||
}
|
||||
|
||||
export async function updateStockItem(store, uuidB64, body) {
|
||||
const payload = await apiRequest(store, `${getPath('items')}/${uuidB64}/stock`, {
|
||||
await apiRequest(store, `${getPath('items')}/${uuidB64}/stock`, {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
return unwrapEntryPayload(payload);
|
||||
return getStockEntry(store, uuidB64);
|
||||
}
|
||||
|
||||
export async function deleteStockItem(store, uuidB64) {
|
||||
@@ -205,25 +245,20 @@ export async function useStockItem(store, uuidB64) {
|
||||
return { status: 'used' };
|
||||
} catch (error) {
|
||||
const status = error?.status || error?.cause?.status;
|
||||
if (status === 409) {
|
||||
if (status === 409 || status === 404) {
|
||||
return { status: 'already_gone' };
|
||||
}
|
||||
|
||||
if (status === 404 || status === 405) {
|
||||
await deleteStockItem(store, uuidB64);
|
||||
return { status: 'fallback_delete' };
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function adjustStockEntry(store, stockId, body) {
|
||||
const payload = await apiRequest(store, `${getPath('items')}/${stockId}/stock`, {
|
||||
await apiRequest(store, `${getPath('items')}/${stockId}/stock`, {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
return unwrapEntryPayload(payload);
|
||||
return getStockEntry(store, stockId);
|
||||
}
|
||||
|
||||
export async function listKitchenChanges(store, { since, limit = 10 } = {}) {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
export const APP_NAME = 'Lonc';
|
||||
export const APP_VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.2.1';
|
||||
export const APP_VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.2.2';
|
||||
export const TRYTON_APPLICATION = 'kitchen';
|
||||
|
||||
export const CONNECTION_STATES = {
|
||||
|
||||
@@ -257,6 +257,23 @@ function groupedFirstExpireDate(group) {
|
||||
return group.first_expire_date || group.expire_date;
|
||||
}
|
||||
|
||||
function isGroupedChildStub(item) {
|
||||
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(
|
||||
'uuid_b64' in item
|
||||
|| 'name' in item
|
||||
|| 'stock_type' in item
|
||||
|| 'date' in item
|
||||
|| 'expire_date' in item
|
||||
|| 'location_initial_uuid_b64' in item
|
||||
|| 'quantity' in item
|
||||
|| 'level' in item
|
||||
);
|
||||
}
|
||||
|
||||
function shortDescription(value, maxLength = 24) {
|
||||
if (!value) {
|
||||
return 'No description';
|
||||
@@ -744,9 +761,9 @@ export function renderStockListPage() {
|
||||
|
||||
<template x-if="isGroupedCardOpen(group.id)">
|
||||
<div>
|
||||
<template x-if="group.items?.length">
|
||||
<template x-if="groupDisplayItems(group).length">
|
||||
<div class="grouped-stock-items">
|
||||
<template x-for="item in group.items" :key="item.id">
|
||||
<template x-for="item in groupDisplayItems(group)" :key="item.id">
|
||||
<div class="grouped-stock-item" :class="[groupedItemClass(item), isItemRefreshing(item) ? 'stock-item-refreshing' : '']">
|
||||
<div class="grouped-stock-item-row">
|
||||
<div class="grouped-stock-item-main">
|
||||
@@ -792,10 +809,10 @@ export function renderStockListPage() {
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!group.items?.length && groupedHydrating">
|
||||
<template x-if="!groupDisplayItems(group).length && (groupedHydrating || hasGroupedChildStubs(group))">
|
||||
<div class="small text-body-secondary py-2">Loading grouped items...</div>
|
||||
</template>
|
||||
<template x-if="!group.items?.length && !groupedHydrating">
|
||||
<template x-if="!groupDisplayItems(group).length && !groupedHydrating && !hasGroupedChildStubs(group)">
|
||||
<div class="small text-body-secondary py-2">No items currently available in this group.</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -1345,7 +1362,11 @@ export function stockListPageData(store) {
|
||||
const existingById = new Map(this.groupedEntries.map((group) => [group.id, group]));
|
||||
const nextGroups = loadedGroups.map((group) => {
|
||||
const existing = existingById.get(group.id);
|
||||
const preservedItems = Array.isArray(group.items) ? group.items : existing?.items || [];
|
||||
const incomingItems = Array.isArray(group.items) ? group.items : [];
|
||||
const hasDetailedIncomingItems = incomingItems.some((item) => !isGroupedChildStub(item));
|
||||
const preservedItems = hasDetailedIncomingItems
|
||||
? incomingItems
|
||||
: existing?.items || incomingItems;
|
||||
return this.indexGroup({
|
||||
...existing,
|
||||
...group,
|
||||
@@ -1369,7 +1390,11 @@ export function stockListPageData(store) {
|
||||
const existingById = new Map(this.groupedEntries.map((group) => [group.id, group]));
|
||||
const nextGroups = loadedGroups.map((group) => {
|
||||
const existing = existingById.get(group.id);
|
||||
const mergedItems = Array.isArray(group.items) ? group.items : existing?.items || [];
|
||||
const incomingItems = Array.isArray(group.items) ? group.items : [];
|
||||
const hasDetailedIncomingItems = incomingItems.some((item) => !isGroupedChildStub(item));
|
||||
const mergedItems = hasDetailedIncomingItems
|
||||
? incomingItems
|
||||
: existing?.items || incomingItems;
|
||||
return this.indexGroup({
|
||||
...existing,
|
||||
...group,
|
||||
@@ -1487,6 +1512,20 @@ export function stockListPageData(store) {
|
||||
showMoreGroups() {
|
||||
this.groupedVisibleLimit += this.groupedPageSize;
|
||||
},
|
||||
groupDisplayItems(group) {
|
||||
if (!Array.isArray(group?.items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return group.items.filter((item) => !isGroupedChildStub(item));
|
||||
},
|
||||
hasGroupedChildStubs(group) {
|
||||
if (!Array.isArray(group?.items)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return group.items.some((item) => isGroupedChildStub(item));
|
||||
},
|
||||
groupItemCount(group) {
|
||||
if (Number.isFinite(group.items_count)) {
|
||||
return group.items_count;
|
||||
|
||||
Reference in New Issue
Block a user