u
This commit is contained in:
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
|
|||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
@@ -28,18 +27,15 @@ import { enqueueSnackbar } from 'notistack';
|
|||||||
import BackendError from './BackendError';
|
import BackendError from './BackendError';
|
||||||
import SingleLineTextEdit from './SingleLineTextEdit';
|
import SingleLineTextEdit from './SingleLineTextEdit';
|
||||||
import { StorageManagerContext } from '../api/storageManager';
|
import { StorageManagerContext } from '../api/storageManager';
|
||||||
import {
|
import { selectAuthUser } from '../store/authSlice';
|
||||||
loadCurrentCompany,
|
import { updateCompany as updateCompanyThunk } from '../store/companySlice';
|
||||||
selectCompanyStatus,
|
|
||||||
selectCurrentCompany,
|
|
||||||
updateCompany as updateCompanyThunk,
|
|
||||||
} from '../store/companySlice';
|
|
||||||
import {
|
import {
|
||||||
createPosterminal,
|
createPosterminal,
|
||||||
deletePosterminal,
|
deletePosterminal,
|
||||||
enableAllTerminalCategories,
|
enableAllTerminalCategories,
|
||||||
} from '../store/posterminalSlice';
|
} from '../store/posterminalSlice';
|
||||||
import { loadCategoryTree } from '../store/categorySlice';
|
import { loadCategoryTree } from '../store/categorySlice';
|
||||||
|
import { requireDefined } from '../utils/apiShape';
|
||||||
import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm';
|
import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm';
|
||||||
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
||||||
import { notifyError } from '../utils/notify';
|
import { notifyError } from '../utils/notify';
|
||||||
@@ -65,34 +61,45 @@ class CompanySettingsDialog extends Component {
|
|||||||
this.syncFormFromCompany = this.syncFormFromCompany.bind(this);
|
this.syncFormFromCompany = this.syncFormFromCompany.bind(this);
|
||||||
this.handleCreateTerminal = this.handleCreateTerminal.bind(this);
|
this.handleCreateTerminal = this.handleCreateTerminal.bind(this);
|
||||||
this.handleDeleteTerminal = this.handleDeleteTerminal.bind(this);
|
this.handleDeleteTerminal = this.handleDeleteTerminal.bind(this);
|
||||||
|
this.loadCompany = this.loadCompany.bind(this);
|
||||||
this.loadTerminals = this.loadTerminals.bind(this);
|
this.loadTerminals = this.loadTerminals.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { open, company, companyStatus } = this.props;
|
const { open } = this.props;
|
||||||
if (open && !prevProps.open) {
|
if (open && !prevProps.open) {
|
||||||
this.setState({ error: null });
|
this.setState({ error: null });
|
||||||
if (company) {
|
void this.loadCompany();
|
||||||
this.syncFormFromCompany(company);
|
|
||||||
} else if (companyStatus !== 'loading') {
|
|
||||||
void this.props.loadCurrentCompany();
|
|
||||||
}
|
|
||||||
void this.loadTerminals();
|
void this.loadTerminals();
|
||||||
}
|
}
|
||||||
if (open && company && company !== prevProps.company) {
|
|
||||||
this.syncFormFromCompany(company);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
syncFormFromCompany(company) {
|
syncFormFromCompany(company) {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: company.name ?? '',
|
name: company.name,
|
||||||
iso639: company.iso639,
|
iso639: company.iso639,
|
||||||
taxrate: company.taxrate != null ? String(company.taxrate) : '',
|
taxrate: company.taxrate != null ? String(company.taxrate) : '',
|
||||||
reducedtaxrate: company.reducedtaxrate != null ? String(company.reducedtaxrate) : '',
|
reducedtaxrate: company.reducedtaxrate != null ? String(company.reducedtaxrate) : '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadCompany() {
|
||||||
|
try {
|
||||||
|
const companyId = requireDefined(
|
||||||
|
this.props.selectedCompanyId,
|
||||||
|
'selectedCompanyId',
|
||||||
|
);
|
||||||
|
const companies = await this.context.companyPromise;
|
||||||
|
const company = requireDefined(
|
||||||
|
companies.find((c) => c.id === companyId),
|
||||||
|
'company',
|
||||||
|
);
|
||||||
|
this.syncFormFromCompany(company);
|
||||||
|
} catch (err) {
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async loadTerminals() {
|
async loadTerminals() {
|
||||||
try {
|
try {
|
||||||
const terminals = await this.context.posterminalPromise;
|
const terminals = await this.context.posterminalPromise;
|
||||||
@@ -156,13 +163,14 @@ class CompanySettingsDialog extends Component {
|
|||||||
this.setState({ busy: true, error: null });
|
this.setState({ busy: true, error: null });
|
||||||
try {
|
try {
|
||||||
await this.props
|
await this.props
|
||||||
.updateCompany({
|
.saveCompany({
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
iso639,
|
iso639,
|
||||||
taxrate: parsedTaxrate,
|
taxrate: parsedTaxrate,
|
||||||
reducedtaxrate: parsedReducedtaxrate,
|
reducedtaxrate: parsedReducedtaxrate,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
await this.context.updateCompany();
|
||||||
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
|
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -173,9 +181,8 @@ class CompanySettingsDialog extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { open, onClose, companyStatus } = this.props;
|
const { open, onClose } = this.props;
|
||||||
const { busy, error, name, iso639, taxrate, reducedtaxrate } = this.state;
|
const { busy, error, name, iso639, taxrate, reducedtaxrate } = this.state;
|
||||||
const loading = companyStatus === 'loading' && !busy;
|
|
||||||
const parsedTaxrate = parseTaxRate(taxrate);
|
const parsedTaxrate = parseTaxRate(taxrate);
|
||||||
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
|
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
|
||||||
const canSave =
|
const canSave =
|
||||||
@@ -199,148 +206,142 @@ class CompanySettingsDialog extends Component {
|
|||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{loading ? (
|
<Stack spacing={2}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
<SingleLineTextEdit
|
||||||
<CircularProgress />
|
required
|
||||||
</Box>
|
size="small"
|
||||||
) : (
|
label="Name"
|
||||||
<Stack spacing={2}>
|
value={name}
|
||||||
<SingleLineTextEdit
|
disabled={busy}
|
||||||
|
onChange={(nextName) => this.setState({ name: nextName })}
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControl required>
|
||||||
|
<FormLabel sx={{ mb: 0.5 }}>Sprache</FormLabel>
|
||||||
|
<ToggleButtonGroup
|
||||||
|
exclusive
|
||||||
required
|
required
|
||||||
size="small"
|
size="small"
|
||||||
label="Name"
|
value={iso639}
|
||||||
value={name}
|
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onChange={(nextName) => this.setState({ name: nextName })}
|
onChange={(_event, value) => {
|
||||||
autoComplete="off"
|
if (value) this.setState({ iso639: value });
|
||||||
autoFocus
|
}}
|
||||||
|
>
|
||||||
|
<ToggleButton value="de">Deutsch</ToggleButton>
|
||||||
|
<ToggleButton value="en">English</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<Stack direction="row" spacing={1}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
label="Normalsatz"
|
||||||
|
value={taxrate}
|
||||||
|
disabled={busy}
|
||||||
|
onChange={(e) => this.setState({ taxrate: e.target.value })}
|
||||||
|
error={taxrate !== '' && parsedTaxrate == null}
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
||||||
|
},
|
||||||
|
htmlInput: { min: 0, max: 100, step: 0.01, required: true },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
label="Ermäßigter Satz"
|
||||||
|
value={reducedtaxrate}
|
||||||
|
disabled={busy}
|
||||||
|
onChange={(e) => this.setState({ reducedtaxrate: e.target.value })}
|
||||||
|
error={reducedtaxrate !== '' && parsedReducedtaxrate == null}
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
||||||
|
},
|
||||||
|
htmlInput: { min: 0, max: 100, step: 0.01, required: true },
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl required>
|
|
||||||
<FormLabel sx={{ mb: 0.5 }}>Sprache</FormLabel>
|
|
||||||
<ToggleButtonGroup
|
|
||||||
exclusive
|
|
||||||
required
|
|
||||||
size="small"
|
|
||||||
value={iso639}
|
|
||||||
disabled={busy}
|
|
||||||
onChange={(_event, value) => {
|
|
||||||
if (value) this.setState({ iso639: value });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ToggleButton value="de">Deutsch</ToggleButton>
|
|
||||||
<ToggleButton value="en">English</ToggleButton>
|
|
||||||
</ToggleButtonGroup>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Stack direction="row" spacing={1}>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
size="small"
|
|
||||||
type="number"
|
|
||||||
label="Normalsatz"
|
|
||||||
value={taxrate}
|
|
||||||
disabled={busy}
|
|
||||||
onChange={(e) => this.setState({ taxrate: e.target.value })}
|
|
||||||
error={taxrate !== '' && parsedTaxrate == null}
|
|
||||||
slotProps={{
|
|
||||||
input: {
|
|
||||||
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
|
||||||
},
|
|
||||||
htmlInput: { min: 0, max: 100, step: 0.01, required: true },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
size="small"
|
|
||||||
type="number"
|
|
||||||
label="Ermäßigter Satz"
|
|
||||||
value={reducedtaxrate}
|
|
||||||
disabled={busy}
|
|
||||||
onChange={(e) => this.setState({ reducedtaxrate: e.target.value })}
|
|
||||||
error={reducedtaxrate !== '' && parsedReducedtaxrate == null}
|
|
||||||
slotProps={{
|
|
||||||
input: {
|
|
||||||
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
|
||||||
},
|
|
||||||
htmlInput: { min: 0, max: 100, step: 0.01, required: true },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Divider sx={{ my: 1 }} />
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<List dense sx={{ py: 0 }}>
|
|
||||||
{this.state.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}
|
|
||||||
autoComplete="off"
|
|
||||||
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>
|
</Stack>
|
||||||
)}
|
|
||||||
|
<Divider sx={{ my: 1 }} />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<List dense sx={{ py: 0 }}>
|
||||||
|
{this.state.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}
|
||||||
|
autoComplete="off"
|
||||||
|
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>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<Button onClick={onClose} disabled={busy}>
|
<Button onClick={onClose} disabled={busy}>
|
||||||
Abbrechen
|
Abbrechen
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" variant="contained" disabled={busy || loading || !canSave}>
|
<Button type="submit" variant="contained" disabled={busy || !canSave}>
|
||||||
Speichern
|
Speichern
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
@@ -351,13 +352,11 @@ class CompanySettingsDialog extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
company: selectCurrentCompany(state),
|
selectedCompanyId: selectAuthUser(state)?.selectedCompanyId,
|
||||||
companyStatus: selectCompanyStatus(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
loadCurrentCompany: () => dispatch(loadCurrentCompany()),
|
saveCompany: (payload) => dispatch(updateCompanyThunk(payload)),
|
||||||
updateCompany: (payload) => dispatch(updateCompanyThunk(payload)),
|
|
||||||
createPosterminal: (payload) => dispatch(createPosterminal(payload)),
|
createPosterminal: (payload) => dispatch(createPosterminal(payload)),
|
||||||
deletePosterminal: (payload) => dispatch(deletePosterminal(payload)),
|
deletePosterminal: (payload) => dispatch(deletePosterminal(payload)),
|
||||||
enableAllTerminalCategories: (payload) => dispatch(enableAllTerminalCategories(payload)),
|
enableAllTerminalCategories: (payload) => dispatch(enableAllTerminalCategories(payload)),
|
||||||
|
|||||||
Reference in New Issue
Block a user