diff --git a/README.md b/README.md index f9bd70b..4cd51e0 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Project-specific operating conventions for future contributors and coding agents - Active kitchen selection and switching - Dashboard with quick actions - Label creation flow with item lookup, location loading, preview, and stock entry creation -- Stock list with search and filters +- Grouped-first stock review with search and overview filters - Stock detail page with stock adjustment workflow - PWA manifest, icons, service worker, and offline fallback @@ -165,6 +165,8 @@ Expected shapes today: Returns item definitions for autocomplete. - `GET /{database}/kitchen/items` Returns the current stock review list. +- `GET /{database}/kitchen/items/grouped?expanded=0|1` + Returns grouped stock data; grouped review uses summary-first loading and hydrates item children in background. - `GET /{database}/kitchen/items/{uuid_b64}` Returns one item detail payload. - `GET /{database}/kitchen/changes` diff --git a/src/api/stock.js b/src/api/stock.js index 4a4036c..fd8e2c7 100644 --- a/src/api/stock.js +++ b/src/api/stock.js @@ -21,7 +21,24 @@ export async function searchItemDefinitions(store, query) { } export async function listStockEntries(store, filters = {}) { - const payload = await apiRequest(store, getPath('items')); + const query = {}; + 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; + } + + const payload = await apiRequest(store, getPath('items'), { + query, + }); if (Array.isArray(payload)) { return payload; @@ -30,9 +47,26 @@ export async function listStockEntries(store, filters = {}) { return payload?.data || payload?.entries || payload?.items || []; } -export async function listGroupedStockEntries(store) { +export async function listGroupedStockEntries(store, options = {}) { + const query = {}; + const expanded = options.expanded ?? 1; + query.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; + } + const payload = await apiRequest(store, `${getPath('items')}/grouped`, { - query: { expanded: 1 }, + query, }); if (Array.isArray(payload)) { diff --git a/src/features/stock/stock-list-page.js b/src/features/stock/stock-list-page.js index 677091e..9058f0d 100644 --- a/src/features/stock/stock-list-page.js +++ b/src/features/stock/stock-list-page.js @@ -1,11 +1,12 @@ import { listGroupedStockEntries, + listKitchenChanges, listStockEntries, updateStockItem, useStockItem, } from '../../api/stock.js'; import { fetchLocations } from '../../api/locations.js'; -import { createAsyncState, runAsyncState } from '../shared/ui-state.js'; +import { createAsyncState } from '../shared/ui-state.js'; import { formatDate } from '../shared/date-utils.js'; const LEVEL_LABELS = { @@ -35,6 +36,8 @@ const EXPIRATION_LEGEND = [ ]; const EXPIRATION_KEYS = EXPIRATION_LEGEND.map((state) => state.key); +const GROUPED_PAGE_SIZE = 24; +const CHANGE_POLL_INTERVAL_MS = 60 * 1000; function todayAtMidnight() { const now = new Date(); @@ -251,34 +254,48 @@ export function renderStockListPage() {
-
-
+
+
+
Stock view mode
+
+ + +
+
+
-
- +
+
+ Updating in background...
@@ -294,7 +311,7 @@ export function renderStockListPage() {
@@ -421,7 +438,7 @@ export function renderStockListPage() {
-