Implement navigation and view management in DataViewer, adding Dashboard and TableManagement components. Update export data handling based on current view. Create new components for managing Kreditor, Konto, and BU tables with CRUD functionality. Refactor admin routes to remove admin access checks and streamline data handling for various entities.
This commit is contained in:
@@ -7,6 +7,9 @@ import {
|
||||
import AuthService from '../services/AuthService';
|
||||
import SummaryHeader from './SummaryHeader';
|
||||
import TransactionsTable from './TransactionsTable';
|
||||
import Navigation from './Navigation';
|
||||
import Dashboard from './Dashboard';
|
||||
import TableManagement from './TableManagement';
|
||||
|
||||
class DataViewer extends Component {
|
||||
constructor(props) {
|
||||
@@ -18,6 +21,7 @@ class DataViewer extends Component {
|
||||
summary: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
currentView: 'dashboard', // 'dashboard' or 'tables'
|
||||
};
|
||||
this.authService = new AuthService();
|
||||
}
|
||||
@@ -110,14 +114,21 @@ class DataViewer extends Component {
|
||||
if (this.props.onUpdateExportData) {
|
||||
this.props.onUpdateExportData({
|
||||
selectedMonth,
|
||||
canExport: !!selectedMonth && !this.state.loading,
|
||||
canExport: !!selectedMonth && !this.state.loading && this.state.currentView === 'dashboard',
|
||||
onExport: this.downloadDatev
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleViewChange = (event, newView) => {
|
||||
this.setState({ currentView: newView });
|
||||
// Update export data when view changes
|
||||
this.updateExportData();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { months, selectedMonth, transactions, summary, loading, error } = this.state;
|
||||
const { months, selectedMonth, transactions, summary, loading, error, currentView } = this.state;
|
||||
const { user } = this.props;
|
||||
|
||||
if (loading && !transactions.length) {
|
||||
return (
|
||||
@@ -137,23 +148,36 @@ class DataViewer extends Component {
|
||||
|
||||
return (
|
||||
<Box sx={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<Box sx={{ flexShrink: 0 }}>
|
||||
<SummaryHeader
|
||||
months={months}
|
||||
selectedMonth={selectedMonth}
|
||||
summary={summary}
|
||||
loading={loading}
|
||||
onMonthChange={this.handleMonthChange}
|
||||
/>
|
||||
</Box>
|
||||
<Navigation
|
||||
currentView={currentView}
|
||||
onViewChange={this.handleViewChange}
|
||||
/>
|
||||
|
||||
<Box sx={{ flex: 1, minHeight: 0 }}>
|
||||
<TransactionsTable
|
||||
transactions={transactions}
|
||||
selectedMonth={selectedMonth}
|
||||
loading={loading}
|
||||
/>
|
||||
</Box>
|
||||
{currentView === 'dashboard' ? (
|
||||
<>
|
||||
<Box sx={{ flexShrink: 0 }}>
|
||||
<SummaryHeader
|
||||
months={months}
|
||||
selectedMonth={selectedMonth}
|
||||
summary={summary}
|
||||
loading={loading}
|
||||
onMonthChange={this.handleMonthChange}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flex: 1, minHeight: 0 }}>
|
||||
<TransactionsTable
|
||||
transactions={transactions}
|
||||
selectedMonth={selectedMonth}
|
||||
loading={loading}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Box sx={{ flex: 1, minHeight: 0, overflow: 'auto', p: 2 }}>
|
||||
<TableManagement user={user} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
45
client/src/components/Navigation.js
Normal file
45
client/src/components/Navigation.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
Paper,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Dashboard as DashboardIcon,
|
||||
TableChart as TableIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
class Navigation extends Component {
|
||||
render() {
|
||||
const { currentView, onViewChange } = this.props;
|
||||
|
||||
return (
|
||||
<Paper elevation={1} sx={{ mb: 3 }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs
|
||||
value={currentView}
|
||||
onChange={onViewChange}
|
||||
variant="fullWidth"
|
||||
sx={{ minHeight: 48 }}
|
||||
>
|
||||
<Tab
|
||||
icon={<DashboardIcon />}
|
||||
label="Dashboard"
|
||||
value="dashboard"
|
||||
sx={{ minHeight: 48 }}
|
||||
/>
|
||||
<Tab
|
||||
icon={<TableIcon />}
|
||||
label="Stammdaten"
|
||||
value="tables"
|
||||
sx={{ minHeight: 48 }}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Navigation;
|
||||
76
client/src/components/TableManagement.js
Normal file
76
client/src/components/TableManagement.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
Paper,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AccountBalance as KreditorIcon,
|
||||
AccountBalanceWallet as KontoIcon,
|
||||
Receipt as BUIcon,
|
||||
} from '@mui/icons-material';
|
||||
import KreditorTable from './admin/KreditorTable';
|
||||
import KontoTable from './admin/KontoTable';
|
||||
import BUTable from './admin/BUTable';
|
||||
|
||||
class TableManagement extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTab: 0,
|
||||
};
|
||||
}
|
||||
|
||||
handleTabChange = (event, newValue) => {
|
||||
this.setState({ activeTab: newValue });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { activeTab } = this.state;
|
||||
const { user } = this.props;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4" component="h1" gutterBottom>
|
||||
Stammdaten verwalten
|
||||
</Typography>
|
||||
|
||||
<Paper elevation={2}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={this.handleTabChange}
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab
|
||||
icon={<KreditorIcon />}
|
||||
label="Kreditoren"
|
||||
sx={{ minHeight: 64 }}
|
||||
/>
|
||||
<Tab
|
||||
icon={<KontoIcon />}
|
||||
label="Konten"
|
||||
sx={{ minHeight: 64 }}
|
||||
/>
|
||||
<Tab
|
||||
icon={<BUIcon />}
|
||||
label="Buchungsschlüssel"
|
||||
sx={{ minHeight: 64 }}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 3 }}>
|
||||
{activeTab === 0 && <KreditorTable user={user} />}
|
||||
{activeTab === 1 && <KontoTable user={user} />}
|
||||
{activeTab === 2 && <BUTable user={user} />}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TableManagement;
|
||||
325
client/src/components/admin/BUTable.js
Normal file
325
client/src/components/admin/BUTable.js
Normal file
@@ -0,0 +1,325 @@
|
||||
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();
|
||||
}
|
||||
|
||||
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) => {
|
||||
this.setState({
|
||||
dialogOpen: true,
|
||||
editingBU: bu,
|
||||
formData: bu ? {
|
||||
bu: bu.bu,
|
||||
name: bu.name,
|
||||
vst: bu.vst || '',
|
||||
} : {
|
||||
bu: '',
|
||||
name: '',
|
||||
vst: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleCloseDialog = () => {
|
||||
this.setState({
|
||||
dialogOpen: false,
|
||||
editingBU: null,
|
||||
formData: {
|
||||
bu: '',
|
||||
name: '',
|
||||
vst: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleInputChange = (field) => (event) => {
|
||||
this.setState({
|
||||
formData: {
|
||||
...this.state.formData,
|
||||
[field]: event.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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) => {
|
||||
this.setState({
|
||||
confirmDialogOpen: true,
|
||||
itemToDelete: bu,
|
||||
});
|
||||
};
|
||||
|
||||
handleDeleteConfirm = async () => {
|
||||
const { itemToDelete } = this.state;
|
||||
if (!itemToDelete) return;
|
||||
|
||||
this.setState({ confirmDialogOpen: false, itemToDelete: null });
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
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 ? `${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>
|
||||
<DialogTitle>
|
||||
{editingBU ? 'Buchungsschlüssel bearbeiten' : 'Neuer Buchungsschlüssel'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<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">
|
||||
Speichern
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={this.state.confirmDialogOpen}
|
||||
onClose={this.handleDeleteCancel}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Löschen bestätigen</DialogTitle>
|
||||
<DialogContent>
|
||||
<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;
|
||||
295
client/src/components/admin/KontoTable.js
Normal file
295
client/src/components/admin/KontoTable.js
Normal file
@@ -0,0 +1,295 @@
|
||||
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 KontoTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
konten: [],
|
||||
loading: true,
|
||||
error: null,
|
||||
dialogOpen: false,
|
||||
editingKonto: null,
|
||||
confirmDialogOpen: false,
|
||||
itemToDelete: null,
|
||||
formData: {
|
||||
konto: '',
|
||||
name: '',
|
||||
},
|
||||
};
|
||||
this.authService = new AuthService();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadKonten();
|
||||
}
|
||||
|
||||
loadKonten = async () => {
|
||||
try {
|
||||
const response = await this.authService.apiCall('/admin/konten');
|
||||
if (response && response.ok) {
|
||||
const data = await response.json();
|
||||
this.setState({ konten: data.konten, loading: false });
|
||||
} else {
|
||||
this.setState({ error: 'Fehler beim Laden der Konten', loading: false });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading konten:', error);
|
||||
this.setState({ error: 'Fehler beim Laden der Konten', loading: false });
|
||||
}
|
||||
};
|
||||
|
||||
handleOpenDialog = (konto = null) => {
|
||||
this.setState({
|
||||
dialogOpen: true,
|
||||
editingKonto: konto,
|
||||
formData: konto ? {
|
||||
konto: konto.konto,
|
||||
name: konto.name,
|
||||
} : {
|
||||
konto: '',
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleCloseDialog = () => {
|
||||
this.setState({
|
||||
dialogOpen: false,
|
||||
editingKonto: null,
|
||||
formData: {
|
||||
konto: '',
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleInputChange = (field) => (event) => {
|
||||
this.setState({
|
||||
formData: {
|
||||
...this.state.formData,
|
||||
[field]: event.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleSave = async () => {
|
||||
const { editingKonto, formData } = this.state;
|
||||
|
||||
try {
|
||||
const url = editingKonto
|
||||
? `/admin/konten/${editingKonto.id}`
|
||||
: '/admin/konten';
|
||||
|
||||
const method = editingKonto ? 'PUT' : 'POST';
|
||||
|
||||
const response = await this.authService.apiCall(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
this.handleCloseDialog();
|
||||
this.loadKonten();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
this.setState({ error: errorData.error || 'Fehler beim Speichern' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving konto:', error);
|
||||
this.setState({ error: 'Fehler beim Speichern des Kontos' });
|
||||
}
|
||||
};
|
||||
|
||||
handleDeleteClick = (konto) => {
|
||||
this.setState({
|
||||
confirmDialogOpen: true,
|
||||
itemToDelete: konto,
|
||||
});
|
||||
};
|
||||
|
||||
handleDeleteConfirm = async () => {
|
||||
const { itemToDelete } = this.state;
|
||||
if (!itemToDelete) return;
|
||||
|
||||
this.setState({ confirmDialogOpen: false, itemToDelete: null });
|
||||
|
||||
try {
|
||||
const response = await this.authService.apiCall(`/admin/konten/${konto.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
this.loadKonten();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
this.setState({ error: errorData.error || 'Fehler beim Löschen' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting konto:', error);
|
||||
this.setState({ error: 'Fehler beim Löschen des Kontos' });
|
||||
}
|
||||
};
|
||||
|
||||
handleDeleteCancel = () => {
|
||||
this.setState({
|
||||
confirmDialogOpen: false,
|
||||
itemToDelete: null,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { konten, loading, error, dialogOpen, editingKonto, 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">Konten</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => this.handleOpenDialog()}
|
||||
>
|
||||
Neues Konto
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Konto</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell align="right">Aktionen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{konten.map((konto) => (
|
||||
<TableRow key={konto.id}>
|
||||
<TableCell>{konto.konto}</TableCell>
|
||||
<TableCell>{konto.name}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleOpenDialog(konto)}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleDeleteClick(konto)}
|
||||
color="error"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Dialog open={dialogOpen} onClose={this.handleCloseDialog} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editingKonto ? 'Konto bearbeiten' : 'Neues Konto'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Konto"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={formData.konto}
|
||||
onChange={this.handleInputChange('konto')}
|
||||
sx={{ mb: 2 }}
|
||||
helperText="z.B. 5400"
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Name"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={formData.name}
|
||||
onChange={this.handleInputChange('name')}
|
||||
helperText="z.B. Wareneingang 19%"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleCloseDialog}>Abbrechen</Button>
|
||||
<Button onClick={this.handleSave} variant="contained">
|
||||
Speichern
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={this.state.confirmDialogOpen}
|
||||
onClose={this.handleDeleteCancel}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Löschen bestätigen</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
{this.state.itemToDelete &&
|
||||
`Konto "${this.state.itemToDelete.konto} - ${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 KontoTable;
|
||||
308
client/src/components/admin/KreditorTable.js
Normal file
308
client/src/components/admin/KreditorTable.js
Normal file
@@ -0,0 +1,308 @@
|
||||
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 KreditorTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
kreditoren: [],
|
||||
loading: true,
|
||||
error: null,
|
||||
dialogOpen: false,
|
||||
editingKreditor: null,
|
||||
confirmDialogOpen: false,
|
||||
itemToDelete: null,
|
||||
formData: {
|
||||
iban: '',
|
||||
name: '',
|
||||
kreditorId: '',
|
||||
},
|
||||
};
|
||||
this.authService = new AuthService();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadKreditoren();
|
||||
}
|
||||
|
||||
loadKreditoren = async () => {
|
||||
try {
|
||||
const response = await this.authService.apiCall('/admin/kreditoren');
|
||||
if (response && response.ok) {
|
||||
const data = await response.json();
|
||||
this.setState({ kreditoren: data.kreditoren, loading: false });
|
||||
} else {
|
||||
this.setState({ error: 'Fehler beim Laden der Kreditoren', loading: false });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading kreditoren:', error);
|
||||
this.setState({ error: 'Fehler beim Laden der Kreditoren', loading: false });
|
||||
}
|
||||
};
|
||||
|
||||
handleOpenDialog = (kreditor = null) => {
|
||||
this.setState({
|
||||
dialogOpen: true,
|
||||
editingKreditor: kreditor,
|
||||
formData: kreditor ? {
|
||||
iban: kreditor.iban,
|
||||
name: kreditor.name,
|
||||
kreditorId: kreditor.kreditorId,
|
||||
} : {
|
||||
iban: '',
|
||||
name: '',
|
||||
kreditorId: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleCloseDialog = () => {
|
||||
this.setState({
|
||||
dialogOpen: false,
|
||||
editingKreditor: null,
|
||||
formData: {
|
||||
iban: '',
|
||||
name: '',
|
||||
kreditorId: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleInputChange = (field) => (event) => {
|
||||
this.setState({
|
||||
formData: {
|
||||
...this.state.formData,
|
||||
[field]: event.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleSave = async () => {
|
||||
const { editingKreditor, formData } = this.state;
|
||||
|
||||
try {
|
||||
const url = editingKreditor
|
||||
? `/admin/kreditoren/${editingKreditor.id}`
|
||||
: '/admin/kreditoren';
|
||||
|
||||
const method = editingKreditor ? 'PUT' : 'POST';
|
||||
|
||||
const response = await this.authService.apiCall(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
this.handleCloseDialog();
|
||||
this.loadKreditoren();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
this.setState({ error: errorData.error || 'Fehler beim Speichern' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving kreditor:', error);
|
||||
this.setState({ error: 'Fehler beim Speichern des Kreditors' });
|
||||
}
|
||||
};
|
||||
|
||||
handleDeleteClick = (kreditor) => {
|
||||
this.setState({
|
||||
confirmDialogOpen: true,
|
||||
itemToDelete: kreditor,
|
||||
});
|
||||
};
|
||||
|
||||
handleDeleteConfirm = async () => {
|
||||
const { itemToDelete } = this.state;
|
||||
if (!itemToDelete) return;
|
||||
|
||||
this.setState({ confirmDialogOpen: false, itemToDelete: null });
|
||||
|
||||
try {
|
||||
const response = await this.authService.apiCall(`/admin/kreditoren/${kreditor.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
this.loadKreditoren();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
this.setState({ error: errorData.error || 'Fehler beim Löschen' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting kreditor:', error);
|
||||
this.setState({ error: 'Fehler beim Löschen des Kreditors' });
|
||||
}
|
||||
};
|
||||
|
||||
handleDeleteCancel = () => {
|
||||
this.setState({
|
||||
confirmDialogOpen: false,
|
||||
itemToDelete: null,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { kreditoren, loading, error, dialogOpen, editingKreditor, 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">Kreditoren</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => this.handleOpenDialog()}
|
||||
>
|
||||
Neuer Kreditor
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Kreditor ID</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>IBAN</TableCell>
|
||||
<TableCell align="right">Aktionen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{kreditoren.map((kreditor) => (
|
||||
<TableRow key={kreditor.id}>
|
||||
<TableCell>{kreditor.kreditorId}</TableCell>
|
||||
<TableCell>{kreditor.name}</TableCell>
|
||||
<TableCell>{kreditor.iban}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleOpenDialog(kreditor)}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleDeleteClick(kreditor)}
|
||||
color="error"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Dialog open={dialogOpen} onClose={this.handleCloseDialog} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editingKreditor ? 'Kreditor bearbeiten' : 'Neuer Kreditor'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Kreditor ID"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={formData.kreditorId}
|
||||
onChange={this.handleInputChange('kreditorId')}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Name"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={formData.name}
|
||||
onChange={this.handleInputChange('name')}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="IBAN"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={formData.iban}
|
||||
onChange={this.handleInputChange('iban')}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleCloseDialog}>Abbrechen</Button>
|
||||
<Button onClick={this.handleSave} variant="contained">
|
||||
Speichern
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={this.state.confirmDialogOpen}
|
||||
onClose={this.handleDeleteCancel}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Löschen bestätigen</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
{this.state.itemToDelete &&
|
||||
`Kreditor "${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 KreditorTable;
|
||||
Reference in New Issue
Block a user