feat(frontend): implement unified design system with 17 reusable template components, skeleton loading states, and golden-ratio-based layouts
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Button, TextField, MenuItem, Select, FormControl,
|
||||
InputLabel, IconButton, Grid, Collapse,
|
||||
Button, TextField, MenuItem, Select, FormControl,
|
||||
InputLabel, Grid, Collapse,
|
||||
} from '@mui/material';
|
||||
import { ArrowBack, Add as AddIcon } from '@mui/icons-material';
|
||||
import { Add as AddIcon } from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageHeader, FormLayout } from '../components/templates';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { issuesApi } from '../services/issues';
|
||||
import type { CreateIssuePayload } from '../types/issue.types';
|
||||
@@ -52,99 +53,98 @@ export default function IssueNeu() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
{/* Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<IconButton onClick={() => navigate('/issues')}>
|
||||
<ArrowBack />
|
||||
</IconButton>
|
||||
<Typography variant="h5">Neues Issue</Typography>
|
||||
</Box>
|
||||
<PageHeader
|
||||
title="Neues Issue"
|
||||
breadcrumbs={[
|
||||
{ label: 'Issues', href: '/issues' },
|
||||
{ label: 'Neues Issue' },
|
||||
]}
|
||||
backTo="/issues"
|
||||
/>
|
||||
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<FormLayout
|
||||
actions={<>
|
||||
<Button onClick={() => navigate('/issues')}>Abbrechen</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!form.titel.trim() || createMut.isPending}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Erstellen
|
||||
</Button>
|
||||
</>}
|
||||
>
|
||||
<TextField
|
||||
label="Titel"
|
||||
required
|
||||
fullWidth
|
||||
value={form.titel}
|
||||
onChange={(e) => setForm({ ...form, titel: e.target.value })}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<Collapse in={showDescription} unmountOnExit>
|
||||
<TextField
|
||||
label="Titel"
|
||||
required
|
||||
label="Beschreibung"
|
||||
multiline
|
||||
rows={4}
|
||||
fullWidth
|
||||
value={form.titel}
|
||||
onChange={(e) => setForm({ ...form, titel: e.target.value })}
|
||||
autoFocus
|
||||
value={form.beschreibung || ''}
|
||||
onChange={(e) => setForm({ ...form, beschreibung: e.target.value })}
|
||||
/>
|
||||
</Collapse>
|
||||
{!showDescription && (
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setShowDescription(true)}
|
||||
sx={{ alignSelf: 'flex-start' }}
|
||||
>
|
||||
Beschreibung hinzufuegen
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Collapse in={showDescription} unmountOnExit>
|
||||
<TextField
|
||||
label="Beschreibung"
|
||||
multiline
|
||||
rows={4}
|
||||
fullWidth
|
||||
value={form.beschreibung || ''}
|
||||
onChange={(e) => setForm({ ...form, beschreibung: e.target.value })}
|
||||
/>
|
||||
</Collapse>
|
||||
{!showDescription && (
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setShowDescription(true)}
|
||||
sx={{ alignSelf: 'flex-start' }}
|
||||
>
|
||||
Beschreibung hinzufuegen
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Typ</InputLabel>
|
||||
<Select
|
||||
value={form.typ_id ?? defaultTypId ?? ''}
|
||||
label="Typ"
|
||||
onChange={(e) => setForm({ ...form, typ_id: Number(e.target.value) })}
|
||||
>
|
||||
{types.filter(t => t.aktiv).map(t => (
|
||||
<MenuItem key={t.id} value={t.id}>{t.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Prioritaet</InputLabel>
|
||||
<Select
|
||||
value={form.prioritaet || defaultPriority}
|
||||
label="Prioritaet"
|
||||
onChange={(e) => setForm({ ...form, prioritaet: e.target.value })}
|
||||
>
|
||||
{priorities.filter(p => p.aktiv).map(p => (
|
||||
<MenuItem key={p.schluessel} value={p.schluessel}>{p.bezeichnung}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
label="Fällig am"
|
||||
type="date"
|
||||
fullWidth
|
||||
value={form.faellig_am || ''}
|
||||
onChange={(e) => setForm({ ...form, faellig_am: e.target.value || null })}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Typ</InputLabel>
|
||||
<Select
|
||||
value={form.typ_id ?? defaultTypId ?? ''}
|
||||
label="Typ"
|
||||
onChange={(e) => setForm({ ...form, typ_id: Number(e.target.value) })}
|
||||
>
|
||||
{types.filter(t => t.aktiv).map(t => (
|
||||
<MenuItem key={t.id} value={t.id}>{t.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 1 }}>
|
||||
<Button onClick={() => navigate('/issues')}>Abbrechen</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!form.titel.trim() || createMut.isPending}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Erstellen
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Prioritaet</InputLabel>
|
||||
<Select
|
||||
value={form.prioritaet || defaultPriority}
|
||||
label="Prioritaet"
|
||||
onChange={(e) => setForm({ ...form, prioritaet: e.target.value })}
|
||||
>
|
||||
{priorities.filter(p => p.aktiv).map(p => (
|
||||
<MenuItem key={p.schluessel} value={p.schluessel}>{p.bezeichnung}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
label="Fällig am"
|
||||
type="date"
|
||||
fullWidth
|
||||
value={form.faellig_am || ''}
|
||||
onChange={(e) => setForm({ ...form, faellig_am: e.target.value || null })}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</FormLayout>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user