import { PureComponent, createRef } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import TuneIcon from '@mui/icons-material/Tune';
import Sortable from 'sortablejs';
import { enqueueSnackbar } from 'notistack';
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
import FillSpace from 'dashgui/FillSpace';
import { PanelTile, PanelTileGrid } from 'dashgui/panels';
import BackendError from '../BackendError';
import { backendErrorFrom } from '../../utils/backendError';
import { notifyError } from '../../utils/notify';
import propertyAutomat, { selectProperties } from '../../automats/propertyAutomat';
import ArtikelMerkmalText from './ArtikelMerkmalText';
import { ORANGE_PANEL } from '../../colors';
/**
* Tab 2: Text-Merkmale
*/
class TextMerkmaleTab extends PureComponent {
// #region Constructor & State
constructor(props) {
super(props);
const initialProperties = selectProperties() || [];
this.state = {
propStatus: propertyAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
error: propertyAutomat.state.error || null,
properties: initialProperties,
propFilter: '',
propSubmitting: false,
propDeletingUnused: false,
propPage: 1,
};
this._cancelled = false;
this.propListRef = createRef();
this._propSortable = null;
this.handleRetry = this.handleRetry.bind(this);
this.handleCreateProperty = this.handleCreateProperty.bind(this);
this.handleDeleteUnusedProperties = this.handleDeleteUnusedProperties.bind(this);
}
// #endregion
// #region Lifecycle
componentDidMount() {
this._cancelled = false;
console.log('TextMerkmaleTab mounted', this.props);
this.unsubProp = propertyAutomat.subscribe((state) => {
if (this._cancelled) return;
if (state.status === 'succeeded') {
this.setState({
propStatus: 'ready',
error: null,
properties: state.properties,
});
} else if (state.status === 'failed') {
this.setState({ propStatus: 'failed', error: backendErrorFrom(state.error) });
}
});
if (propertyAutomat.state.status !== 'succeeded') {
void this.load();
} else {
this._initPropSortable();
}
}
componentDidUpdate(prevProps, prevState) {
if (
(prevState.properties !== this.state.properties ||
prevState.propFilter !== this.state.propFilter ||
prevState.propPage !== this.state.propPage ||
prevState.propStatus !== this.state.propStatus) &&
this.propListRef.current
) {
if (this.state.propStatus === 'ready') {
this._initPropSortable();
}
}
}
componentWillUnmount() {
this._cancelled = true;
this.unsubProp?.();
this._destroySortables();
}
// #endregion
// #region Sortable Reorder
_initPropSortable() {
this._propSortable?.destroy();
const el = this.propListRef.current;
if (!el) return;
const isFiltered = Boolean(this.state.propFilter?.trim());
this._propSortable = Sortable.create(el, {
handle: '.drag-handle',
animation: 150,
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
disabled: isFiltered,
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (oldIndex === newIndex) return;
const offset = ((this.state.propPage || 1) - 1) * 8;
const realOld = offset + oldIndex;
const realNew = offset + newIndex;
if (realOld === realNew) return;
this.setState(
(prev) => {
const next = [...prev.properties];
const [moved] = next.splice(realOld, 1);
next.splice(realNew, 0, moved);
return { properties: next };
},
() => {
const ids = this.state.properties.map((p) => p.id);
void this._persistPropOrder(ids);
}
);
},
});
}
_destroySortables() {
this._propSortable?.destroy();
this._propSortable = null;
}
async _persistPropOrder(ids) {
try {
await propertyAutomat.actions.reorderProperties(ids);
} catch (err) {
if (this._cancelled) return;
notifyError(err);
void propertyAutomat.actions.load();
}
}
// #endregion
// #region Actions & Handlers
async load() {
this.setState({
propStatus: propertyAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
error: null,
});
try {
await propertyAutomat.actions.load();
} catch (err) {
if (this._cancelled) return;
this.setState({ error: backendErrorFrom(err) });
}
}
handleRetry() {
void this.load();
}
async handleCreateProperty() {
const name = this.state.propFilter.trim();
if (!name || this.state.propSubmitting) return;
this.setState({ propSubmitting: true });
try {
const row = await propertyAutomat.actions.createProperty({
name,
exclusive: true,
});
if (this._cancelled) return;
this.setState({ propFilter: '', propSubmitting: false });
enqueueSnackbar(`Merkmal „${row?.name || name}“ angelegt.`, { variant: 'success' });
} catch (err) {
if (this._cancelled) return;
this.setState({ propSubmitting: false });
notifyError(err);
}
}
async handleDeleteUnusedProperties() {
const { properties, propDeletingUnused } = this.state;
if (propDeletingUnused) return;
const unused = properties.filter((p) => (p.articleCount ?? 0) === 0);
if (!unused.length) return;
if (!window.confirm(`Alle ${unused.length} ungenutzten Text-Merkmale wirklich löschen?`)) {
return;
}
this.setState({ propDeletingUnused: true });
try {
await propertyAutomat.actions.deleteProperties(unused.map((p) => p.id));
enqueueSnackbar(`${unused.length} ungenutzte Text-Merkmale gelöscht.`, { variant: 'success' });
} catch (err) {
notifyError(err);
} finally {
if (!this._cancelled) {
this.setState({ propDeletingUnused: false });
}
}
}
// #endregion
// #region Render
render() {
const {
propStatus,
error,
properties,
propFilter,
propSubmitting,
propDeletingUnused,
propPage,
} = this.state;
if (propStatus === 'loading' && !properties.length) {
return (
);
}
if (error && !properties.length) {
return ;
}
const propFilterTrimmed = propFilter.trim();
const filteredProperties = propFilterTrimmed
? properties.filter((p) => p.name.toLowerCase().includes(propFilterTrimmed.toLowerCase()))
: properties;
const unusedPropCount = properties.filter((p) => (p.articleCount ?? 0) === 0).length;
return (
{/* Tile 3: Text-Merkmale */}
Text-Merkmale
this.setState({ propFilter: next })}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
void this.handleCreateProperty();
}
}}
/>
this.setState({ propPage: page })}
renderItem={ArtikelMerkmalText}
/>
);
}
// #endregion
}
export default TextMerkmaleTab;