{
- if (!readOnly) onChange(event.target.value);
- }}
- autoFocus={autoFocus}
- autoComplete={autoComplete}
- slotProps={{
- ...slotProps,
- input: {
- readOnly,
- ...slotProps?.input,
- },
- htmlInput: {
- spellCheck: false,
- readOnly,
- tabIndex: readOnly ? -1 : undefined,
- ...slotProps?.htmlInput,
- },
- }}
- sx={sx}
- {...rest}
- />
- );
- }
-}
-
-export default SingleLineTextEdit;
diff --git a/src/components/article/ArticlePhotoTile.jsx b/src/components/article/ArticlePhotoTile.jsx
index 6c114e6..016a501 100644
--- a/src/components/article/ArticlePhotoTile.jsx
+++ b/src/components/article/ArticlePhotoTile.jsx
@@ -2,7 +2,7 @@ import { PureComponent, createRef } from 'react';
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import { StorageManagerContext } from '../../api/storageManagerContext';
-import { PanelTile } from '../panels';
+import { PanelTile } from 'dashgui/panels';
import { requireObject } from '../../utils/apiShape';
import { notifyError } from '../../utils/notify';
import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto';
diff --git a/src/components/article/ArticleTileTab.jsx b/src/components/article/ArticleTileTab.jsx
index 49d0c33..ec4530b 100644
--- a/src/components/article/ArticleTileTab.jsx
+++ b/src/components/article/ArticleTileTab.jsx
@@ -24,8 +24,8 @@ import {
} from '../../pages/artikel/articleRoute';
import { router } from '../../appRouter';
import { matchPath } from 'react-router-dom';
-import { PanelTile } from '../panels';
-import { PANEL_TILE_BORDER_RADIUS, responsiveColumnSpan } from '../panels/panelTileLayout';
+import { PanelTile } from 'dashgui/panels';
+import { PANEL_TILE_BORDER_RADIUS, responsiveColumnSpan } from 'dashgui/panels/panelTileLayout';
import ArticleAttributesTab from './tabs/ArticleAttributesTab';
import ArticleCompoundTab from './tabs/ArticleCompoundTab';
import ArticleHistoryTab from './tabs/ArticleHistoryTab';
diff --git a/src/components/article/ArticleView.jsx b/src/components/article/ArticleView.jsx
index 88fbbbf..fb4bbca 100644
--- a/src/components/article/ArticleView.jsx
+++ b/src/components/article/ArticleView.jsx
@@ -1,5 +1,5 @@
import { PureComponent } from 'react';
-import { PanelTileGrid } from '../panels';
+import { PanelTileGrid } from 'dashgui/panels';
import { StorageManagerContext } from '../../api/storageManagerContext';
import withNavigate from '../withNavigate';
import {
diff --git a/src/components/article/boxes/ArticleViewBrand.jsx b/src/components/article/boxes/ArticleViewBrand.jsx
index a462f5d..56cbced 100644
--- a/src/components/article/boxes/ArticleViewBrand.jsx
+++ b/src/components/article/boxes/ArticleViewBrand.jsx
@@ -3,7 +3,7 @@ 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 { EditablePanelTile } from 'dashgui/panels';
import { StorageManagerContext } from '../../../api/storageManagerContext';
import { requireDefined, requireObject } from '../../../utils/apiShape';
import { notifyError } from '../../../utils/notify';
@@ -150,6 +150,7 @@ export default class ArticleViewBrand extends PureComponent {
color="#e9d5ff"
value={value}
onSave={this.handleSave}
+ onError={notifyError}
isDirty={brandDraftDirty}
canSave={brandDraftValid}
display={{value.name || null}}
diff --git a/src/components/article/boxes/ArticleViewEANs.jsx b/src/components/article/boxes/ArticleViewEANs.jsx
index cc8febf..e0b3aa1 100644
--- a/src/components/article/boxes/ArticleViewEANs.jsx
+++ b/src/components/article/boxes/ArticleViewEANs.jsx
@@ -3,10 +3,11 @@ 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 { EditablePanelTile } from 'dashgui/panels';
import { StorageManagerContext } from '../../../api/storageManagerContext';
import { requireObject } from '../../../utils/apiShape';
import { articleEans } from '../../../utils/articleDetail';
+import { notifyError } from '../../../utils/notify';
class PanelValue extends Component {
render() {
const { label, children } = this.props;
@@ -115,6 +116,7 @@ export default class ArticleViewEANs extends PureComponent {
contentAlignItems="flex-start"
value={value}
onSave={this.handleSave}
+ onError={notifyError}
isDirty={eansDraftDirty}
canSave={eansDraftValid}
display={}
diff --git a/src/components/article/boxes/ArticleViewLists.jsx b/src/components/article/boxes/ArticleViewLists.jsx
index 17ce63e..8f86b3c 100644
--- a/src/components/article/boxes/ArticleViewLists.jsx
+++ b/src/components/article/boxes/ArticleViewLists.jsx
@@ -2,7 +2,7 @@ import { Component, PureComponent } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
-import { PanelTile } from '../../panels';
+import { PanelTile } from 'dashgui/panels';
import { artikelDetailPath } from '../../../pages/artikel/articleRoute';
class CountLine extends Component {
diff --git a/src/components/article/boxes/ArticleViewName.jsx b/src/components/article/boxes/ArticleViewName.jsx
index 0e1c16b..7d5a5d5 100644
--- a/src/components/article/boxes/ArticleViewName.jsx
+++ b/src/components/article/boxes/ArticleViewName.jsx
@@ -1,11 +1,12 @@
import { Component, PureComponent } from 'react';
-import { EditablePanelTile } from '../../panels';
-import SingleLineTextEdit from '../../SingleLineTextEdit';
+import { EditablePanelTile } from 'dashgui/panels';
+import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
import { StorageManagerContext } from '../../../api/storageManagerContext';
import { getCategoryBreadcrumbs } from '../../../store/categorySlice';
import { Box, Chip, IconButton } from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import CategoryPickerDialog from '../CategoryPickerDialog';
+import { notifyError } from '../../../utils/notify';
const articleNameTextSx = {
fontWeight: 700,
@@ -288,6 +289,7 @@ export default class ArticleViewName extends PureComponent {
color="#ffffff"
value={value}
onSave={this.handleSave}
+ onError={notifyError}
isDirty={() => draft != null && draftDirty(draft, value)}
canSave={() => draft != null && draftValid(draft)}
display={
diff --git a/src/components/article/boxes/ArticleViewPrice.jsx b/src/components/article/boxes/ArticleViewPrice.jsx
index f74a93d..ca353bd 100644
--- a/src/components/article/boxes/ArticleViewPrice.jsx
+++ b/src/components/article/boxes/ArticleViewPrice.jsx
@@ -3,7 +3,7 @@ import Box from '@mui/material/Box';
import InputAdornment from '@mui/material/InputAdornment';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
-import { EditablePanelTile } from '../../panels';
+import { EditablePanelTile } from 'dashgui/panels';
import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog';
import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog';
import { StorageManagerContext } from '../../../api/storageManagerContext';
@@ -510,6 +510,7 @@ export default class ArticleViewPrice extends PureComponent {
contentAlignItems="flex-start"
value={priceValue}
onSave={this.handleSave}
+ onError={notifyError}
isDirty={priceDraftDirty}
canSave={priceDraftValid}
overlay={({ editing, draft, onChange }) => (
diff --git a/src/components/article/boxes/ArticleViewStock.jsx b/src/components/article/boxes/ArticleViewStock.jsx
index d6f98d4..1fcee43 100644
--- a/src/components/article/boxes/ArticleViewStock.jsx
+++ b/src/components/article/boxes/ArticleViewStock.jsx
@@ -1,7 +1,7 @@
import { Component, PureComponent } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
-import { PanelTile } from '../../panels';
+import { PanelTile } from 'dashgui/panels';
import { formatStock } from '../../../pages/artikel/searchFormat';
class PanelValue extends Component {
diff --git a/src/components/manifest/ManifestForm.jsx b/src/components/manifest/ManifestForm.jsx
index 8d6de61..915aaea 100644
--- a/src/components/manifest/ManifestForm.jsx
+++ b/src/components/manifest/ManifestForm.jsx
@@ -4,7 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';
import { enqueueSnackbar } from 'notistack';
import BackendError from '../BackendError';
-import { PanelTile, PanelTileGrid } from '../panels';
+import { PanelTile, PanelTileGrid } from 'dashgui/panels';
import { StorageManagerContext } from '../../api/storageManagerContext';
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
import { backendErrorFrom } from '../../utils/backendError';
diff --git a/src/components/manifest/ManifestHeader.jsx b/src/components/manifest/ManifestHeader.jsx
index 1e23d21..3ac6138 100644
--- a/src/components/manifest/ManifestHeader.jsx
+++ b/src/components/manifest/ManifestHeader.jsx
@@ -2,8 +2,8 @@ import { PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
-import SingleLineTextEdit from '../SingleLineTextEdit';
-import { PanelTile } from '../panels';
+import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
+import { PanelTile } from 'dashgui/panels';
import { manifestConfig, partyDisplayName } from './manifestConfig';
class ManifestHeader extends PureComponent {
diff --git a/src/components/manifest/ManifestLines.jsx b/src/components/manifest/ManifestLines.jsx
index 6ac588e..a3e9b98 100644
--- a/src/components/manifest/ManifestLines.jsx
+++ b/src/components/manifest/ManifestLines.jsx
@@ -9,7 +9,7 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import AddIcon from '@mui/icons-material/Add';
-import { PanelTile } from '../panels';
+import { PanelTile } from 'dashgui/panels';
import { LINE_COLUMNS } from './manifestConfig';
import ManifestFormLine from './ManifestFormLine';
diff --git a/src/components/manifest/ManifestList.jsx b/src/components/manifest/ManifestList.jsx
index bd8ad41..8ab3e12 100644
--- a/src/components/manifest/ManifestList.jsx
+++ b/src/components/manifest/ManifestList.jsx
@@ -15,7 +15,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import { enqueueSnackbar } from 'notistack';
import BackendError from '../BackendError';
-import { PanelTile, PanelTileGrid } from '../panels';
+import { PanelTile, PanelTileGrid } from 'dashgui/panels';
import { StorageManagerContext } from '../../api/storageManagerContext';
import { backendErrorFrom } from '../../utils/backendError';
import { notifyError } from '../../utils/notify';
diff --git a/src/components/panels/EditablePanelTile.jsx b/src/components/panels/EditablePanelTile.jsx
deleted file mode 100644
index 2e7f0e0..0000000
--- a/src/components/panels/EditablePanelTile.jsx
+++ /dev/null
@@ -1,278 +0,0 @@
-import { Component, createRef } from 'react';
-import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
-import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
-import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
-import Alert from '@mui/material/Alert';
-import Box from '@mui/material/Box';
-import CircularProgress from '@mui/material/CircularProgress';
-import IconButton from '@mui/material/IconButton';
-import Paper from '@mui/material/Paper';
-import Typography from '@mui/material/Typography';
-import { injectMuiTheme } from './injectMuiTheme';
-import { notifyError } from '../../utils/notify';
-import { panelTilePaperSx, TILE_ACTION_PADDING, tileActionIconSx, tileActionSx } from './panelTileLayout';
-
-function valuesEqual(a, b) {
- return JSON.stringify(a) === JSON.stringify(b);
-}
-
-/**
- * Panel tile with centralized inline-edit chrome.
- * Pass `onSave` to enable edit UI; omit it for read-only tiles (no edit icon).
- *
- * Content:
- * display — React node shown when not editing
- * edit — React node, or ( { draft, onChange, inputRef } ) => node, while editing
- * overlay — optional ( { editing, draft, onChange } ) => node
- */
-class EditablePanelTileBase extends Component {
- constructor(props) {
- super(props);
- this.inputRef = createRef();
- this.state = {
- editing: false,
- draft: null,
- saving: false,
- error: null,
- };
- }
-
- componentDidUpdate(_prevProps, prevState) {
- if (!prevState.editing && this.state.editing) {
- this.inputRef.current?.focus();
- }
- }
-
- getSavedValue() {
- return this.props.value;
- }
-
- isDirty() {
- const { draft, editing } = this.state;
- if (!editing || draft == null) return false;
- if (this.props.isDirty) {
- return this.props.isDirty(draft, this.getSavedValue());
- }
- return !valuesEqual(draft, this.getSavedValue());
- }
-
- canSave() {
- const { draft, saving } = this.state;
- if (saving || !this.isDirty() || draft == null) return false;
- if (this.props.canSave) {
- return this.props.canSave(draft, this.getSavedValue());
- }
- return true;
- }
-
- handleStartEdit = (event) => {
- event.stopPropagation();
- this.setState({
- editing: true,
- draft: structuredClone(this.getSavedValue()),
- error: null,
- });
- };
-
- handleCancel = (event) => {
- event?.stopPropagation();
- this.setState({
- editing: false,
- draft: null,
- error: null,
- });
- };
-
- handleDraftChange = (nextDraft) => {
- this.setState({ draft: nextDraft, error: null });
- };
-
- handleEditKeyDown = (event) => {
- if (event.key === 'Escape') {
- event.preventDefault();
- event.stopPropagation();
- this.handleCancel();
- return;
- }
-
- if (event.key === 'Enter' && !event.shiftKey) {
- event.preventDefault();
- event.stopPropagation();
- if (this.canSave()) {
- void this.handleSave(event);
- }
- }
- };
-
- handleSave = async (event) => {
- event?.stopPropagation();
- if (!this.canSave()) return;
-
- const { onSave } = this.props;
- const { draft } = this.state;
-
- try {
- await onSave(draft);
- this.setState({ editing: false, draft: null, saving: false, error: null });
- } catch (err) {
- const normalized = notifyError(err);
- this.setState({ error: normalized.message });
- }
- };
-
- renderTitle() {
- const { title, onSave } = this.props;
- if (!title) return null;
- return (
-
- {title}
-
- );
- }
-
- renderActions() {
- const { onSave } = this.props;
- if (!onSave) return null;
-
- const { editing, saving } = this.state;
- const iconSx = tileActionIconSx;
-
- if (!editing) {
- return (
- <>
-
-
-
-
-
-
- >
- );
- }
-
- return (
- <>
-
- {saving ? (
-
- ) : (
-
- )}
-
-
-
-
- >
- );
- }
-
- renderBody() {
- const { display, edit } = this.props;
- const { editing, draft } = this.state;
-
- if (editing && draft != null) {
- if (typeof edit === 'function') {
- return edit({
- draft,
- onChange: this.handleDraftChange,
- inputRef: this.inputRef,
- });
- }
- return edit;
- }
- return display;
- }
-
- renderOverlayNode() {
- const { overlay } = this.props;
- const { editing, draft } = this.state;
- if (typeof overlay !== 'function') return null;
- return overlay({
- editing,
- draft,
- onChange: this.handleDraftChange,
- });
- }
-
- render() {
- const {
- onSave,
- theme,
- wide = 1,
- sx,
- minHeight = 120,
- color,
- contentAlignItems = 'center',
- } = this.props;
- const { editing, error } = this.state;
- const pending = this.isDirty();
- const actionPadding = onSave ? TILE_ACTION_PADDING : 0;
-
- return (
-
- {this.renderActions()}
- {this.renderOverlayNode()}
- {this.renderTitle()}
-
- {this.renderBody()}
-
- {error ? (
-
- {error}
-
- ) : null}
-
- );
- }
-}
-
-export const EditablePanelTile = injectMuiTheme(EditablePanelTileBase);
diff --git a/src/components/panels/PanelTile.jsx b/src/components/panels/PanelTile.jsx
deleted file mode 100644
index ebe54e0..0000000
--- a/src/components/panels/PanelTile.jsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import { Component, createRef } from 'react';
-import Box from '@mui/material/Box';
-import CircularProgress from '@mui/material/CircularProgress';
-import IconButton from '@mui/material/IconButton';
-import Paper from '@mui/material/Paper';
-import Typography from '@mui/material/Typography';
-import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
-import { injectMuiTheme } from './injectMuiTheme';
-import {
- GRID_COLUMNS,
- TILE_ACTION_PADDING,
- panelTilePaperSx,
- tileActionIconSx,
- tileActionSx,
-} from './panelTileLayout';
-
-class PanelTileBase extends Component {
- renderTitle() {
- const { title, action } = this.props;
- if (!title) return null;
- return (
-
- {title}
-
- );
- }
-
- renderAction() {
- const { action } = this.props;
- if (!action) return null;
-
- const { label, icon: Icon, onClick, busy } = action;
-
- return (
- <>
-
-
-
-
- {busy ? (
-
- ) : (
-
- )}
-
- >
- );
- }
-
- render() {
- const {
- theme,
- wide = 1,
- children,
- sx,
- minHeight = 120,
- color,
- action,
- elevation = 4,
- } = this.props;
- const actionPadding = action ? TILE_ACTION_PADDING : 0;
-
- return (
-
- {this.renderAction()}
- {this.renderTitle()}
- {children}
-
- );
- }
-}
-
-export const PanelTile = injectMuiTheme(PanelTileBase);
-
-export function PanelTileGrid({ children, sx }) {
- return (
-
- {children}
-
- );
-}
-
-export { GRID_COLUMNS };
diff --git a/src/components/panels/index.js b/src/components/panels/index.js
deleted file mode 100644
index d26b4a4..0000000
--- a/src/components/panels/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { PanelTile, PanelTileGrid, GRID_COLUMNS } from './PanelTile';
-export { EditablePanelTile } from './EditablePanelTile';
-export { PENDING_EDIT_BORDER } from './panelTileLayout';
diff --git a/src/components/panels/injectMuiTheme.jsx b/src/components/panels/injectMuiTheme.jsx
deleted file mode 100644
index 6ad40fa..0000000
--- a/src/components/panels/injectMuiTheme.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useTheme } from '@mui/material/styles';
-
-/** Injects MUI theme into class components (replaces removed withTheme HOC). */
-export function injectMuiTheme(Wrapped) {
- function ComponentWithTheme(props) {
- const theme = useTheme();
- return ;
- }
-
- const name = Wrapped.displayName || Wrapped.name || 'Component';
- ComponentWithTheme.displayName = `injectMuiTheme(${name})`;
- return ComponentWithTheme;
-}
diff --git a/src/components/panels/panelTileLayout.js b/src/components/panels/panelTileLayout.js
deleted file mode 100644
index 980e5e2..0000000
--- a/src/components/panels/panelTileLayout.js
+++ /dev/null
@@ -1,74 +0,0 @@
-const GRID_COLUMNS = 4;
-
-export const PENDING_EDIT_BORDER = '#e11d48';
-export const EDITING_TILE_BORDER = '#94a3b8';
-
-function clampWide(wide) {
- return Math.min(Math.max(wide, 1), 3);
-}
-
-export function responsiveColumnSpan(wide) {
- const span = clampWide(wide);
- return {
- xs: '1 / -1',
- sm: span >= 2 ? 'span 2' : 'span 1',
- md: `span ${span}`,
- };
-}
-
-export const PENDING_EDIT_RING_PX = 4;
-export const TILE_ACTION_PADDING = 12;
-
-export const tileActionSx = {
- position: 'absolute',
- top: 4,
- color: 'text.secondary',
-};
-
-export const tileActionIconSx = {
- fontSize: 28,
-};
-
-export const PANEL_TILE_BORDER_RADIUS = 4;
-
-function tileRingBoxShadow(ringColor, elevationShadow) {
- const inset = `inset 0 0 0 ${PENDING_EDIT_RING_PX}px ${ringColor}`;
- return elevationShadow ? `${inset}, ${elevationShadow}` : inset;
-}
-
-export function panelTilePaperSx({
- wide,
- sx,
- minHeight = 120,
- editing = false,
- pending = false,
- color,
- theme,
-}) {
- const elevationShadow = theme?.shadows?.[4];
- const ringColor = pending
- ? PENDING_EDIT_BORDER
- : editing
- ? EDITING_TILE_BORDER
- : null;
-
- return {
- gridColumn: responsiveColumnSpan(wide),
- position: 'relative',
- p: 2,
- minHeight,
- display: 'flex',
- flexDirection: 'column',
- border: 'none',
- borderRadius: PANEL_TILE_BORDER_RADIUS,
- ...(color ? { bgcolor: color } : {}),
- ...(ringColor
- ? {
- boxShadow: tileRingBoxShadow(ringColor, elevationShadow),
- }
- : {}),
- ...sx,
- };
-}
-
-export { GRID_COLUMNS };
diff --git a/src/components/tiles/ActionTile.jsx b/src/components/tiles/ActionTile.jsx
index e2bd20d..549908e 100644
--- a/src/components/tiles/ActionTile.jsx
+++ b/src/components/tiles/ActionTile.jsx
@@ -1,6 +1,6 @@
import AddIcon from '@mui/icons-material/Add';
import Typography from '@mui/material/Typography';
-import { Tile } from './Tile';
+import { Tile } from 'dashgui/tiles';
export class ActionTile extends Tile {
renderIcon() {
diff --git a/src/components/tiles/ArticleTile.jsx b/src/components/tiles/ArticleTile.jsx
index 1e08c69..094145f 100644
--- a/src/components/tiles/ArticleTile.jsx
+++ b/src/components/tiles/ArticleTile.jsx
@@ -2,7 +2,7 @@ import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
import Box from '@mui/material/Box';
import ButtonBase from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography';
-import { Tile, tileBaseSx } from './Tile';
+import { Tile, tileBaseSx } from 'dashgui/tiles';
import { formatPrice } from '../../utils/price';
import { articleSku } from '../../pages/artikel/articleRoute';
import ArticlePhoto from '../article/ArticlePhoto';
diff --git a/src/components/tiles/BackTile.jsx b/src/components/tiles/BackTile.jsx
index 9689973..b31a2c0 100644
--- a/src/components/tiles/BackTile.jsx
+++ b/src/components/tiles/BackTile.jsx
@@ -1,7 +1,7 @@
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
-import { Tile, tileBaseSx } from './Tile';
+import { Tile, tileBaseSx } from 'dashgui/tiles';
/**
* BackTile renders as a (Box) instead of