Files
dashfront/src/components/article/boxes/ArticleViewBrand.jsx
2026-08-06 00:19:21 +02:00

162 lines
4.5 KiB
JavaScript

import { Component, PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { EditablePanelTile } from '../../panels';
import { StorageManagerContext } from '../../../api/storageManagerContext';
import { requireDefined, requireObject } from '../../../utils/apiShape';
import { notifyError } from '../../../utils/notify';
class PanelValue extends Component {
render() {
const { label, children } = this.props;
if (children == null || children === '') return null;
return (
<Box sx={{ mb: 1 }}>
{label ? (
<Typography variant="caption" color="text.secondary" display="block">
{label}
</Typography>
) : null}
<Typography variant="body2" component="div">
{children}
</Typography>
</Box>
);
}
}
function brandDraftValue(article) {
return { name: article?.brand?.name ?? '' };
}
function brandDraftDirty(draft, saved) {
return String(draft.name ?? '').trim() !== String(saved.name ?? '').trim();
}
function brandDraftValid(draft) {
return String(draft.name ?? '').trim().length > 0;
}
const SECOND_ROW_MIN_HEIGHT = 220;
class BrandEdit extends Component {
render() {
const { draft, onChange, inputRef, brands } = this.props;
return (
<Autocomplete
fullWidth
freeSolo
options={brands}
value={draft.name ?? ''}
inputValue={draft.name ?? ''}
getOptionLabel={(option) =>
typeof option === 'string' ? option : String(option.name ?? '')
}
onInputChange={(_event, value) => onChange({ name: value })}
onChange={(_event, option) => {
onChange({ name: option ? String(option.name ?? '') : '' });
}}
renderInput={(params) => (
<TextField
{...params}
inputRef={inputRef}
label="Marke"
size="small"
slotProps={{ inputLabel: { shrink: true } }}
/>
)}
/>
);
}
}
export default class ArticleViewBrand extends PureComponent {
static contextType = StorageManagerContext;
constructor(props) {
super(props);
const brands = this.context?.brands;
this.state = {
brands: brands ?? [],
};
this._cancelled = false;
this._brandsReady = brands != null;
}
componentDidMount() {
this._cancelled = false;
if (this._brandsReady) return;
this.context.brandPromise
.then((list) => {
if (!this._cancelled) this.setState({ brands: list });
})
.catch((err) => {
if (!this._cancelled) notifyError(err);
});
}
componentWillUnmount() {
this._cancelled = true;
}
handleSave = async (draft) => {
const { article: articleData, onArticleUpdated } = this.props;
const store = this.context;
const { brands } = this.state;
const name = String(draft.name ?? '').trim();
if (!name) {
throw new Error('Bitte eine Marke angeben.');
}
const existing = brands.find(
(row) => String(row.name ?? '').toLowerCase() === name.toLowerCase(),
);
let brandId;
if (existing) {
brandId = existing.id;
} else {
const createdRes = await store.brand.upsert({ name });
requireObject(createdRes, 'PUT /api/brand');
requireObject(createdRes.brand, 'brand');
requireDefined(createdRes.brand.id, 'brand.id');
brandId = createdRes.brand.id;
const nextBrands = await store.updateBrand();
this.setState({ brands: nextBrands });
}
const res = await store.article.setBrand(articleData.sku, { brandId });
requireObject(res, 'PUT /api/article/:sku/brand');
requireObject(res.article, 'article');
onArticleUpdated?.(res.article);
};
render() {
const { article: articleData } = this.props;
const { brands } = this.state;
const value = brandDraftValue(articleData);
return (
<EditablePanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e9d5ff"
value={value}
onSave={this.handleSave}
isDirty={brandDraftDirty}
canSave={brandDraftValid}
display={<PanelValue>{value.name || null}</PanelValue>}
edit={({ draft, onChange, inputRef }) => (
<BrandEdit
brands={brands}
draft={draft}
onChange={onChange}
inputRef={inputRef}
/>
)}
/>
);
}
}