supplierAutomat
This commit is contained in:
@@ -3,3 +3,4 @@ export { default as authAutomat } from './authAutomat';
|
||||
export * from './authAutomat';
|
||||
export { default as navigationAutomat } from './navigationAutomat';
|
||||
export { setLastDeliveryPath, setLastArticleSku, clearNavigation } from './navigationAutomat';
|
||||
export { default as supplierAutomat } from './supplierAutomat';
|
||||
|
||||
201
src/automats/supplierAutomat.js
Normal file
201
src/automats/supplierAutomat.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Automat } from 'react-automat';
|
||||
import { supplier as supplierApi } from '../api';
|
||||
import { requireArray, requireDefined, requireObject } from '../utils/apiShape';
|
||||
import authAutomat from './authAutomat';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Supplier
|
||||
* @property {string|number} id Supplier identifier.
|
||||
* @property {string} name Supplier name.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SupplierState
|
||||
* @property {Array<Supplier>} suppliers Company-scoped suppliers.
|
||||
* @property {'idle'|'loading'|'ready'|'failed'} status Load lifecycle state.
|
||||
* @property {Error|null} error Last load error.
|
||||
*/
|
||||
|
||||
/** @type {SupplierState} */
|
||||
const initialState = {
|
||||
suppliers: [],
|
||||
status: 'idle',
|
||||
error: null,
|
||||
};
|
||||
|
||||
let supplierAutomat;
|
||||
|
||||
/**
|
||||
* Fetches the company-scoped supplier list.
|
||||
*
|
||||
* @returns {Promise<Array<Supplier>>} Supplier list.
|
||||
*/
|
||||
async function fetchSuppliers() {
|
||||
const data = await supplierApi.list();
|
||||
requireObject(data, 'GET /api/supplier');
|
||||
return requireArray(data.suppliers, 'suppliers');
|
||||
}
|
||||
|
||||
/** In-flight `load()` promise so concurrent consumers share one request. */
|
||||
let inflightLoad = null;
|
||||
|
||||
/**
|
||||
* Loads the supplier list once, deduplicating concurrent callers.
|
||||
*
|
||||
* @returns {Promise<Array<Supplier>>} Loaded suppliers.
|
||||
*/
|
||||
function loadSuppliers() {
|
||||
if (inflightLoad) return inflightLoad;
|
||||
inflightLoad = (async () => {
|
||||
supplierAutomat.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const suppliers = await fetchSuppliers();
|
||||
supplierAutomat.setState({ suppliers, status: 'ready', error: null });
|
||||
return suppliers;
|
||||
} catch (error) {
|
||||
supplierAutomat.setState({ status: 'failed', error });
|
||||
throw error;
|
||||
} finally {
|
||||
inflightLoad = null;
|
||||
}
|
||||
})();
|
||||
return inflightLoad;
|
||||
}
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Loads the supplier list, exposing a loading/failed lifecycle.
|
||||
*
|
||||
* @returns {Promise<Array<Supplier>>} Loaded suppliers.
|
||||
*/
|
||||
load() {
|
||||
return loadSuppliers();
|
||||
},
|
||||
|
||||
/**
|
||||
* Silently re-fetches the supplier list while keeping the current state.
|
||||
*
|
||||
* @returns {Promise<Array<Supplier>>} Fresh suppliers.
|
||||
*/
|
||||
async reload() {
|
||||
try {
|
||||
const suppliers = await fetchSuppliers();
|
||||
supplierAutomat.setState({ suppliers, status: 'ready', error: null });
|
||||
return suppliers;
|
||||
} catch (error) {
|
||||
supplierAutomat.setState({ error });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a supplier and refreshes the cached list.
|
||||
*
|
||||
* @param {{name: string}} body Creation payload.
|
||||
* @returns {Promise<Supplier>} Created supplier.
|
||||
*/
|
||||
async create(body) {
|
||||
const data = await supplierApi.create(body);
|
||||
requireObject(data, 'POST /api/supplier');
|
||||
requireObject(data.supplier, 'supplier');
|
||||
requireDefined(data.supplier.id, 'supplier.id');
|
||||
await supplierAutomat.actions.reload();
|
||||
return data.supplier;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renames a supplier and refreshes the cached list.
|
||||
*
|
||||
* @param {string|number} id Supplier identifier.
|
||||
* @param {{name: string}} body Update payload.
|
||||
* @returns {Promise<Supplier>} Updated supplier.
|
||||
*/
|
||||
async update(id, body) {
|
||||
const data = await supplierApi.update(id, body);
|
||||
requireObject(data, 'PATCH /api/supplier/:id');
|
||||
requireObject(data.supplier, 'supplier');
|
||||
requireDefined(data.supplier.id, 'supplier.id');
|
||||
await supplierAutomat.actions.reload();
|
||||
return data.supplier;
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a supplier and refreshes the cached list.
|
||||
*
|
||||
* @param {string|number} id Supplier identifier.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async remove(id) {
|
||||
await supplierApi.delete(id);
|
||||
await supplierAutomat.actions.reload();
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces the cached list without a round-trip.
|
||||
*
|
||||
* @param {Array<Supplier>} suppliers Suppliers to cache.
|
||||
* @returns {void}
|
||||
*/
|
||||
setSuppliers(suppliers) {
|
||||
supplierAutomat.setState({
|
||||
suppliers: Array.isArray(suppliers) ? suppliers : [],
|
||||
status: 'ready',
|
||||
error: null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Forgets the cached list and returns to the initial state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
supplierAutomat.setState({ ...initialState });
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Company-scoped supplier cache. Loads automatically once an authenticated
|
||||
* session with a selected company exists and is cleared when auth is gone.
|
||||
*
|
||||
* @type {Automat<SupplierState>}
|
||||
*/
|
||||
supplierAutomat = new Automat(initialState, actions, { name: 'supplier' });
|
||||
|
||||
/**
|
||||
* 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 supplier 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) {
|
||||
supplierAutomat.actions.clear();
|
||||
} else {
|
||||
void supplierAutomat.actions.load().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
authAutomat.subscribe(syncWithAuth);
|
||||
syncWithAuth(authAutomat.state);
|
||||
|
||||
export default supplierAutomat;
|
||||
Reference in New Issue
Block a user