This commit is contained in:
seb
2026-08-03 07:13:42 +02:00
parent 4db4948090
commit f302eb9e67
9 changed files with 985 additions and 51 deletions

View File

@@ -8,13 +8,22 @@ import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Divider from '@mui/material/Divider';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import ListItemText from '@mui/material/ListItemText';
import Stack from '@mui/material/Stack';
import Switch from '@mui/material/Switch';
import TextField from '@mui/material/TextField';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
import { enqueueSnackbar } from 'notistack';
import BackendError from './BackendError';
import SingleLineTextEdit from './SingleLineTextEdit';
@@ -25,8 +34,18 @@ import {
selectCurrentCompany,
updateCompany as updateCompanyThunk,
} from '../store/companySlice';
import {
loadPosterminals,
createPosterminal,
deletePosterminal,
enableAllTerminalCategories,
selectPosterminals,
selectPosterminalStatus,
} from '../store/posterminalSlice';
import { loadCategoryTree } from '../store/categorySlice';
import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm';
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
import { notifyError } from '../utils/notify';
class CompanySettingsDialog extends Component {
constructor(props) {
@@ -38,9 +57,15 @@ class CompanySettingsDialog extends Component {
iso639: 'de',
taxrate: '',
reducedtaxrate: '',
newTerminalName: '',
creatingTerminal: false,
enableAllForNew: true,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.syncFormFromCompany = this.syncFormFromCompany.bind(this);
this.handleCreateTerminal = this.handleCreateTerminal.bind(this);
this.handleDeleteTerminal = this.handleDeleteTerminal.bind(this);
this.handleEnableAllCategories = this.handleEnableAllCategories.bind(this);
}
componentDidUpdate(prevProps) {
@@ -52,6 +77,10 @@ class CompanySettingsDialog extends Component {
} else if (companyStatus !== 'loading') {
void this.props.loadCurrentCompany();
}
// Load posterminals when dialog opens
if (this.props.posterminalStatus !== 'loading') {
void this.props.loadPosterminals();
}
}
if (open && company && company !== prevProps.company) {
this.syncFormFromCompany(company);
@@ -67,6 +96,56 @@ class CompanySettingsDialog extends Component {
});
}
async handleCreateTerminal() {
const { newTerminalName, enableAllForNew } = this.state;
const trimmed = newTerminalName.trim();
if (!trimmed) {
enqueueSnackbar('Terminalname ist erforderlich.', { variant: 'error' });
return;
}
this.setState({ creatingTerminal: true });
try {
const terminal = await this.props.createPosterminal({ name: trimmed }).unwrap();
// If "enable all" toggle is on, enable all categories for this terminal
if (enableAllForNew && terminal.id) {
await this.props.enableAllTerminalCategories({ terminalId: terminal.id }).unwrap();
// Reload category tree so tiles reflect the updated posterminalIds
this.props.loadCategoryTree();
}
this.setState({ newTerminalName: '' });
} catch (err) {
notifyError(err);
} finally {
this.setState({ creatingTerminal: false });
}
}
async handleDeleteTerminal(terminal) {
try {
await this.props.deletePosterminal({ terminalId: terminal.id }).unwrap();
} catch (err) {
notifyError(err);
}
}
async handleEnableAllCategories(terminal) {
try {
const hasCategories = terminal.categoryIds && terminal.categoryIds.length > 0;
if (hasCategories) {
await this.props.disableAllTerminalCategories({ terminalId: terminal.id }).unwrap();
enqueueSnackbar('Alle Kategorien für Terminal deaktiviert.', { variant: 'success' });
} else {
await this.props.enableAllTerminalCategories({ terminalId: terminal.id }).unwrap();
enqueueSnackbar('Alle Kategorien für Terminal aktiviert.', { variant: 'success' });
}
} catch (err) {
notifyError(err);
}
}
async handleSubmit(event) {
event.preventDefault();
const { onClose } = this.props;
@@ -200,6 +279,77 @@ class CompanySettingsDialog extends Component {
}}
/>
</Stack>
<Divider sx={{ my: 1 }} />
<Box>
{this.props.posterminalStatus === 'loading' ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
<CircularProgress size={24} />
</Box>
) : (
<>
<List dense sx={{ py: 0 }}>
{this.props.terminals.map((terminal) => (
<ListItem
key={terminal.id}
sx={{ px: 0, py: 0.5 }}
>
<PointOfSaleIcon sx={{ mr: 1, color: 'text.secondary', fontSize: 20 }} />
<ListItemText
primary={terminal.name}
secondary={`PIN: ${terminal.pairingpin}`}
/>
<ListItemSecondaryAction>
<IconButton
edge="end"
size="small"
onClick={() => this.handleDeleteTerminal(terminal)}
disabled={busy || this.state.creatingTerminal}
>
<DeleteOutlinedIcon fontSize="small" />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
<TextField
size="small"
placeholder="Neues Terminal..."
value={this.state.newTerminalName}
onChange={(e) => this.setState({ newTerminalName: e.target.value })}
disabled={busy || this.state.creatingTerminal}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.handleCreateTerminal();
}
}}
sx={{ flex: 1 }}
/>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<PointOfSaleIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
<Switch
size="small"
checked={this.state.enableAllForNew}
onChange={(e) => this.setState({ enableAllForNew: e.target.checked })}
disabled={busy || this.state.creatingTerminal}
/>
</Box>
<Button
variant="contained"
size="small"
onClick={this.handleCreateTerminal}
disabled={busy || this.state.creatingTerminal || !this.state.newTerminalName.trim()}
>
Hinzufügen
</Button>
</Box>
</>
)}
</Box>
</Stack>
)}
</DialogContent>
@@ -221,11 +371,18 @@ const mapStateToProps = (state) => ({
user: selectAuthUser(state),
company: selectCurrentCompany(state),
companyStatus: selectCompanyStatus(state),
terminals: selectPosterminals(state),
posterminalStatus: selectPosterminalStatus(state),
});
const mapDispatchToProps = (dispatch) => ({
loadCurrentCompany: () => dispatch(loadCurrentCompany()),
updateCompany: (payload) => dispatch(updateCompanyThunk(payload)),
loadPosterminals: () => dispatch(loadPosterminals()),
createPosterminal: (payload) => dispatch(createPosterminal(payload)),
deletePosterminal: (payload) => dispatch(deletePosterminal(payload)),
enableAllTerminalCategories: (payload) => dispatch(enableAllTerminalCategories(payload)),
loadCategoryTree: () => dispatch(loadCategoryTree()),
});
export default connect(mapStateToProps, mapDispatchToProps)(CompanySettingsDialog);