267 lines
7.4 KiB
TypeScript
267 lines
7.4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Button,
|
|
TextField,
|
|
Grid,
|
|
MenuItem,
|
|
CircularProgress,
|
|
Alert,
|
|
Typography,
|
|
} from '@mui/material';
|
|
import { incidentsApi, EINSATZ_ARTEN, EINSATZ_ART_LABELS, CreateEinsatzPayload } from '../../services/incidents';
|
|
import { useNotification } from '../../contexts/NotificationContext';
|
|
|
|
interface CreateEinsatzDialogProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
// Default alarm_time = now (rounded to minute)
|
|
function nowISO(): string {
|
|
const d = new Date();
|
|
d.setSeconds(0, 0);
|
|
return d.toISOString().slice(0, 16); // YYYY-MM-DDTHH:mm
|
|
}
|
|
|
|
const INITIAL_FORM: CreateEinsatzPayload & { alarm_time_local: string } = {
|
|
alarm_time: '',
|
|
alarm_time_local: nowISO(),
|
|
einsatz_art: 'Brand',
|
|
einsatz_stichwort: '',
|
|
strasse: '',
|
|
hausnummer: '',
|
|
ort: '',
|
|
bericht_kurz: '',
|
|
alarmierung_art: 'ILS',
|
|
status: 'aktiv',
|
|
};
|
|
|
|
const CreateEinsatzDialog: React.FC<CreateEinsatzDialogProps> = ({
|
|
open,
|
|
onClose,
|
|
onSuccess,
|
|
}) => {
|
|
const notification = useNotification();
|
|
const [form, setForm] = useState({ ...INITIAL_FORM, alarm_time_local: nowISO() });
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
) => {
|
|
const { name, value } = e.target;
|
|
setForm((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError(null);
|
|
|
|
if (!form.alarm_time_local) {
|
|
setError('Alarmzeit ist pflicht.');
|
|
return;
|
|
}
|
|
if (!form.einsatz_art) {
|
|
setError('Einsatzart ist pflicht.');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
// Convert local datetime string to UTC ISO string
|
|
const payload: CreateEinsatzPayload = {
|
|
alarm_time: new Date(form.alarm_time_local).toISOString(),
|
|
einsatz_art: form.einsatz_art,
|
|
einsatz_stichwort: form.einsatz_stichwort || null,
|
|
strasse: form.strasse || null,
|
|
hausnummer: form.hausnummer || null,
|
|
ort: form.ort || null,
|
|
bericht_kurz: form.bericht_kurz || null,
|
|
alarmierung_art: form.alarmierung_art || 'ILS',
|
|
status: form.status || 'aktiv',
|
|
};
|
|
|
|
await incidentsApi.create(payload);
|
|
notification.showSuccess('Einsatz erfolgreich angelegt');
|
|
setForm({ ...INITIAL_FORM, alarm_time_local: nowISO() });
|
|
onSuccess();
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : 'Fehler beim Anlegen des Einsatzes';
|
|
setError(msg);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
if (loading) return;
|
|
setError(null);
|
|
setForm({ ...INITIAL_FORM, alarm_time_local: nowISO() });
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onClose={handleClose}
|
|
maxWidth="sm"
|
|
fullWidth
|
|
PaperProps={{ component: 'form', onSubmit: handleSubmit }}
|
|
>
|
|
<DialogTitle>
|
|
<Typography variant="h6" component="div">
|
|
Neuen Einsatz anlegen
|
|
</Typography>
|
|
</DialogTitle>
|
|
|
|
<DialogContent dividers>
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
<Grid container spacing={2}>
|
|
{/* Alarmzeit — most important field */}
|
|
<Grid item xs={12} sm={7}>
|
|
<TextField
|
|
label="Alarmzeit *"
|
|
name="alarm_time_local"
|
|
type="datetime-local"
|
|
value={form.alarm_time_local}
|
|
onChange={handleChange}
|
|
InputLabelProps={{ shrink: true }}
|
|
fullWidth
|
|
required
|
|
helperText="DD.MM.YYYY HH:mm"
|
|
inputProps={{
|
|
'aria-label': 'Alarmzeit',
|
|
// HTML datetime-local uses YYYY-MM-DDTHH:mm format
|
|
}}
|
|
/>
|
|
</Grid>
|
|
|
|
{/* Einsatzart */}
|
|
<Grid item xs={12} sm={5}>
|
|
<TextField
|
|
label="Einsatzart *"
|
|
name="einsatz_art"
|
|
select
|
|
value={form.einsatz_art}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
required
|
|
>
|
|
{EINSATZ_ARTEN.map((art) => (
|
|
<MenuItem key={art} value={art}>
|
|
{EINSATZ_ART_LABELS[art]}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</Grid>
|
|
|
|
{/* Stichwort */}
|
|
<Grid item xs={12} sm={5}>
|
|
<TextField
|
|
label="Einsatzstichwort"
|
|
name="einsatz_stichwort"
|
|
value={form.einsatz_stichwort ?? ''}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
placeholder="z.B. B2, THL 1"
|
|
inputProps={{ maxLength: 30 }}
|
|
/>
|
|
</Grid>
|
|
|
|
{/* Alarmierungsart */}
|
|
<Grid item xs={12} sm={7}>
|
|
<TextField
|
|
label="Alarmierungsart"
|
|
name="alarmierung_art"
|
|
select
|
|
value={form.alarmierung_art}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
>
|
|
{['ILS', 'DME', 'Telefon', 'Vor_Ort', 'Sonstiges'].map((a) => (
|
|
<MenuItem key={a} value={a}>
|
|
{a === 'Vor_Ort' ? 'Vor Ort' : a}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</Grid>
|
|
|
|
{/* Location */}
|
|
<Grid item xs={12} sm={8}>
|
|
<TextField
|
|
label="Straße"
|
|
name="strasse"
|
|
value={form.strasse ?? ''}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
inputProps={{ maxLength: 150 }}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} sm={4}>
|
|
<TextField
|
|
label="Hausnr."
|
|
name="hausnummer"
|
|
value={form.hausnummer ?? ''}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
inputProps={{ maxLength: 20 }}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12}>
|
|
<TextField
|
|
label="Ort"
|
|
name="ort"
|
|
value={form.ort ?? ''}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
inputProps={{ maxLength: 100 }}
|
|
/>
|
|
</Grid>
|
|
|
|
{/* Short description */}
|
|
<Grid item xs={12}>
|
|
<TextField
|
|
label="Kurzbeschreibung"
|
|
name="bericht_kurz"
|
|
value={form.bericht_kurz ?? ''}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
multiline
|
|
rows={2}
|
|
inputProps={{ maxLength: 255 }}
|
|
helperText={`${(form.bericht_kurz ?? '').length}/255`}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</DialogContent>
|
|
|
|
<DialogActions sx={{ px: 3, py: 2 }}>
|
|
<Button onClick={handleClose} disabled={loading}>
|
|
Abbrechen
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
variant="contained"
|
|
color="primary"
|
|
disabled={loading}
|
|
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : undefined}
|
|
>
|
|
{loading ? 'Speichere...' : 'Einsatz anlegen'}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export default CreateEinsatzDialog;
|