247 lines
8.5 KiB
TypeScript
247 lines
8.5 KiB
TypeScript
import { Box, Typography, Button, Paper, TextField, FormControlLabel,
|
|
Checkbox, Select, MenuItem, FormControl, InputLabel, Divider, CircularProgress } from "@mui/material";
|
|
import { useState, useEffect } from "react";
|
|
import type { Kennzahl } from "../types/kpi";
|
|
import { typeDisplayMapping } from "../types/kpi";
|
|
|
|
interface KPIFormProps {
|
|
mode: 'add' | 'edit';
|
|
initialData?: Kennzahl | null;
|
|
onSave: (data: Partial<Kennzahl>) => Promise<void>;
|
|
onCancel: () => void;
|
|
loading?: boolean;
|
|
}
|
|
|
|
const emptyKPI: Partial<Kennzahl> = {
|
|
name: '',
|
|
description: '',
|
|
mandatory: false,
|
|
type: 'string',
|
|
translation: '',
|
|
example: '',
|
|
active: true
|
|
};
|
|
|
|
export function KPIForm({ mode, initialData, onSave, onCancel, loading = false }: KPIFormProps) {
|
|
const [formData, setFormData] = useState<Partial<Kennzahl>>(emptyKPI);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (mode === 'edit' && initialData) {
|
|
setFormData(initialData);
|
|
} else {
|
|
setFormData(emptyKPI);
|
|
}
|
|
}, [mode, initialData]);
|
|
|
|
const handleSave = async () => {
|
|
if (!formData.name?.trim()) {
|
|
alert('Name ist erforderlich');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await onSave(formData);
|
|
} catch (error) {
|
|
console.error('Error saving KPI:', error);
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
onCancel();
|
|
};
|
|
|
|
const updateField = (field: keyof Kennzahl, value: any) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box
|
|
display="flex"
|
|
justifyContent="center"
|
|
alignItems="center"
|
|
minHeight="400px"
|
|
flexDirection="column"
|
|
>
|
|
<CircularProgress sx={{ color: '#383838', mb: 2 }} />
|
|
<Typography>
|
|
{mode === 'edit' ? 'Lade KPI Details...' : 'Laden...'}
|
|
</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Paper
|
|
elevation={2}
|
|
sx={{
|
|
width: "90%",
|
|
maxWidth: 800,
|
|
p: 4,
|
|
borderRadius: 2,
|
|
backgroundColor: "white"
|
|
}}
|
|
>
|
|
<Box mb={4}>
|
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
Kennzahl
|
|
</Typography>
|
|
<TextField
|
|
fullWidth
|
|
label="Name *"
|
|
value={formData.name || ''}
|
|
onChange={(e) => updateField('name', e.target.value)}
|
|
sx={{ mb: 2 }}
|
|
required
|
|
error={!formData.name?.trim()}
|
|
helperText={!formData.name?.trim() ? 'Name ist erforderlich' : ''}
|
|
/>
|
|
</Box>
|
|
|
|
<Divider sx={{ my: 3 }} />
|
|
|
|
<Box mb={4}>
|
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
Beschreibung
|
|
</Typography>
|
|
<TextField
|
|
fullWidth
|
|
multiline
|
|
rows={3}
|
|
label="Beschreibung"
|
|
value={formData.description || ''}
|
|
onChange={(e) => updateField('description', e.target.value)}
|
|
helperText="Beschreibung der Kennzahl"
|
|
/>
|
|
|
|
<Box mt={3}>
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={formData.mandatory || false}
|
|
onChange={(e) => updateField('mandatory', e.target.checked)}
|
|
sx={{ color: '#383838' }}
|
|
/>
|
|
}
|
|
label="Erforderlich"
|
|
/>
|
|
<Typography variant="body2" color="text.secondary" ml={4}>
|
|
Die Kennzahl erlaubt keine leeren Werte
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Divider sx={{ my: 3 }} />
|
|
|
|
<Box mb={4}>
|
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
Format: {typeDisplayMapping[formData.type as keyof typeof typeDisplayMapping] || formData.type}
|
|
</Typography>
|
|
<FormControl fullWidth sx={{ mb: 2 }}>
|
|
<InputLabel>Typ</InputLabel>
|
|
<Select
|
|
value={formData.type || 'string'}
|
|
label="Typ"
|
|
onChange={(e) => updateField('type', e.target.value)}
|
|
>
|
|
<MenuItem value="string">Text</MenuItem>
|
|
<MenuItem value="number">Zahl</MenuItem>
|
|
<MenuItem value="date">Datum</MenuItem>
|
|
<MenuItem value="boolean">Ja/Nein</MenuItem>
|
|
<MenuItem value="array">Liste (mehrfach)</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
|
|
<Divider sx={{ my: 3 }} />
|
|
|
|
<Box mb={4}>
|
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
Synonyme & Übersetzungen
|
|
</Typography>
|
|
<TextField
|
|
fullWidth
|
|
label="Übersetzung"
|
|
value={formData.translation || ''}
|
|
onChange={(e) => updateField('translation', e.target.value)}
|
|
helperText="z.B. Englische Übersetzung der Kennzahl"
|
|
/>
|
|
</Box>
|
|
|
|
<Divider sx={{ my: 3 }} />
|
|
|
|
<Box mb={4}>
|
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
Beispiele von Kennzahl
|
|
</Typography>
|
|
<TextField
|
|
fullWidth
|
|
multiline
|
|
rows={2}
|
|
label="Beispiel"
|
|
value={formData.example || ''}
|
|
onChange={(e) => updateField('example', e.target.value)}
|
|
helperText="Beispielwerte für diese Kennzahl"
|
|
/>
|
|
</Box>
|
|
|
|
{mode === 'add' && (
|
|
<>
|
|
<Divider sx={{ my: 3 }} />
|
|
<Box mb={4}>
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={formData.active !== false}
|
|
onChange={(e) => updateField('active', e.target.checked)}
|
|
sx={{ color: '#383838' }}
|
|
/>
|
|
}
|
|
label="Aktiv"
|
|
/>
|
|
<Typography variant="body2" color="text.secondary" ml={4}>
|
|
Die Kennzahl ist aktiv und wird angezeigt
|
|
</Typography>
|
|
</Box>
|
|
</>
|
|
)}
|
|
|
|
<Box display="flex" justifyContent="flex-end" gap={2} mt={4}>
|
|
<Button
|
|
variant="outlined"
|
|
onClick={handleCancel}
|
|
disabled={isSaving}
|
|
sx={{
|
|
borderColor: "#383838",
|
|
color: "#383838",
|
|
"&:hover": { borderColor: "#2e2e2e", backgroundColor: "#f5f5f5" }
|
|
}}
|
|
>
|
|
Abbrechen
|
|
</Button>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSave}
|
|
disabled={isSaving || !formData.name?.trim()}
|
|
sx={{
|
|
backgroundColor: "#383838",
|
|
"&:hover": { backgroundColor: "#2e2e2e" },
|
|
}}
|
|
>
|
|
{isSaving ? (
|
|
<>
|
|
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
|
|
{mode === 'add' ? 'Hinzufügen...' : 'Speichern...'}
|
|
</>
|
|
) : (
|
|
mode === 'add' ? 'Hinzufügen' : 'Speichern'
|
|
)}
|
|
</Button>
|
|
</Box>
|
|
</Paper>
|
|
);
|
|
} |