feat(frontend): implement unified design system with 17 reusable template components, skeleton loading states, and golden-ratio-based layouts

This commit is contained in:
Matthias Hochmeister
2026-04-13 10:43:27 +02:00
parent 5acfd7cc4f
commit 43ce1f930c
69 changed files with 3289 additions and 3115 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useMemo } from 'react';
import {
Box, Typography, Paper, Chip, IconButton, Button, Dialog, DialogTitle,
DialogContent, DialogActions, TextField, MenuItem, Select, FormControl,
Box, Typography, Paper, Chip, IconButton, Button,
TextField, MenuItem, Select, FormControl,
InputLabel, Divider, CircularProgress, Autocomplete, Grid, Card, CardContent,
List, ListItem, ListItemIcon, ListItemText, ListItemSecondaryAction,
} from '@mui/material';
@@ -20,6 +20,7 @@ import { usePermissionContext } from '../contexts/PermissionContext';
import { useAuth } from '../contexts/AuthContext';
import { issuesApi } from '../services/issues';
import type { Issue, IssueComment, UpdateIssuePayload, AssignableMember, IssueStatusDef, IssuePriorityDef, IssueHistorie, IssueDatei } from '../types/issue.types';
import { ConfirmDialog, FormDialog, PageHeader } from '../components/templates';
// ── Helpers (copied from Issues.tsx) ──
@@ -260,21 +261,16 @@ export default function IssueDetail() {
return (
<DashboardLayout>
<Box sx={{ p: 3 }}>
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}>
<IconButton onClick={() => navigate('/issues')}>
<ArrowBack />
</IconButton>
<Box sx={{ flex: 1 }}>
<Typography variant="h5">
{formatIssueId(issue)} {issue.titel}
</Typography>
</Box>
<Chip
label={getStatusLabel(statuses, issue.status)}
color={getStatusColor(statuses, issue.status)}
/>
</Box>
<PageHeader
title={`${formatIssueId(issue)}${issue.titel}`}
backTo="/issues"
actions={
<Chip
label={getStatusLabel(statuses, issue.status)}
color={getStatusColor(statuses, issue.status)}
/>
}
/>
{/* Info cards */}
<Grid container spacing={2} sx={{ mb: 3 }}>
@@ -559,50 +555,38 @@ export default function IssueDetail() {
</Box>
{/* Reopen Dialog */}
<Dialog open={reopenOpen} onClose={() => setReopenOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>Issue wiedereröffnen</DialogTitle>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: '20px !important' }}>
<TextField
label="Kommentar (Pflicht)"
required
multiline
rows={3}
fullWidth
value={reopenComment}
onChange={(e) => setReopenComment(e.target.value)}
autoFocus
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setReopenOpen(false)}>Abbrechen</Button>
<Button
variant="contained"
disabled={!reopenComment.trim() || updateMut.isPending}
onClick={handleReopen}
>
Wiedereröffnen
</Button>
</DialogActions>
</Dialog>
<FormDialog
open={reopenOpen}
onClose={() => setReopenOpen(false)}
onSubmit={handleReopen}
title="Issue wiedereröffnen"
submitLabel="Wiedereröffnen"
isSubmitting={updateMut.isPending}
maxWidth="sm"
>
<TextField
label="Kommentar (Pflicht)"
required
multiline
rows={3}
fullWidth
value={reopenComment}
onChange={(e) => setReopenComment(e.target.value)}
autoFocus
/>
</FormDialog>
{/* Delete Confirmation Dialog */}
<Dialog open={deleteOpen} onClose={() => setDeleteOpen(false)} maxWidth="xs" fullWidth>
<DialogTitle>Issue löschen</DialogTitle>
<DialogContent>
<Typography>Soll dieses Issue wirklich gelöscht werden?</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setDeleteOpen(false)}>Abbrechen</Button>
<Button
variant="contained"
color="error"
disabled={deleteMut.isPending}
onClick={() => deleteMut.mutate()}
>
Löschen
</Button>
</DialogActions>
</Dialog>
<ConfirmDialog
open={deleteOpen}
onClose={() => setDeleteOpen(false)}
onConfirm={() => deleteMut.mutate()}
title="Issue löschen"
message="Soll dieses Issue wirklich gelöscht werden?"
confirmLabel="Löschen"
confirmColor="error"
isLoading={deleteMut.isPending}
/>
</DashboardLayout>
);
}