Files
fibdash/client/src/components/admin/BUTable.js

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;