u
This commit is contained in:
@@ -10,15 +10,12 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
||||
import propertyAutomat, { selectProperties } from '../../../automats/propertyAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { article as articleApi } from '../../../api';
|
||||
|
||||
export default class ArticleAttributesTab extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
getArticle() {
|
||||
return this.props.article || getArticle(this.props.sku);
|
||||
}
|
||||
@@ -165,16 +162,15 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
if (!sku) return;
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const api = this.context?.article || articleApi;
|
||||
const [attrRes, propRes] = await Promise.all([
|
||||
api.getAttributeValues(sku),
|
||||
api.getProperties(sku),
|
||||
const res = await Promise.all([
|
||||
articleApi.getAttributeValues(sku),
|
||||
articleApi.getProperties(sku),
|
||||
]);
|
||||
if (this._cancelled) return;
|
||||
|
||||
this.setState({
|
||||
assignedAttributes: Array.isArray(attrRes?.attributevalues) ? attrRes.attributevalues : [],
|
||||
assignedProperties: Array.isArray(propRes?.properties) ? propRes.properties : [],
|
||||
assignedAttributes: Array.isArray(res[0]?.attributevalues) ? res[0].attributevalues : [],
|
||||
assignedProperties: Array.isArray(res[1]?.properties) ? res[1].properties : [],
|
||||
loading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -231,7 +227,6 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
const effectiveKey = selectedOption?.key || selectedMerkmalKey;
|
||||
|
||||
if (saving || !sku || !effectiveKey) return;
|
||||
const api = this.context?.article || articleApi;
|
||||
|
||||
if (effectiveKey.startsWith('attr-')) {
|
||||
const attrId = Number(effectiveKey.replace('attr-', ''));
|
||||
@@ -262,7 +257,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const res = await api.setAttributeValues(sku, nextValues);
|
||||
const res = await articleApi.setAttributeValues(sku, nextValues);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedAttributes: Array.isArray(res?.attributevalues) ? res.attributevalues : [],
|
||||
@@ -304,7 +299,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const res = await api.setProperties(sku, nextProps);
|
||||
const res = await articleApi.setProperties(sku, nextProps);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedProperties: Array.isArray(res?.properties) ? res.properties : [],
|
||||
@@ -331,8 +326,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const api = this.context?.article || articleApi;
|
||||
const res = await api.setAttributeValues(sku, nextValues);
|
||||
const res = await articleApi.setAttributeValues(sku, nextValues);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedAttributes: Array.isArray(res?.attributevalues) ? res.attributevalues : [],
|
||||
@@ -357,8 +351,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const api = this.context?.article || articleApi;
|
||||
const res = await api.setProperties(sku, nextProperties);
|
||||
const res = await articleApi.setProperties(sku, nextProperties);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedProperties: Array.isArray(res?.properties) ? res.properties : [],
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { articleHistoryAutomat } from '../../../automats/articleDetailAutomat';
|
||||
import sseAutomat from '../../../automats/sseAutomat';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import ResultsPagination from '../ResultsPagination';
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
@@ -13,46 +15,68 @@ function formatDate(iso) {
|
||||
return new Date(iso).toLocaleString('de-DE');
|
||||
}
|
||||
|
||||
export default class ArticleHistoryTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
export default class ArticleHistoryTab extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
history: null,
|
||||
total: null,
|
||||
skip: 0,
|
||||
};
|
||||
this.unwatchHistory = null;
|
||||
this.setSkipAndOffset = null;
|
||||
this.state = { all: [], skip: 0 };
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { unwatch, setSkipAndOffset } = this.context.article.historyWatch(
|
||||
this.props.sku,
|
||||
(history, total) => this.setState({ history, total }),
|
||||
);
|
||||
this.unwatchHistory = unwatch;
|
||||
this.setSkipAndOffset = setSkipAndOffset;
|
||||
setSkipAndOffset(0, PAGE_SIZE);
|
||||
this.watch();
|
||||
void articleHistoryAutomat.load(String(this.props.sku)).catch((err) => {
|
||||
if (this.unsub) notifyError(err);
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.sku !== this.props.sku) {
|
||||
this.watch();
|
||||
void articleHistoryAutomat.load(String(this.props.sku)).catch((err) => {
|
||||
if (this.unsub) notifyError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unwatchHistory?.();
|
||||
this.unwatchHistory = null;
|
||||
this.setSkipAndOffset = null;
|
||||
this.unsub?.();
|
||||
this.unsub = null;
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to the keyed history cache and to the article SSE signal.
|
||||
* The backend re-emits `article-<sku>` whenever the article changes, and
|
||||
* `sseAutomat` reloads the history key for us.
|
||||
*/
|
||||
watch() {
|
||||
this.unsub?.();
|
||||
const handle = articleHistoryAutomat.key(String(this.props.sku));
|
||||
this.unsub = handle.subscribe(this, (item) => {
|
||||
const all = Array.isArray(item.data) ? item.data : [];
|
||||
this.setState({ all });
|
||||
});
|
||||
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = sseAutomat.actions.watchArticle(String(this.props.sku), () => {
|
||||
void articleHistoryAutomat.reload(String(this.props.sku)).catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
/** Client-side pagination over the full history list. */
|
||||
pageOf(all, skip) {
|
||||
return all.slice(skip, skip + PAGE_SIZE);
|
||||
}
|
||||
|
||||
handlePageChange(_event, page) {
|
||||
const skip = (page - 1) * PAGE_SIZE;
|
||||
this.setSkipAndOffset?.(skip, PAGE_SIZE);
|
||||
this.setState({ skip });
|
||||
this.setState({ skip: (page - 1) * PAGE_SIZE });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { history, total, skip } = this.state;
|
||||
if (history == null || total == null) return null;
|
||||
const { all, skip } = this.state;
|
||||
const total = all.length;
|
||||
const history = this.pageOf(all, skip);
|
||||
|
||||
const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
const currentPage = Math.floor(skip / PAGE_SIZE) + 1;
|
||||
|
||||
@@ -2,13 +2,11 @@ import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { article as articleApi } from '../../../api';
|
||||
import { artikelAutomat } from '../../../automats';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
|
||||
export default class ArticleSettingsTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { busy: false };
|
||||
@@ -24,7 +22,7 @@ export default class ArticleSettingsTab extends Component {
|
||||
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.context.article.delete(articleData.sku);
|
||||
await articleApi.delete(articleData.sku);
|
||||
artikelAutomat.actions.clearArticle(articleData.sku);
|
||||
onArticleDeleted?.(articleData);
|
||||
} catch (err) {
|
||||
|
||||
@@ -20,15 +20,16 @@ import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { article as articleApi } from '../../../api';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import { formatPrice } from '../../../utils/price';
|
||||
import supplierAutomat from '../../../automats/supplierAutomat';
|
||||
import { articleSupplierAutomat } from '../../../automats/articleDetailAutomat';
|
||||
import sseAutomat from '../../../automats/sseAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import ArticleSupplierDialog from '../ArticleSupplierDialog';
|
||||
|
||||
export default class ArticleSupplierTab extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -40,10 +41,12 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
editItem: null,
|
||||
busyId: null,
|
||||
};
|
||||
this.unwatchSuppliers = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||
company: state.current ?? null,
|
||||
}));
|
||||
this.initData();
|
||||
}
|
||||
|
||||
@@ -54,46 +57,53 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unwatchSuppliers?.();
|
||||
this.unwatchSuppliers = null;
|
||||
this.unsubSuppliers?.();
|
||||
this.unsubSuppliers = null;
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = null;
|
||||
this.unsubCompany?.();
|
||||
this.unsubCompany = null;
|
||||
}
|
||||
|
||||
async initData() {
|
||||
const sku = this.props.article?.sku;
|
||||
if (!sku) return;
|
||||
|
||||
this.unwatchSuppliers?.();
|
||||
this.setState({ loading: true });
|
||||
|
||||
// 1. Subscribe to SSE-backed supplier updates
|
||||
const { unwatch, refresh } = this.context.article.supplierWatch(
|
||||
sku,
|
||||
(assignedSuppliers) => {
|
||||
this.setState({
|
||||
assignedSuppliers: assignedSuppliers ?? [],
|
||||
loading: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
this.unwatchSuppliers = unwatch;
|
||||
this.refreshSuppliers = refresh;
|
||||
|
||||
// 2. Fetch available company suppliers and company metadata
|
||||
try {
|
||||
const [companySuppliers, company] = await Promise.all([
|
||||
supplierAutomat.actions.load(),
|
||||
this.context.company ?? this.context.companyPromise,
|
||||
]);
|
||||
// 1. Subscribe to the keyed supplier cache and the article SSE signal
|
||||
this.unsubSuppliers?.();
|
||||
const handle = articleSupplierAutomat.key(String(sku));
|
||||
this.unsubSuppliers = handle.subscribe(this, (item) => {
|
||||
this.setState({
|
||||
companySuppliers: companySuppliers ?? [],
|
||||
company: company ?? null,
|
||||
assignedSuppliers: Array.isArray(item.data) ? item.data : [],
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
void articleSupplierAutomat.load(String(sku)).catch((err) => {
|
||||
if (this.unsubSuppliers) notifyError(err);
|
||||
});
|
||||
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = sseAutomat.actions.watchArticle(String(sku), () => {
|
||||
void articleSupplierAutomat.reload(String(sku)).catch(() => {});
|
||||
});
|
||||
|
||||
// 2. Fetch available company suppliers
|
||||
try {
|
||||
const companySuppliers = await supplierAutomat.actions.load();
|
||||
this.setState({ companySuppliers: companySuppliers ?? [] });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/** Re-fetches the assigned suppliers for the current article. */
|
||||
refreshSuppliers = async () => {
|
||||
const sku = this.props.article?.sku;
|
||||
if (!sku) return;
|
||||
await articleSupplierAutomat.reload(String(sku));
|
||||
};
|
||||
|
||||
handleOpenAdd = () => {
|
||||
this.setState({ dialogOpen: true, editItem: null });
|
||||
};
|
||||
@@ -112,7 +122,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
|
||||
try {
|
||||
if (payload.id) {
|
||||
await this.context.article.updateSupplier(sku, payload.id, {
|
||||
await articleApi.updateSupplier(sku, payload.id, {
|
||||
sku: payload.sku,
|
||||
pu: payload.pu,
|
||||
stock: payload.stock,
|
||||
@@ -121,7 +131,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
});
|
||||
enqueueSnackbar('Lieferantendaten aktualisiert', { variant: 'success' });
|
||||
} else {
|
||||
await this.context.article.addSupplier(sku, {
|
||||
await articleApi.addSupplier(sku, {
|
||||
entityId: payload.entityId,
|
||||
sku: payload.sku,
|
||||
pu: payload.pu,
|
||||
@@ -132,7 +142,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
enqueueSnackbar('Lieferant erfolgreich zugewiesen', { variant: 'success' });
|
||||
}
|
||||
this.handleCloseDialog();
|
||||
await this.refreshSuppliers?.();
|
||||
await this.refreshSuppliers();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
throw err;
|
||||
@@ -150,9 +160,9 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
|
||||
this.setState({ busyId: item.id });
|
||||
try {
|
||||
await this.context.article.deleteSupplier(sku, item.id);
|
||||
await articleApi.deleteSupplier(sku, item.id);
|
||||
enqueueSnackbar('Lieferant entfernt', { variant: 'success' });
|
||||
await this.refreshSuppliers?.();
|
||||
await this.refreshSuppliers();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
} finally {
|
||||
@@ -176,11 +186,11 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
this.setState({ assignedSuppliers: nextList, busyId: moved.id });
|
||||
|
||||
try {
|
||||
await this.context.article.orderSuppliers(sku, { ids });
|
||||
await this.refreshSuppliers?.();
|
||||
await articleApi.orderSuppliers(sku, ids);
|
||||
await this.refreshSuppliers();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
await this.refreshSuppliers?.();
|
||||
await this.refreshSuppliers();
|
||||
} finally {
|
||||
this.setState({ busyId: null });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user