u
This commit is contained in:
@@ -21,7 +21,8 @@ import sseAutomat, { getSseClientId } from './sseAutomat';
|
||||
|
||||
/**
|
||||
* @typedef {Object} BrandState
|
||||
* @property {Array<object>} brands
|
||||
* @property {Array<object>} brands All brands (full list, decoupled from page UI).
|
||||
* @property {number} total Unpaged brand count.
|
||||
* @property {'idle'|'loading'|'succeeded'|'failed'} status
|
||||
* @property {string|null} error
|
||||
*/
|
||||
@@ -29,6 +30,7 @@ import sseAutomat, { getSseClientId } from './sseAutomat';
|
||||
/** @type {BrandState} */
|
||||
const initialState = {
|
||||
brands: [],
|
||||
total: 0,
|
||||
status: 'idle',
|
||||
error: null,
|
||||
};
|
||||
@@ -44,20 +46,33 @@ let brandAutomat;
|
||||
/** In-flight `load()`/`reload()` promise so concurrent callers share one request. */
|
||||
let inflightLoad = null;
|
||||
|
||||
async function fetchBrands() {
|
||||
const res = await brandApi.list();
|
||||
return sortBrands(Array.isArray(res.brands) ? res.brands : []);
|
||||
/** Max page size the API accepts; full list is fetched by paging through. */
|
||||
const FETCH_PAGE_SIZE = 100;
|
||||
|
||||
async function fetchAllBrands() {
|
||||
let offset = 0;
|
||||
let total = Infinity;
|
||||
const collected = [];
|
||||
for (;;) {
|
||||
const res = await brandApi.list({ offset, limit: FETCH_PAGE_SIZE });
|
||||
if (offset === 0) total = Number(res.total ?? res.brands?.length ?? 0);
|
||||
const page = Array.isArray(res.brands) ? res.brands : [];
|
||||
collected.push(...page);
|
||||
offset += page.length;
|
||||
if (page.length === 0 || collected.length >= total) break;
|
||||
}
|
||||
return { brands: sortBrands(collected), total: Number.isFinite(total) ? total : collected.length };
|
||||
}
|
||||
|
||||
const actions = {
|
||||
/** Load all brands for the selected company. */
|
||||
/** Load all brands for the selected company (pages through the API). */
|
||||
async load() {
|
||||
if (inflightLoad) return inflightLoad;
|
||||
brandAutomat.setState({ status: 'loading', error: null });
|
||||
inflightLoad = (async () => {
|
||||
try {
|
||||
const brands = await fetchBrands();
|
||||
brandAutomat.setState({ brands, status: 'succeeded' });
|
||||
const { brands, total } = await fetchAllBrands();
|
||||
brandAutomat.setState({ brands, total, status: 'succeeded' });
|
||||
return brands;
|
||||
} catch (err) {
|
||||
brandAutomat.setState({ status: 'failed', error: err?.message ?? String(err) });
|
||||
@@ -70,7 +85,7 @@ const actions = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Silently re-fetches the brand list while keeping the current state
|
||||
* Silently re-fetches all brands while keeping the current state
|
||||
* visible (no loading flash). Subscribed views re-render from the updated
|
||||
* state — this is the reload pattern for SSE-driven refreshes.
|
||||
*
|
||||
@@ -80,8 +95,8 @@ const actions = {
|
||||
if (inflightLoad) return inflightLoad;
|
||||
inflightLoad = (async () => {
|
||||
try {
|
||||
const brands = await fetchBrands();
|
||||
brandAutomat.setState({ brands, status: 'succeeded', error: null });
|
||||
const { brands, total } = await fetchAllBrands();
|
||||
brandAutomat.setState({ brands, total, status: 'succeeded', error: null });
|
||||
return brands;
|
||||
} catch (err) {
|
||||
brandAutomat.setState({ error: err?.message ?? String(err) });
|
||||
@@ -104,7 +119,7 @@ const actions = {
|
||||
...brandAutomat.state.brands.filter((b) => b.id !== row.id),
|
||||
row,
|
||||
]);
|
||||
brandAutomat.setState({ brands });
|
||||
brandAutomat.setState({ brands, total: brands.length });
|
||||
return row;
|
||||
},
|
||||
|
||||
@@ -130,7 +145,7 @@ const actions = {
|
||||
async remove(id) {
|
||||
await brandApi.delete(id);
|
||||
const brands = brandAutomat.state.brands.filter((b) => b.id !== id);
|
||||
brandAutomat.setState({ brands });
|
||||
brandAutomat.setState({ brands, total: brands.length });
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user