213 lines
5.7 KiB
JavaScript
213 lines
5.7 KiB
JavaScript
import { Automat } from 'react-automat';
|
|
import { warehouse as warehouseApi } from '../api';
|
|
import { requireArray, requireDefined, requireObject } from '../utils/apiShape';
|
|
import authAutomat from './authAutomat';
|
|
|
|
/**
|
|
* @typedef {Object} Warehouse
|
|
* @property {string|number} id Warehouse identifier.
|
|
* @property {string} name Warehouse name.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} WarehouseState
|
|
* @property {Array<Warehouse>} warehouses Company-scoped warehouses.
|
|
* @property {'idle'|'loading'|'ready'|'failed'} status Load lifecycle state.
|
|
* @property {Error|null} error Last load error.
|
|
*/
|
|
|
|
/** @type {WarehouseState} */
|
|
const initialState = {
|
|
warehouses: [],
|
|
status: 'idle',
|
|
error: null,
|
|
};
|
|
|
|
let warehouseAutomat;
|
|
|
|
/**
|
|
* Fetches the company-scoped warehouse list.
|
|
*
|
|
* @returns {Promise<Array<Warehouse>>} Warehouse list.
|
|
*/
|
|
async function fetchWarehouses() {
|
|
const data = await warehouseApi.list();
|
|
requireObject(data, 'GET /api/warehouse');
|
|
return requireArray(data.warehouses, 'warehouses');
|
|
}
|
|
|
|
/** In-flight `load()` promise so concurrent callers share one request. */
|
|
let inflightLoad = null;
|
|
|
|
/**
|
|
* Loads the warehouse list once, deduplicating concurrent callers.
|
|
*
|
|
* @returns {Promise<Array<Warehouse>>} Loaded warehouses.
|
|
*/
|
|
function loadWarehouses() {
|
|
if (inflightLoad) return inflightLoad;
|
|
inflightLoad = (async () => {
|
|
warehouseAutomat.setState({ status: 'loading', error: null });
|
|
try {
|
|
const warehouses = await fetchWarehouses();
|
|
warehouseAutomat.setState({ warehouses, status: 'ready', error: null });
|
|
return warehouses;
|
|
} catch (error) {
|
|
warehouseAutomat.setState({ status: 'failed', error });
|
|
throw error;
|
|
} finally {
|
|
inflightLoad = null;
|
|
}
|
|
})();
|
|
return inflightLoad;
|
|
}
|
|
|
|
const actions = {
|
|
/**
|
|
* Loads the warehouse list, exposing a loading/failed lifecycle.
|
|
*
|
|
* @returns {Promise<Array<Warehouse>>} Loaded warehouses.
|
|
*/
|
|
load() {
|
|
return loadWarehouses();
|
|
},
|
|
|
|
/**
|
|
* Silently re-fetches the warehouse list while keeping the current state.
|
|
*
|
|
* @returns {Promise<Array<Warehouse>>} Fresh warehouses.
|
|
*/
|
|
async reload() {
|
|
try {
|
|
const warehouses = await fetchWarehouses();
|
|
warehouseAutomat.setState({ warehouses, status: 'ready', error: null });
|
|
return warehouses;
|
|
} catch (error) {
|
|
warehouseAutomat.setState({ error });
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a warehouse and refreshes the cached list.
|
|
*
|
|
* @param {{name: string}} body Creation payload.
|
|
* @returns {Promise<Warehouse>} Created warehouse.
|
|
*/
|
|
async create(body) {
|
|
const data = await warehouseApi.create(body);
|
|
requireObject(data, 'POST /api/warehouse');
|
|
requireObject(data.warehouse, 'warehouse');
|
|
requireDefined(data.warehouse.id, 'warehouse.id');
|
|
await warehouseAutomat.actions.reload();
|
|
return data.warehouse;
|
|
},
|
|
|
|
/**
|
|
* Renames a warehouse and refreshes the cached list.
|
|
*
|
|
* @param {string|number} id Warehouse identifier.
|
|
* @param {{name: string}} body Update payload.
|
|
* @returns {Promise<Warehouse>} Updated warehouse.
|
|
*/
|
|
async update(id, body) {
|
|
const data = await warehouseApi.update(id, body);
|
|
requireObject(data, 'PATCH /api/warehouse/:id');
|
|
requireObject(data.warehouse, 'warehouse');
|
|
requireDefined(data.warehouse.id, 'warehouse.id');
|
|
await warehouseAutomat.actions.reload();
|
|
return data.warehouse;
|
|
},
|
|
|
|
/**
|
|
* Deletes a warehouse and refreshes the cached list.
|
|
*
|
|
* @param {string|number} id Warehouse identifier.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async remove(id) {
|
|
await warehouseApi.delete(id);
|
|
await warehouseAutomat.actions.reload();
|
|
},
|
|
|
|
/**
|
|
* Persists a new sort order for all warehouses.
|
|
*
|
|
* @param {Array<string|number>} warehouseIds IDs in the desired order.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async reorder(warehouseIds) {
|
|
await warehouseApi.order({ warehouseIds });
|
|
await warehouseAutomat.actions.reload();
|
|
},
|
|
|
|
/**
|
|
* Replaces the cached list without a round-trip.
|
|
*
|
|
* @param {Array<Warehouse>} warehouses Warehouses to cache.
|
|
* @returns {void}
|
|
*/
|
|
setWarehouses(warehouses) {
|
|
warehouseAutomat.setState({
|
|
warehouses: Array.isArray(warehouses) ? warehouses : [],
|
|
status: 'ready',
|
|
error: null,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Forgets the cached list and returns to the initial state.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
clear() {
|
|
warehouseAutomat.setState({ ...initialState });
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Company-scoped warehouse cache. Loads automatically once an authenticated
|
|
* session with a selected company exists and is cleared when auth is gone.
|
|
*
|
|
* @type {Automat<WarehouseState>}
|
|
*/
|
|
warehouseAutomat = new Automat(initialState, actions, { name: 'warehouse' });
|
|
|
|
/**
|
|
* Derives the cache key from the auth state: the selected company while
|
|
* authenticated, otherwise `null`.
|
|
*
|
|
* @param {import('./authAutomat').AuthState} authState Auth automat state.
|
|
* @returns {string|null} Cache key.
|
|
*/
|
|
function authKey(authState) {
|
|
if (authState?.status !== 'authed' || !authState.user) return null;
|
|
const companyId = authState.user.selectedCompanyId;
|
|
return companyId == null ? null : String(companyId);
|
|
}
|
|
|
|
let lastAuthKey;
|
|
|
|
/**
|
|
* Loads the warehouse list when auth/company becomes available and forgets it
|
|
* when the session ends.
|
|
*
|
|
* @param {import('./authAutomat').AuthState} authState Auth automat state.
|
|
* @returns {void}
|
|
*/
|
|
function syncWithAuth(authState) {
|
|
const key = authKey(authState);
|
|
if (key === lastAuthKey) return;
|
|
lastAuthKey = key;
|
|
if (key == null) {
|
|
warehouseAutomat.actions.clear();
|
|
} else {
|
|
void warehouseAutomat.actions.load().catch(() => {});
|
|
}
|
|
}
|
|
|
|
authAutomat.subscribe(syncWithAuth);
|
|
syncWithAuth(authAutomat.state);
|
|
|
|
export default warehouseAutomat;
|