385 lines
11 KiB
JavaScript
385 lines
11 KiB
JavaScript
import React, { Component } from 'react';
|
|
import {
|
|
Box,
|
|
Button,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Paper,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
TextField,
|
|
IconButton,
|
|
Typography,
|
|
Alert,
|
|
CircularProgress,
|
|
} from '@mui/material';
|
|
import {
|
|
Add as AddIcon,
|
|
Edit as EditIcon,
|
|
Delete as DeleteIcon,
|
|
} from '@mui/icons-material';
|
|
import AuthService from '../../services/AuthService';
|
|
|
|
class BUTable extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
buchungsschluessel: [],
|
|
loading: true,
|
|
error: null,
|
|
dialogOpen: false,
|
|
editingBU: null,
|
|
confirmDialogOpen: false,
|
|
itemToDelete: null,
|
|
formData: {
|
|
bu: '',
|
|
name: '',
|
|
vst: '',
|
|
},
|
|
};
|
|
this.authService = new AuthService();
|
|
|
|
// Focus management refs
|
|
this.triggerRef = React.createRef();
|
|
this.dialogRef = React.createRef();
|
|
this.confirmDialogRef = React.createRef();
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.loadBuchungsschluessel();
|
|
}
|
|
|
|
loadBuchungsschluessel = async () => {
|
|
try {
|
|
const response = await this.authService.apiCall('/admin/buchungsschluessel');
|
|
if (response && response.ok) {
|
|
const data = await response.json();
|
|
this.setState({ buchungsschluessel: data.buchungsschluessel, loading: false });
|
|
} else {
|
|
this.setState({ error: 'Fehler beim Laden der Buchungsschlüssel', loading: false });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading buchungsschluessel:', error);
|
|
this.setState({ error: 'Fehler beim Laden der Buchungsschlüssel', loading: false });
|
|
}
|
|
};
|
|
|
|
handleOpenDialog = (bu = null) => {
|
|
// Store reference to the trigger element for focus restoration
|
|
this.triggerRef.current = document.activeElement;
|
|
|
|
this.setState({
|
|
dialogOpen: true,
|
|
editingBU: bu,
|
|
formData: bu ? {
|
|
bu: bu.bu,
|
|
name: bu.name,
|
|
vst: bu.vst !== null && bu.vst !== undefined ? bu.vst.toString() : '',
|
|
} : {
|
|
bu: '',
|
|
name: '',
|
|
vst: '',
|
|
},
|
|
});
|
|
};
|
|
|
|
handleCloseDialog = () => {
|
|
this.setState({
|
|
dialogOpen: false,
|
|
editingBU: null,
|
|
formData: {
|
|
bu: '',
|
|
name: '',
|
|
vst: '',
|
|
},
|
|
});
|
|
|
|
// Restore focus to the trigger element after dialog closes
|
|
setTimeout(() => {
|
|
if (this.triggerRef.current && this.triggerRef.current.focus) {
|
|
this.triggerRef.current.focus();
|
|
}
|
|
}, 100);
|
|
};
|
|
|
|
handleInputChange = (field) => (event) => {
|
|
this.setState({
|
|
formData: {
|
|
...this.state.formData,
|
|
[field]: event.target.value,
|
|
},
|
|
});
|
|
};
|
|
|
|
isFormValid = () => {
|
|
const { formData } = this.state;
|
|
return formData.bu.trim() !== '' &&
|
|
formData.name.trim() !== '' &&
|
|
formData.vst !== '';
|
|
};
|
|
|
|
handleSave = async () => {
|
|
const { editingBU, formData } = this.state;
|
|
|
|
// Convert vst to number or null
|
|
const payload = {
|
|
...formData,
|
|
vst: formData.vst !== '' ? parseFloat(formData.vst) : null,
|
|
};
|
|
|
|
try {
|
|
const url = editingBU
|
|
? `/admin/buchungsschluessel/${editingBU.id}`
|
|
: '/admin/buchungsschluessel';
|
|
|
|
const method = editingBU ? 'PUT' : 'POST';
|
|
|
|
const response = await this.authService.apiCall(url, {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (response && response.ok) {
|
|
this.handleCloseDialog();
|
|
this.loadBuchungsschluessel();
|
|
} else {
|
|
const errorData = await response.json();
|
|
this.setState({ error: errorData.error || 'Fehler beim Speichern' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving BU:', error);
|
|
this.setState({ error: 'Fehler beim Speichern des Buchungsschlüssels' });
|
|
}
|
|
};
|
|
|
|
handleDeleteClick = (bu) => {
|
|
// Store reference to the trigger element for focus restoration
|
|
this.triggerRef.current = document.activeElement;
|
|
|
|
this.setState({
|
|
confirmDialogOpen: true,
|
|
itemToDelete: bu,
|
|
});
|
|
};
|
|
|
|
handleDeleteConfirm = async () => {
|
|
const { itemToDelete } = this.state;
|
|
if (!itemToDelete) return;
|
|
|
|
this.setState({ confirmDialogOpen: false, itemToDelete: null });
|
|
|
|
// Restore focus to the trigger element after dialog closes
|
|
setTimeout(() => {
|
|
if (this.triggerRef.current && this.triggerRef.current.focus) {
|
|
this.triggerRef.current.focus();
|
|
}
|
|
}, 100);
|
|
|
|
try {
|
|
const response = await this.authService.apiCall(`/admin/buchungsschluessel/${itemToDelete.id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (response && response.ok) {
|
|
this.loadBuchungsschluessel();
|
|
} else {
|
|
const errorData = await response.json();
|
|
this.setState({ error: errorData.error || 'Fehler beim Löschen' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting BU:', error);
|
|
this.setState({ error: 'Fehler beim Löschen des Buchungsschlüssels' });
|
|
}
|
|
};
|
|
|
|
handleDeleteCancel = () => {
|
|
this.setState({
|
|
confirmDialogOpen: false,
|
|
itemToDelete: null,
|
|
});
|
|
|
|
// Restore focus to the trigger element after dialog closes
|
|
setTimeout(() => {
|
|
if (this.triggerRef.current && this.triggerRef.current.focus) {
|
|
this.triggerRef.current.focus();
|
|
}
|
|
}, 100);
|
|
};
|
|
|
|
render() {
|
|
const { buchungsschluessel, loading, error, dialogOpen, editingBU, formData } = this.state;
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box>
|
|
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
|
|
<Typography variant="h6">Buchungsschlüssel</Typography>
|
|
<Button
|
|
variant="contained"
|
|
startIcon={<AddIcon />}
|
|
onClick={() => this.handleOpenDialog()}
|
|
>
|
|
Neuer Buchungsschlüssel
|
|
</Button>
|
|
</Box>
|
|
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
<TableContainer component={Paper}>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>BU</TableCell>
|
|
<TableCell>Name</TableCell>
|
|
<TableCell align="right">VST %</TableCell>
|
|
<TableCell align="right">Aktionen</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{buchungsschluessel.map((bu) => (
|
|
<TableRow key={bu.id}>
|
|
<TableCell>{bu.bu}</TableCell>
|
|
<TableCell>{bu.name}</TableCell>
|
|
<TableCell align="right">
|
|
{bu.vst !== null && bu.vst !== undefined ? `${bu.vst}%` : '-'}
|
|
</TableCell>
|
|
<TableCell align="right">
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => this.handleOpenDialog(bu)}
|
|
>
|
|
<EditIcon />
|
|
</IconButton>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => this.handleDeleteClick(bu)}
|
|
color="error"
|
|
>
|
|
<DeleteIcon />
|
|
</IconButton>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
<Dialog
|
|
open={dialogOpen}
|
|
onClose={this.handleCloseDialog}
|
|
maxWidth="sm"
|
|
fullWidth
|
|
ref={this.dialogRef}
|
|
disableAutoFocus={false}
|
|
disableEnforceFocus={false}
|
|
disableRestoreFocus={true}
|
|
aria-labelledby="bu-dialog-title"
|
|
aria-describedby="bu-dialog-content"
|
|
>
|
|
<DialogTitle id="bu-dialog-title">
|
|
{editingBU ? 'Buchungsschlüssel bearbeiten' : 'Neuer Buchungsschlüssel'}
|
|
</DialogTitle>
|
|
<DialogContent id="bu-dialog-content">
|
|
<TextField
|
|
autoFocus
|
|
margin="dense"
|
|
label="BU"
|
|
fullWidth
|
|
variant="outlined"
|
|
value={formData.bu}
|
|
onChange={this.handleInputChange('bu')}
|
|
sx={{ mb: 2 }}
|
|
helperText="z.B. 9, 8, 506, 511"
|
|
/>
|
|
<TextField
|
|
margin="dense"
|
|
label="Name"
|
|
fullWidth
|
|
variant="outlined"
|
|
value={formData.name}
|
|
onChange={this.handleInputChange('name')}
|
|
sx={{ mb: 2 }}
|
|
helperText="z.B. 19% VST, 7% VST, Dienstleistung aus EU"
|
|
/>
|
|
<TextField
|
|
margin="dense"
|
|
label="Vorsteuer %"
|
|
fullWidth
|
|
variant="outlined"
|
|
type="number"
|
|
value={formData.vst}
|
|
onChange={this.handleInputChange('vst')}
|
|
helperText="z.B. 19.00 für 19% (optional)"
|
|
inputProps={{
|
|
step: 0.01,
|
|
min: 0,
|
|
max: 100,
|
|
}}
|
|
/>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={this.handleCloseDialog}>Abbrechen</Button>
|
|
<Button
|
|
onClick={this.handleSave}
|
|
variant="contained"
|
|
disabled={!this.isFormValid()}
|
|
>
|
|
Speichern
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
{/* Confirmation Dialog */}
|
|
<Dialog
|
|
open={this.state.confirmDialogOpen}
|
|
onClose={this.handleDeleteCancel}
|
|
maxWidth="sm"
|
|
fullWidth
|
|
ref={this.confirmDialogRef}
|
|
disableAutoFocus={false}
|
|
disableEnforceFocus={false}
|
|
disableRestoreFocus={true}
|
|
aria-labelledby="confirm-dialog-title"
|
|
aria-describedby="confirm-dialog-content"
|
|
>
|
|
<DialogTitle id="confirm-dialog-title">Löschen bestätigen</DialogTitle>
|
|
<DialogContent id="confirm-dialog-content">
|
|
<Typography>
|
|
{this.state.itemToDelete &&
|
|
`Buchungsschlüssel "${this.state.itemToDelete.bu} - ${this.state.itemToDelete.name}" wirklich löschen?`
|
|
}
|
|
</Typography>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={this.handleDeleteCancel}>
|
|
Abbrechen
|
|
</Button>
|
|
<Button onClick={this.handleDeleteConfirm} color="error" variant="contained">
|
|
Löschen
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Box>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default BUTable; |