diff --git a/package-lock.json b/package-lock.json
index 0409aef..64463f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@shopify/draggable": "^1.2.1",
"dashgui": "file:../dashgui",
"eslint": "^10.11.0",
+ "jsbarcode": "^3.12.3",
"notistack": "^3.0.2",
"pdfconvert-wasm": "^0.3.0",
"react": "^19.3.0",
@@ -5415,6 +5416,12 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
+ "node_modules/jsbarcode": {
+ "version": "3.12.3",
+ "resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.12.3.tgz",
+ "integrity": "sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg==",
+ "license": "MIT"
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
diff --git a/package.json b/package.json
index 7adbca6..f437f7d 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"@shopify/draggable": "^1.2.1",
"dashgui": "file:../dashgui",
"eslint": "^10.11.0",
+ "jsbarcode": "^3.12.3",
"notistack": "^3.0.2",
"pdfconvert-wasm": "^0.3.0",
"react": "^19.3.0",
diff --git a/src/api/ApiClient.js b/src/api/ApiClient.js
index 514fe8e..10954d1 100644
--- a/src/api/ApiClient.js
+++ b/src/api/ApiClient.js
@@ -442,6 +442,19 @@ export class ApiClient {
update: (id, body) => this.#json('PATCH', `/api/number/${enc(id)}`, { body }),
};
+ this.printer = {
+ /** GET /api/printer → { printers: Printer[] } */
+ list: () => this.#json('GET', '/api/printer'),
+ /** POST /api/printer → 201 { printer } */
+ create: (body) => this.#json('POST', '/api/printer', { body }),
+ /** PATCH /api/printer/{id} → { printer } */
+ update: (id, body) => this.#json('PATCH', `/api/printer/${enc(id)}`, { body }),
+ /** DELETE /api/printer/{id} → 204 */
+ delete: (id) => this.#empty('DELETE', `/api/printer/${enc(id)}`),
+ /** PUT /api/printer/order body { printerIds } → { printerIds } */
+ order: (body) => this.#json('PUT', '/api/printer/order', { body }),
+ };
+
this.setting = {
/** GET /api/company/setting?frontendOnly=1 → { settings: [{ key, value, frontend }] } */
list: (query) => this.#json('GET', '/api/company/setting', { query }),
diff --git a/src/api/index.js b/src/api/index.js
index 5b1d970..0da6f97 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -581,6 +581,14 @@ export const brand = {
upsert(body) {
return http('/api/brand', { method: 'PUT', body });
},
+
+ /**
+ * DELETE /api/brand/:id
+ * → 204 No Content · 404 Brand not found
+ */
+ delete(id) {
+ return http(`/api/brand/${encodeURIComponent(id)}`, { method: 'DELETE' });
+ },
};
/**
diff --git a/src/appRouter.jsx b/src/appRouter.jsx
index aed4c07..835b4cd 100644
--- a/src/appRouter.jsx
+++ b/src/appRouter.jsx
@@ -7,6 +7,7 @@ const ArtikelUebersichtPage = lazy(() => import('./pages/artikel/index'));
const ArtikelKategorienPage = lazy(() => import('./pages/artikel/kategorien'));
const RedirectArtikelKategorien = lazy(() => import('./pages/artikel/RedirectArtikelKategorien'));
const ArtikelLagerPage = lazy(() => import('./pages/artikel/lager'));
+const ArtikelMarkenPage = lazy(() => import('./pages/artikel/marken'));
const ArtikelSkuPage = lazy(() => import('./pages/artikel/sku'));
const LieferungListePage = lazy(() => import('./pages/lieferung/index'));
const LieferungNeuPage = lazy(() => import('./pages/lieferung/neu'));
@@ -53,6 +54,7 @@ const routes = [
{ index: true, element: },
{ path: 'kategorien/*', element: },
{ path: 'lager', element: },
+ { path: 'marken', element: },
{ path: ':sku/:tab?', element: },
],
},
diff --git a/src/automats/brandAutomat.js b/src/automats/brandAutomat.js
index a521635..b910b57 100644
--- a/src/automats/brandAutomat.js
+++ b/src/automats/brandAutomat.js
@@ -54,6 +54,16 @@ const actions = {
return row;
},
+ /**
+ * Delete a brand by id.
+ * @param {number} id
+ */
+ async remove(id) {
+ await brandApi.delete(id);
+ const brands = brandAutomat.state.brands.filter((b) => b.id !== id);
+ brandAutomat.setState({ brands });
+ },
+
/** Reset to idle state (called on logout / company switch). */
reset() {
brandAutomat.setState(initialState);
diff --git a/src/components/article/boxes/ArticleViewEANs.jsx b/src/components/article/boxes/ArticleViewEANs.jsx
index e4a8f4b..f3f60dd 100644
--- a/src/components/article/boxes/ArticleViewEANs.jsx
+++ b/src/components/article/boxes/ArticleViewEANs.jsx
@@ -8,6 +8,7 @@ import { StorageManagerContext } from '../../../api/storageManagerContext';
import { requireObject } from '../../../utils/apiShape';
import { articleEans } from '../../../utils/articleDetail';
import { notifyError } from '../../../utils/notify';
+import JsBarcode from 'jsbarcode';
class PanelValue extends Component {
render() {
const { label, children } = this.props;
@@ -41,20 +42,63 @@ function eansDraftValid(draft) {
);
}
+class SkuBarcode extends PureComponent {
+ componentDidMount() {
+ this.renderBarcode();
+ }
+
+ componentDidUpdate() {
+ this.renderBarcode();
+ }
+
+ renderBarcode() {
+ if (!this._svg || !this.props.value) return;
+ try {
+ JsBarcode(this._svg, String(this.props.value).trim(), {
+ format: 'CODE128',
+ width: 1.5,
+ height: 38,
+ displayValue: false,
+ margin: 0,
+ background: 'transparent',
+ lineColor: '#1e293b',
+ });
+ } catch (e) {
+ console.warn('Barcode render error:', e);
+ }
+ }
+
+ render() {
+ const { value } = this.props;
+ if (!value) return null;
+ return (
+
+
+ );
+ }
+}
+
const SECOND_ROW_MIN_HEIGHT = 220;
class EansDisplay extends Component {
render() {
const { sku, eans } = this.props;
return (
- <>
+
{sku}
+
{(eans ?? []).map((ean) => (
{ean}
))}
- >
+
);
}
}
@@ -65,7 +109,8 @@ class EansEdit extends Component {
return (
{sku}
-
+
+
({ form: { ...prev.form, [field]: value }, error: null }));
+ }
+
+ async handleSubmit() {
+ const { form } = this.state;
+ const validationError = validateForm(form);
+ if (validationError) {
+ this.setState({ error: validationError });
+ return;
+ }
+ this.setState({ busy: true, error: null });
+ const body = {
+ address: form.address.trim(),
+ type: form.type,
+ model: form.model.trim() || null,
+ serial: form.serial.trim() || null,
+ desc: form.desc.trim() || null,
+ };
+ try {
+ await this.props.onSubmit(body);
+ } catch (err) {
+ if (!this._unmounted) {
+ this.setState({ busy: false, error: backendErrorFrom(err) });
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ this._unmounted = true;
+ }
+
+ render() {
+ const { open, onClose, title } = this.props;
+ const { form, busy, error } = this.state;
+ return (
+
+ );
+ }
+}
+
+export default class PrinterSettingsPanel extends Component {
+ static contextType = StorageManagerContext;
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ status: 'loading',
+ error: null,
+ printers: [],
+ draggingId: null,
+ dragOverId: null,
+ addOpen: false,
+ editPrinter: null,
+ deleteId: null,
+ deleteBusy: false,
+ };
+ this.handleRetry = this.handleRetry.bind(this);
+ this.handleAdd = this.handleAdd.bind(this);
+ this.handleAddSubmit = this.handleAddSubmit.bind(this);
+ this.handleEditSubmit = this.handleEditSubmit.bind(this);
+ this.handleCloseAdd = this.handleCloseAdd.bind(this);
+ this.handleCloseEdit = this.handleCloseEdit.bind(this);
+ }
+
+ componentDidMount() {
+ void this.load();
+ }
+
+ async load() {
+ this.setState({ status: 'loading', error: null });
+ try {
+ const data = await this.context.printer.list();
+ requireObject(data, 'GET /api/printer');
+ const printers = requireArray(data.printers, 'printers');
+ this.setState({ status: 'ready', printers });
+ } catch (err) {
+ notifyError(err);
+ this.setState({ status: 'failed', error: backendErrorFrom(err) });
+ }
+ }
+
+ handleRetry() {
+ void this.load();
+ }
+
+ handleAdd() {
+ this.setState({ addOpen: true });
+ }
+
+ handleCloseAdd() {
+ this.setState({ addOpen: false });
+ }
+
+ handleCloseEdit() {
+ this.setState({ editPrinter: null });
+ }
+
+ async handleAddSubmit(body) {
+ const data = await this.context.printer.create(body);
+ requireObject(data, 'POST /api/printer');
+ const printer = data.printer;
+ this.setState((prev) => ({
+ printers: [...prev.printers, printer],
+ addOpen: false,
+ }));
+ enqueueSnackbar('Drucker hinzugefügt.', { variant: 'success' });
+ }
+
+ async handleEditSubmit(body) {
+ const { editPrinter } = this.state;
+ const data = await this.context.printer.update(editPrinter.id, body);
+ requireObject(data, 'PATCH /api/printer/:id');
+ const updated = data.printer;
+ this.setState((prev) => ({
+ printers: prev.printers.map((p) => (p.id === updated.id ? updated : p)),
+ editPrinter: null,
+ }));
+ enqueueSnackbar('Drucker gespeichert.', { variant: 'success' });
+ }
+
+ async handleDelete(id) {
+ this.setState({ deleteId: id, deleteBusy: true });
+ try {
+ await this.context.printer.delete(id);
+ this.setState((prev) => ({
+ printers: prev.printers.filter((p) => p.id !== id),
+ deleteId: null,
+ deleteBusy: false,
+ }));
+ enqueueSnackbar('Drucker gelöscht.', { variant: 'success' });
+ } catch (err) {
+ if (this._unmounted) return;
+ notifyError(err);
+ this.setState({ deleteId: null, deleteBusy: false });
+ }
+ }
+
+ handleDragStart(id) {
+ this.setState({ draggingId: id, dragOverId: null });
+ }
+
+ handleDragOver(e, id) {
+ e.preventDefault();
+ if (id !== this.state.dragOverId) {
+ this.setState({ dragOverId: id });
+ }
+ }
+
+ handleDragLeave() {
+ this.setState({ dragOverId: null });
+ }
+
+ async handleDrop(targetId) {
+ const { draggingId, printers } = this.state;
+ this.setState({ draggingId: null, dragOverId: null });
+ if (!draggingId || draggingId === targetId) return;
+
+ const fromIdx = printers.findIndex((p) => p.id === draggingId);
+ const toIdx = printers.findIndex((p) => p.id === targetId);
+ if (fromIdx === -1 || toIdx === -1) return;
+
+ const reordered = [...printers];
+ const [item] = reordered.splice(fromIdx, 1);
+ reordered.splice(toIdx, 0, item);
+ const printerIds = reordered.map((p) => p.id);
+
+ this.setState({ printers: reordered });
+
+ try {
+ await this.context.printer.order({ printerIds });
+ } catch (err) {
+ if (this._unmounted) return;
+ notifyError(err);
+ this.setState({ printers });
+ }
+ }
+
+ handleDragEnd() {
+ this.setState({ draggingId: null, dragOverId: null });
+ }
+
+ componentWillUnmount() {
+ this._unmounted = true;
+ }
+
+ render() {
+ const {
+ status, error, printers,
+ draggingId, dragOverId,
+ addOpen, editPrinter,
+ deleteId, deleteBusy,
+ } = this.state;
+
+ if (status === 'loading') {
+ return (
+
+
+
+ );
+ }
+ if (status === 'failed') {
+ return ;
+ }
+
+ return (
+ <>
+
+ }
+ onClick={this.handleAdd}
+ >
+ Drucker hinzufügen
+
+
+
+ {printers.length === 0 ? (
+
+ Noch keine Drucker konfiguriert.
+
+ ) : (
+
+
+
+
+
+ Adresse
+ Typ
+ Modell
+ Beschreibung
+
+
+
+
+ {printers.map((printer) => {
+ const isDeleting = deleteId === printer.id && deleteBusy;
+ const isDragging = draggingId === printer.id;
+ const isDragOver = dragOverId === printer.id;
+ return (
+ this.handleDragStart(printer.id)}
+ onDragOver={(e) => this.handleDragOver(e, printer.id)}
+ onDragLeave={() => this.handleDragLeave()}
+ onDrop={() => this.handleDrop(printer.id)}
+ onDragEnd={() => this.handleDragEnd()}
+ sx={{
+ opacity: isDragging ? 0.4 : 1,
+ outline: isDragOver ? '2px solid' : 'none',
+ outlineColor: 'primary.main',
+ cursor: 'grab',
+ transition: 'opacity 0.15s',
+ }}
+ >
+
+
+
+
+ {printer.address}
+
+
+
+ {printer.type}
+
+
+
+
+ {printer.model ?? '—'}
+
+
+
+
+ {printer.desc ?? '—'}
+
+
+
+
+ this.setState({ editPrinter: printer })}
+ >
+
+
+ void this.handleDelete(printer.id)}
+ >
+ {isDeleting ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+
+
+ Reihenfolge durch Ziehen ändern. Unterstützte Typen: esc/pos, zpl.
+
+
+
+
+
+ >
+ );
+ }
+}
diff --git a/src/components/settings/SettingsDialog.jsx b/src/components/settings/SettingsDialog.jsx
index e298172..5acddad 100644
--- a/src/components/settings/SettingsDialog.jsx
+++ b/src/components/settings/SettingsDialog.jsx
@@ -6,18 +6,22 @@ import DialogTitle from '@mui/material/DialogTitle';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import NumbersIcon from '@mui/icons-material/Numbers';
+import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined';
import TuneIcon from '@mui/icons-material/Tune';
import NumberSettingsPanel from './NumberSettingsPanel';
+import PrinterSettingsPanel from './PrinterSettingsPanel';
import SettingsKeysPanel from './SettingsKeysPanel';
const TAB_KEYS = {
keys: 'keys',
numbers: 'numbers',
+ printers: 'printers',
};
const TABS = [
{ id: TAB_KEYS.keys, label: 'Einstellungen', icon: , Panel: SettingsKeysPanel },
{ id: TAB_KEYS.numbers, label: 'Nummernkreise', icon: , Panel: NumberSettingsPanel },
+ { id: TAB_KEYS.printers, label: 'Drucker', icon: , Panel: PrinterSettingsPanel },
];
export default class SettingsDialog extends Component {
diff --git a/src/components/settings/SettingsKeysPanel.jsx b/src/components/settings/SettingsKeysPanel.jsx
index db7bfec..ccad89f 100644
--- a/src/components/settings/SettingsKeysPanel.jsx
+++ b/src/components/settings/SettingsKeysPanel.jsx
@@ -27,16 +27,12 @@ const SETTABLE_KEYS = [
'ui.theme',
'keys.tavily',
'keys.openrouter',
- 'labelprinter.addr',
- 'labelprinter.type',
];
const KEY_LABELS = {
'ui.theme': 'UI-Theme',
'keys.tavily': 'Tavily API-Key',
'keys.openrouter': 'OpenRouter API-Key',
- 'labelprinter.addr': 'Labeldrucker-Adresse',
- 'labelprinter.type': 'Labeldrucker-Typ',
};
function keyLabel(key) {
diff --git a/src/components/settings/index.js b/src/components/settings/index.js
index df96d02..cc6f719 100644
--- a/src/components/settings/index.js
+++ b/src/components/settings/index.js
@@ -1,3 +1,3 @@
export { default as SettingsDialog } from './SettingsDialog';
export { default as SettingsKeysPanel } from './SettingsKeysPanel';
-export { default as NumberSettingsPanel } from './NumberSettingsPanel';
\ No newline at end of file
+export { default as NumberSettingsPanel } from './NumberSettingsPanel';export { default as PrinterSettingsPanel } from './PrinterSettingsPanel';
diff --git a/src/nav.js b/src/nav.js
index 500d312..af5063c 100644
--- a/src/nav.js
+++ b/src/nav.js
@@ -8,6 +8,7 @@ import InfoIcon from '@mui/icons-material/Info';
import ViewListIcon from '@mui/icons-material/ViewList';
import CategoryIcon from '@mui/icons-material/Category';
import WarehouseIcon from '@mui/icons-material/Warehouse';
+import CopyrightIcon from '@mui/icons-material/Copyright';
import ListAltIcon from '@mui/icons-material/ListAlt';
import GroupsIcon from '@mui/icons-material/Groups';
import AddBoxIcon from '@mui/icons-material/AddBox';
@@ -103,6 +104,7 @@ export const SUB_NAV = {
{ segment: '', label: 'Liste', title: 'Artikel', icon: ViewListIcon },
{ segment: 'kategorien', label: 'Kategorieen', title: 'Kategorien', icon: CategoryIcon },
{ segment: 'lager', label: 'Lager', icon: WarehouseIcon },
+ { segment: 'marken', label: 'Marken', icon: CopyrightIcon },
],
'/lieferung': [
{ segment: '', label: 'Liste', title: 'Lieferungen', icon: ListAltIcon },
diff --git a/src/pages/artikel/marken.jsx b/src/pages/artikel/marken.jsx
new file mode 100644
index 0000000..68df76a
--- /dev/null
+++ b/src/pages/artikel/marken.jsx
@@ -0,0 +1,267 @@
+import { PureComponent } from 'react';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import CircularProgress from '@mui/material/CircularProgress';
+import IconButton from '@mui/material/IconButton';
+import List from '@mui/material/List';
+import ListItem from '@mui/material/ListItem';
+import Typography from '@mui/material/Typography';
+import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
+import CopyrightIcon from '@mui/icons-material/Copyright';
+import BackendError from '../../components/BackendError';
+import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
+import { PanelTile, PanelTileGrid } from 'dashgui/panels';
+import { backendErrorFrom } from '../../utils/backendError';
+import { notifyError } from '../../utils/notify';
+import { setLastArticleSku } from '../../automats';
+import brandAutomat from '../../automats/brandAutomat';
+import { enqueueSnackbar } from 'notistack';
+
+function draftsFrom(brands) {
+ return Object.fromEntries(brands.map((b) => [b.id, b.name]));
+}
+
+class ArtikelMarkenPage extends PureComponent {
+ constructor(props) {
+ super(props);
+ const initialBrands = brandAutomat.state.brands || [];
+ this.state = {
+ status: brandAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
+ error: brandAutomat.state.error ? backendErrorFrom({ message: brandAutomat.state.error }) : null,
+ brands: initialBrands,
+ drafts: draftsFrom(initialBrands),
+ newName: '',
+ busyKey: null,
+ };
+ this._cancelled = false;
+ this.handleCreate = this.handleCreate.bind(this);
+ this.handleRetry = this.handleRetry.bind(this);
+ this.loadBrands = this.loadBrands.bind(this);
+ }
+
+ componentDidMount() {
+ setLastArticleSku(null);
+ this._cancelled = false;
+
+ this.unsub = brandAutomat.subscribe((state) => {
+ if (this._cancelled) return;
+ if (state.status === 'succeeded') {
+ this.applyBrands(state.brands);
+ } else if (state.status === 'failed') {
+ this.setState({ status: 'failed', error: backendErrorFrom({ message: state.error }) });
+ }
+ });
+
+ if (brandAutomat.state.status !== 'succeeded') {
+ void this.loadBrands();
+ }
+ }
+
+ componentWillUnmount() {
+ this._cancelled = true;
+ this.unsub?.();
+ }
+
+ applyBrands(brands) {
+ this.setState((prev) => {
+ const currentDrafts = prev.drafts || {};
+ const nextDrafts = { ...draftsFrom(brands) };
+ for (const b of brands) {
+ if (prev.busyKey !== b.id && currentDrafts[b.id] !== undefined) {
+ nextDrafts[b.id] = currentDrafts[b.id];
+ }
+ }
+ return {
+ status: 'ready',
+ error: null,
+ brands,
+ drafts: nextDrafts,
+ };
+ });
+ }
+
+ async loadBrands() {
+ if (brandAutomat.state.status !== 'succeeded') {
+ this.setState({ status: 'loading', error: null });
+ }
+ try {
+ const list = await brandAutomat.actions.load();
+ if (this._cancelled) return;
+ this.applyBrands(list);
+ } catch (err) {
+ if (this._cancelled) return;
+ this.setState({ status: 'failed', error: backendErrorFrom(err) });
+ }
+ }
+
+ handleRetry() {
+ void this.loadBrands();
+ }
+
+ async handleCreate() {
+ const name = this.state.newName.trim();
+ if (!name || this.state.busyKey != null) return;
+
+ this.setState({ busyKey: 'new' });
+ try {
+ const row = await brandAutomat.actions.upsert({ name });
+ if (this._cancelled) return;
+ this.setState({ newName: '', busyKey: null });
+ enqueueSnackbar(`Marke „${row.name || name}" angelegt.`, { variant: 'success' });
+ } catch (err) {
+ if (this._cancelled) return;
+ notifyError(err);
+ this.setState({ busyKey: null });
+ }
+ }
+
+ async handleRename(brand) {
+ const draft = String(this.state.drafts[brand.id] ?? '').trim();
+ if (!draft) {
+ this.setState((prev) => ({
+ drafts: { ...prev.drafts, [brand.id]: brand.name },
+ }));
+ return;
+ }
+ if (draft === brand.name || this.state.busyKey != null) return;
+
+ this.setState({ busyKey: brand.id });
+ try {
+ await brandAutomat.actions.upsert({ name: draft });
+ if (this._cancelled) return;
+ this.setState({ busyKey: null });
+ enqueueSnackbar('Marke gespeichert.', { variant: 'success' });
+ } catch (err) {
+ if (this._cancelled) return;
+ notifyError(err);
+ this.setState((prev) => ({
+ busyKey: null,
+ drafts: { ...prev.drafts, [brand.id]: brand.name },
+ }));
+ }
+ }
+
+ async handleDelete(brand) {
+ if (this.state.busyKey != null) return;
+ if (!window.confirm(`Marke „${brand.name}" wirklich löschen?`)) return;
+
+ this.setState({ busyKey: brand.id });
+ try {
+ await brandAutomat.actions.remove(brand.id);
+ if (this._cancelled) return;
+ this.setState({ busyKey: null });
+ enqueueSnackbar('Marke gelöscht.', { variant: 'success' });
+ } catch (err) {
+ if (this._cancelled) return;
+ notifyError(err);
+ this.setState({ busyKey: null });
+ }
+ }
+
+ render() {
+ const { status, error, brands, drafts, newName, busyKey } = this.state;
+ const busy = busyKey != null;
+
+ if (status === 'loading') {
+ return (
+
+
+
+ );
+ }
+
+ if (status === 'failed') {
+ return ;
+ }
+
+ return (
+
+
+
+ Marken der ausgewählten Firma.
+
+
+
+ {brands.length === 0 ? (
+
+ Noch keine Marken angelegt.
+
+ ) : (
+ brands.map((b) => (
+
+
+
+ this.setState((prev) => ({
+ drafts: { ...prev.drafts, [b.id]: next },
+ }))
+ }
+ onBlur={() => void this.handleRename(b)}
+ onKeyDown={(event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ event.target.blur();
+ }
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ this.setState((prev) => ({
+ drafts: { ...prev.drafts, [b.id]: b.name },
+ }));
+ event.target.blur();
+ }
+ }}
+ sx={{ flex: 1 }}
+ />
+ void this.handleDelete(b)}
+ >
+
+
+
+ ))
+ )}
+
+
+
+ this.setState({ newName: next })}
+ onKeyDown={(event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ void this.handleCreate();
+ }
+ }}
+ sx={{ flex: 1 }}
+ />
+
+
+
+
+ );
+ }
+}
+
+export default ArtikelMarkenPage;