u
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
@@ -20,225 +21,256 @@ import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import BusinessIcon from '@mui/icons-material/Business';
|
||||
import { company } from '../api';
|
||||
import { useAuth } from '../auth/useAuth';
|
||||
import { createCompany, logout, selectCompany } from '../store/authSlice';
|
||||
import {
|
||||
defaultIso639,
|
||||
ISO639_OPTIONS,
|
||||
parseTaxRate,
|
||||
} from '../utils/companyForm';
|
||||
|
||||
export default function CompanySetupDialog() {
|
||||
const { createCompany, selectCompany, logout } = useAuth();
|
||||
const [companies, setCompanies] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [name, setName] = useState('');
|
||||
const [iso639, setIso639] = useState(defaultIso639);
|
||||
const [taxrate, setTaxrate] = useState('19');
|
||||
const [reducedtaxrate, setReducedtaxrate] = useState('7');
|
||||
class CompanySetupDialog extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
companies: [],
|
||||
loading: true,
|
||||
busy: false,
|
||||
error: null,
|
||||
name: '',
|
||||
iso639: defaultIso639(),
|
||||
taxrate: '19',
|
||||
reducedtaxrate: '7',
|
||||
};
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
this.handleCreate = this.handleCreate.bind(this);
|
||||
this.handleLogout = this.handleLogout.bind(this);
|
||||
}
|
||||
|
||||
const hasCompanies = companies.length > 0;
|
||||
componentDidMount() {
|
||||
void this.loadCompanies();
|
||||
}
|
||||
|
||||
const loadCompanies = useCallback(async () => {
|
||||
setLoading(true);
|
||||
async loadCompanies() {
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const data = await company.list();
|
||||
setCompanies(data.companies ?? []);
|
||||
this.setState({ companies: data.companies ?? [] });
|
||||
} catch {
|
||||
setCompanies([]);
|
||||
this.setState({ companies: [] });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void loadCompanies();
|
||||
}, [loadCompanies]);
|
||||
|
||||
const parsedTaxrate = parseTaxRate(taxrate);
|
||||
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
|
||||
const canCreate =
|
||||
name.trim() !== '' &&
|
||||
ISO639_OPTIONS.includes(iso639) &&
|
||||
parsedTaxrate != null &&
|
||||
parsedReducedtaxrate != null;
|
||||
|
||||
const handleSelect = async (companyId) => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
async handleSelect(companyId) {
|
||||
this.setState({ busy: true, error: null });
|
||||
try {
|
||||
await selectCompany(companyId);
|
||||
await this.props.selectCompany(companyId).unwrap();
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
this.setState({ error: err.message });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleCreate = async (event) => {
|
||||
async handleCreate(event) {
|
||||
event.preventDefault();
|
||||
const { name, iso639, taxrate, reducedtaxrate } = this.state;
|
||||
const parsedTaxrate = parseTaxRate(taxrate);
|
||||
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
|
||||
const canCreate =
|
||||
name.trim() !== '' &&
|
||||
ISO639_OPTIONS.includes(iso639) &&
|
||||
parsedTaxrate != null &&
|
||||
parsedReducedtaxrate != null;
|
||||
|
||||
if (!canCreate) {
|
||||
setError('Bitte alle Pflichtfelder ausfüllen.');
|
||||
this.setState({ error: 'Bitte alle Pflichtfelder ausfüllen.' });
|
||||
return;
|
||||
}
|
||||
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
this.setState({ busy: true, error: null });
|
||||
try {
|
||||
await createCompany({
|
||||
name: name.trim(),
|
||||
iso639,
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
});
|
||||
await this.props
|
||||
.createCompany({
|
||||
name: name.trim(),
|
||||
iso639,
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
})
|
||||
.unwrap();
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
this.setState({ error: err.message });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open disableEscapeKeyDown maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{loading
|
||||
? 'Unternehmen'
|
||||
: hasCompanies
|
||||
? 'Unternehmen auswählen'
|
||||
: 'Unternehmen anlegen'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{!loading ? (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
{hasCompanies
|
||||
? 'Wählen Sie ein bestehendes Unternehmen oder legen Sie ein neues an.'
|
||||
: 'Legen Sie Ihr Unternehmen an, um fortzufahren.'}
|
||||
</Typography>
|
||||
) : null}
|
||||
handleLogout() {
|
||||
void this.props.logout();
|
||||
}
|
||||
|
||||
{error ? (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
) : null}
|
||||
render() {
|
||||
const { companies, loading, busy, error, name, iso639, taxrate, reducedtaxrate } =
|
||||
this.state;
|
||||
const hasCompanies = companies.length > 0;
|
||||
const parsedTaxrate = parseTaxRate(taxrate);
|
||||
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
|
||||
const canCreate =
|
||||
name.trim() !== '' &&
|
||||
ISO639_OPTIONS.includes(iso639) &&
|
||||
parsedTaxrate != null &&
|
||||
parsedReducedtaxrate != null;
|
||||
|
||||
{loading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : (
|
||||
<Stack spacing={3}>
|
||||
{hasCompanies ? (
|
||||
<Box>
|
||||
<List disablePadding sx={{ bgcolor: 'background.paper', borderRadius: 1 }}>
|
||||
{companies.map((item) => (
|
||||
<ListItemButton
|
||||
key={item.id}
|
||||
disabled={busy}
|
||||
onClick={() => handleSelect(item.id)}
|
||||
>
|
||||
<BusinessIcon sx={{ mr: 1.5, color: 'primary.main' }} />
|
||||
<ListItemText primary={item.name} />
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
) : null}
|
||||
return (
|
||||
<Dialog open disableEscapeKeyDown maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{loading
|
||||
? 'Unternehmen'
|
||||
: hasCompanies
|
||||
? 'Unternehmen auswählen'
|
||||
: 'Unternehmen anlegen'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{!loading ? (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
{hasCompanies
|
||||
? 'Wählen Sie ein bestehendes Unternehmen oder legen Sie ein neues an.'
|
||||
: 'Legen Sie Ihr Unternehmen an, um fortzufahren.'}
|
||||
</Typography>
|
||||
) : null}
|
||||
|
||||
{hasCompanies ? <Divider>oder</Divider> : null}
|
||||
{error ? (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<Box component="form" onSubmit={handleCreate} noValidate>
|
||||
<Stack spacing={2}>
|
||||
{hasCompanies ? (
|
||||
<Typography variant="subtitle2">Neues Unternehmen anlegen</Typography>
|
||||
) : null}
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
size="small"
|
||||
label="Name"
|
||||
value={name}
|
||||
disabled={busy}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
autoComplete="organization"
|
||||
autoFocus={!hasCompanies}
|
||||
/>
|
||||
|
||||
<FormControl required>
|
||||
<FormLabel sx={{ mb: 0.5 }}>Sprache</FormLabel>
|
||||
<ToggleButtonGroup
|
||||
exclusive
|
||||
required
|
||||
size="small"
|
||||
value={iso639}
|
||||
disabled={busy}
|
||||
onChange={(_event, value) => {
|
||||
if (value) setIso639(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) => setTaxrate(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) => setReducedtaxrate(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>
|
||||
|
||||
<Button type="submit" variant="contained" disabled={busy || !canCreate}>
|
||||
Anlegen
|
||||
</Button>
|
||||
</Stack>
|
||||
{loading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : (
|
||||
<Stack spacing={3}>
|
||||
{hasCompanies ? (
|
||||
<Box>
|
||||
<List disablePadding sx={{ bgcolor: 'background.paper', borderRadius: 1 }}>
|
||||
{companies.map((item) => (
|
||||
<ListItemButton
|
||||
key={item.id}
|
||||
disabled={busy}
|
||||
onClick={() => this.handleSelect(item.id)}
|
||||
>
|
||||
<BusinessIcon sx={{ mr: 1.5, color: 'primary.main' }} />
|
||||
<ListItemText primary={item.name} />
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="text"
|
||||
color="inherit"
|
||||
disabled={busy}
|
||||
onClick={() => logout()}
|
||||
sx={{ alignSelf: 'center' }}
|
||||
>
|
||||
Abmelden
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
{hasCompanies ? <Divider>oder</Divider> : null}
|
||||
|
||||
<Box component="form" onSubmit={this.handleCreate} noValidate>
|
||||
<Stack spacing={2}>
|
||||
{hasCompanies ? (
|
||||
<Typography variant="subtitle2">Neues Unternehmen anlegen</Typography>
|
||||
) : null}
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
size="small"
|
||||
label="Name"
|
||||
value={name}
|
||||
disabled={busy}
|
||||
onChange={(e) => this.setState({ name: e.target.value })}
|
||||
autoComplete="organization"
|
||||
autoFocus={!hasCompanies}
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
<Button type="submit" variant="contained" disabled={busy || !canCreate}>
|
||||
Anlegen
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="text"
|
||||
color="inherit"
|
||||
disabled={busy}
|
||||
onClick={this.handleLogout}
|
||||
sx={{ alignSelf: 'center' }}
|
||||
>
|
||||
Abmelden
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
createCompany: (payload) => dispatch(createCompany(payload)),
|
||||
selectCompany: (companyId) => dispatch(selectCompany(companyId)),
|
||||
logout: () => dispatch(logout()),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(CompanySetupDialog);
|
||||
|
||||
Reference in New Issue
Block a user