inital
This commit is contained in:
69
frontend/src/pages/Ausruestung.tsx
Normal file
69
frontend/src/pages/Ausruestung.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import { Build } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
|
||||
function Ausruestung() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Ausrüstungsverwaltung
|
||||
</Typography>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Build color="primary" sx={{ fontSize: 48, mr: 2 }} />
|
||||
<Box>
|
||||
<Typography variant="h6">Ausrüstung</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Diese Funktion wird in Kürze verfügbar sein
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="body1" color="text.secondary" paragraph>
|
||||
Geplante Features:
|
||||
</Typography>
|
||||
<ul>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Inventarverwaltung
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Wartungsprüfungen und -protokolle
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Prüffristen und Erinnerungen
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Schutzausrüstung (PSA)
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Atemschutzgeräte und -wartung
|
||||
</Typography>
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Ausruestung;
|
||||
203
frontend/src/pages/Dashboard.tsx
Normal file
203
frontend/src/pages/Dashboard.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Box,
|
||||
Typography,
|
||||
Grid,
|
||||
Fade,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
People,
|
||||
Warning,
|
||||
EventNote,
|
||||
LocalFireDepartment,
|
||||
} from '@mui/icons-material';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import SkeletonCard from '../components/shared/SkeletonCard';
|
||||
import UserProfile from '../components/dashboard/UserProfile';
|
||||
import NextcloudCard from '../components/dashboard/NextcloudCard';
|
||||
import VikunjaCard from '../components/dashboard/VikunjaCard';
|
||||
import BookstackCard from '../components/dashboard/BookstackCard';
|
||||
import StatsCard from '../components/dashboard/StatsCard';
|
||||
import ActivityFeed from '../components/dashboard/ActivityFeed';
|
||||
|
||||
function Dashboard() {
|
||||
const { user } = useAuth();
|
||||
const [dataLoading, setDataLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate loading data
|
||||
const timer = setTimeout(() => {
|
||||
setDataLoading(false);
|
||||
}, 800);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Grid container spacing={3}>
|
||||
{/* Welcome Message */}
|
||||
<Grid item xs={12}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600}>
|
||||
<Box>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Willkommen zurück, {user?.given_name || user?.name.split(' ')[0]}!
|
||||
</Typography>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* User Profile Card */}
|
||||
{user && (
|
||||
<Grid item xs={12}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="detailed" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '100ms' }}>
|
||||
<Box>
|
||||
<UserProfile user={user} />
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Stats Cards Row */}
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '200ms' }}>
|
||||
<Box>
|
||||
<StatsCard
|
||||
title="Aktive Mitglieder"
|
||||
value="24"
|
||||
icon={People}
|
||||
color="primary.main"
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '250ms' }}>
|
||||
<Box>
|
||||
<StatsCard
|
||||
title="Einsätze (Jahr)"
|
||||
value="18"
|
||||
icon={Warning}
|
||||
color="error.main"
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '300ms' }}>
|
||||
<Box>
|
||||
<StatsCard
|
||||
title="Offene Aufgaben"
|
||||
value="7"
|
||||
icon={EventNote}
|
||||
color="warning.main"
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '350ms' }}>
|
||||
<Box>
|
||||
<StatsCard
|
||||
title="Fahrzeuge"
|
||||
value="5"
|
||||
icon={LocalFireDepartment}
|
||||
color="success.main"
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* Service Integration Cards */}
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
|
||||
Dienste und Integrationen
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '400ms' }}>
|
||||
<Box>
|
||||
<NextcloudCard
|
||||
onClick={() => console.log('Nextcloud clicked')}
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '450ms' }}>
|
||||
<Box>
|
||||
<VikunjaCard
|
||||
onClick={() => console.log('Vikunja clicked')}
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="basic" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '500ms' }}>
|
||||
<Box>
|
||||
<BookstackCard
|
||||
onClick={() => console.log('Bookstack clicked')}
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* Activity Feed */}
|
||||
<Grid item xs={12}>
|
||||
{dataLoading ? (
|
||||
<SkeletonCard variant="detailed" />
|
||||
) : (
|
||||
<Fade in={true} timeout={600} style={{ transitionDelay: '550ms' }}>
|
||||
<Box>
|
||||
<ActivityFeed />
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
69
frontend/src/pages/Einsaetze.tsx
Normal file
69
frontend/src/pages/Einsaetze.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import { LocalFireDepartment } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
|
||||
function Einsaetze() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Einsatzübersicht
|
||||
</Typography>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<LocalFireDepartment color="primary" sx={{ fontSize: 48, mr: 2 }} />
|
||||
<Box>
|
||||
<Typography variant="h6">Einsatzverwaltung</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Diese Funktion wird in Kürze verfügbar sein
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="body1" color="text.secondary" paragraph>
|
||||
Geplante Features:
|
||||
</Typography>
|
||||
<ul>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Einsatzliste mit Filteroptionen
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Einsatzberichte erstellen und verwalten
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Statistiken und Auswertungen
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Einsatzdokumentation
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Alarmstufen und Kategorien
|
||||
</Typography>
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Einsaetze;
|
||||
69
frontend/src/pages/Fahrzeuge.tsx
Normal file
69
frontend/src/pages/Fahrzeuge.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import { DirectionsCar } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
|
||||
function Fahrzeuge() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Fahrzeugverwaltung
|
||||
</Typography>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<DirectionsCar color="primary" sx={{ fontSize: 48, mr: 2 }} />
|
||||
<Box>
|
||||
<Typography variant="h6">Fahrzeuge</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Diese Funktion wird in Kürze verfügbar sein
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="body1" color="text.secondary" paragraph>
|
||||
Geplante Features:
|
||||
</Typography>
|
||||
<ul>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Fahrzeugliste mit Details
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Wartungspläne und -historie
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Tankbuch und Kilometerstände
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
TÜV/HU Erinnerungen
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Fahrzeugdokumentation
|
||||
</Typography>
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Fahrzeuge;
|
||||
122
frontend/src/pages/Login.tsx
Normal file
122
frontend/src/pages/Login.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Container,
|
||||
Box,
|
||||
Paper,
|
||||
Button,
|
||||
Typography,
|
||||
CircularProgress,
|
||||
Fade,
|
||||
} from '@mui/material';
|
||||
import { LocalFireDepartment, Login as LoginIcon } from '@mui/icons-material';
|
||||
import { authService } from '../services/auth';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
function Login() {
|
||||
const navigate = useNavigate();
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
// Redirect to dashboard if already authenticated
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
setIsRedirecting(true);
|
||||
navigate('/dashboard', { replace: true });
|
||||
}
|
||||
}, [isAuthenticated, navigate]);
|
||||
|
||||
const handleLogin = () => {
|
||||
try {
|
||||
const authUrl = authService.getAuthUrl();
|
||||
window.location.href = authUrl;
|
||||
} catch (error) {
|
||||
console.error('Failed to initiate login:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading || isRedirecting) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '100vh',
|
||||
bgcolor: 'background.default',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
<CircularProgress size={60} />
|
||||
<Typography variant="body1" sx={{ mt: 2 }} color="text.secondary">
|
||||
{isRedirecting ? 'Weiterleitung...' : 'Lade...'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 8,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
minHeight: '100vh',
|
||||
}}
|
||||
>
|
||||
<Fade in={true} timeout={800}>
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
padding: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<LocalFireDepartment sx={{ fontSize: 60, color: 'primary.main', mb: 2 }} />
|
||||
<Typography component="h1" variant="h5" gutterBottom>
|
||||
Feuerwehr Dashboard
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3, textAlign: 'center' }}>
|
||||
Bitte melden Sie sich mit Ihrem Authentik-Konto an
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={handleLogin}
|
||||
startIcon={<LoginIcon />}
|
||||
sx={{ mt: 2 }}
|
||||
aria-label="Mit Authentik anmelden"
|
||||
>
|
||||
Mit Authentik anmelden
|
||||
</Button>
|
||||
</Paper>
|
||||
</Fade>
|
||||
|
||||
<Box
|
||||
component="footer"
|
||||
sx={{
|
||||
mt: 'auto',
|
||||
py: 3,
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" color="text.secondary" align="center" display="block">
|
||||
Feuerwehr Dashboard v0.0.1
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" align="center" display="block">
|
||||
{new Date().getFullYear()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
69
frontend/src/pages/Mitglieder.tsx
Normal file
69
frontend/src/pages/Mitglieder.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import { People } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
|
||||
function Mitglieder() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Mitgliederverwaltung
|
||||
</Typography>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<People color="primary" sx={{ fontSize: 48, mr: 2 }} />
|
||||
<Box>
|
||||
<Typography variant="h6">Mitglieder</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Diese Funktion wird in Kürze verfügbar sein
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="body1" color="text.secondary" paragraph>
|
||||
Geplante Features:
|
||||
</Typography>
|
||||
<ul>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Mitgliederliste mit Kontaktdaten
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Qualifikationen und Lehrgänge
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Anwesenheitsverwaltung
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Dienstpläne und -einteilungen
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Atemschutz-G26 Untersuchungen
|
||||
</Typography>
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Mitglieder;
|
||||
50
frontend/src/pages/NotFound.tsx
Normal file
50
frontend/src/pages/NotFound.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Container, Box, Typography, Button, Paper } from '@mui/material';
|
||||
import { Home } from '@mui/icons-material';
|
||||
|
||||
function NotFound() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="sm">
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 8,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
padding: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h1" component="h1" color="error" gutterBottom>
|
||||
404
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2" gutterBottom>
|
||||
Seite nicht gefunden
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Die angeforderte Seite existiert nicht.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<Home />}
|
||||
onClick={() => navigate('/dashboard')}
|
||||
>
|
||||
Zurück zum Dashboard
|
||||
</Button>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
262
frontend/src/pages/Profile.tsx
Normal file
262
frontend/src/pages/Profile.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
import {
|
||||
Container,
|
||||
Paper,
|
||||
Box,
|
||||
Typography,
|
||||
Avatar,
|
||||
Grid,
|
||||
TextField,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import { Person, Email, Badge, Group, AccessTime } from '@mui/icons-material';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
|
||||
function Profile() {
|
||||
const { user } = useAuth();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get initials for large avatar
|
||||
const getInitials = () => {
|
||||
const initials = (user.given_name?.[0] || '') + (user.family_name?.[0] || '');
|
||||
return initials || user.name?.[0] || '?';
|
||||
};
|
||||
|
||||
// Format date (if we had lastLogin)
|
||||
const formatDate = (date?: Date | string) => {
|
||||
if (!date) return 'Nicht verfügbar';
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Mein Profil
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{/* User Info Card */}
|
||||
<Grid item xs={12} md={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
py: 2,
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
bgcolor: 'primary.main',
|
||||
width: 120,
|
||||
height: 120,
|
||||
fontSize: '3rem',
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
{getInitials()}
|
||||
</Avatar>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{user.name}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
{user.email}
|
||||
</Typography>
|
||||
{user.preferred_username && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}
|
||||
>
|
||||
<Badge fontSize="small" />
|
||||
@{user.preferred_username}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
{/* Groups/Roles */}
|
||||
{user.groups && user.groups.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
color="text.secondary"
|
||||
gutterBottom
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}
|
||||
>
|
||||
<Group fontSize="small" />
|
||||
Gruppen
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 1 }}>
|
||||
{user.groups.map((group) => (
|
||||
<Chip key={group} label={group} size="small" color="primary" />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Personal Information */}
|
||||
<Grid item xs={12} md={8}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
|
||||
Persönliche Informationen
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Vorname"
|
||||
value={user.given_name || ''}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
startAdornment: (
|
||||
<Person sx={{ mr: 1, color: 'text.secondary' }} />
|
||||
),
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Nachname"
|
||||
value={user.family_name || ''}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
startAdornment: (
|
||||
<Person sx={{ mr: 1, color: 'text.secondary' }} />
|
||||
),
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="E-Mail-Adresse"
|
||||
value={user.email}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
startAdornment: (
|
||||
<Email sx={{ mr: 1, color: 'text.secondary' }} />
|
||||
),
|
||||
}}
|
||||
variant="outlined"
|
||||
helperText="E-Mail-Adresse wird von Authentik verwaltet"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{user.preferred_username && (
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Benutzername"
|
||||
value={user.preferred_username}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
startAdornment: (
|
||||
<Badge sx={{ mr: 1, color: 'text.secondary' }} />
|
||||
),
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mt: 3,
|
||||
p: 2,
|
||||
backgroundColor: 'info.lighter',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" color="info.dark">
|
||||
Diese Informationen werden von Authentik verwaltet und können hier nicht
|
||||
bearbeitet werden. Bitte wenden Sie sich an Ihren Administrator, um
|
||||
Änderungen vorzunehmen.
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Activity Information */}
|
||||
<Card sx={{ mt: 3 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
|
||||
Aktivitätsinformationen
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
p: 2,
|
||||
backgroundColor: 'background.default',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<AccessTime color="primary" />
|
||||
<Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Letzte Anmeldung
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
{formatDate(new Date())}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* User Preferences */}
|
||||
<Card sx={{ mt: 3 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
|
||||
Benutzereinstellungen
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Kommende Features: Benachrichtigungseinstellungen, Anzeigeoptionen,
|
||||
Spracheinstellungen
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Profile;
|
||||
209
frontend/src/pages/Settings.tsx
Normal file
209
frontend/src/pages/Settings.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Grid,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Divider,
|
||||
Box,
|
||||
Button,
|
||||
} from '@mui/material';
|
||||
import { Settings as SettingsIcon, Notifications, Palette, Language, Save } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
|
||||
function Settings() {
|
||||
const notification = useNotification();
|
||||
|
||||
// Settings state
|
||||
const [emailNotifications, setEmailNotifications] = useState(true);
|
||||
const [alarmNotifications, setAlarmNotifications] = useState(true);
|
||||
const [maintenanceReminders, setMaintenanceReminders] = useState(false);
|
||||
const [systemNotifications, setSystemNotifications] = useState(true);
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const [compactView, setCompactView] = useState(true);
|
||||
const [animations, setAnimations] = useState(true);
|
||||
|
||||
const handleSaveSettings = () => {
|
||||
try {
|
||||
// In a real application, save settings to backend
|
||||
notification.showSuccess('Einstellungen erfolgreich gespeichert');
|
||||
} catch (error) {
|
||||
notification.showError('Fehler beim Speichern der Einstellungen');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Einstellungen
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{/* Notification Settings */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Notifications color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">Benachrichtigungen</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={emailNotifications}
|
||||
onChange={(e) => setEmailNotifications(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="E-Mail-Benachrichtigungen"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={alarmNotifications}
|
||||
onChange={(e) => setAlarmNotifications(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Einsatz-Alarme"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={maintenanceReminders}
|
||||
onChange={(e) => setMaintenanceReminders(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Wartungserinnerungen"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={systemNotifications}
|
||||
onChange={(e) => setSystemNotifications(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="System-Benachrichtigungen"
|
||||
/>
|
||||
</FormGroup>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Display Settings */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Palette color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">Anzeigeoptionen</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={darkMode}
|
||||
onChange={(e) => {
|
||||
setDarkMode(e.target.checked);
|
||||
notification.showInfo('Dunkler Modus wird in einer zukünftigen Version verfügbar sein');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Dunkler Modus (Vorschau)"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={compactView}
|
||||
onChange={(e) => setCompactView(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Kompakte Ansicht"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={animations}
|
||||
onChange={(e) => setAnimations(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Animationen"
|
||||
/>
|
||||
</FormGroup>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Language Settings */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Language color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">Sprache</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Aktuelle Sprache: Deutsch
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
|
||||
Kommende Features: Sprachauswahl, Datumsformat, Zeitzone
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* General Settings */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<SettingsIcon color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">Allgemein</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Kommende Features: Dashboard-Layout, Standardansichten, Exporteinstellungen
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mt: 3,
|
||||
p: 2,
|
||||
backgroundColor: 'info.lighter',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" color="info.dark">
|
||||
Diese Einstellungen sind derzeit nur zur Demonstration verfügbar. Die Funktionalität
|
||||
wird in zukünftigen Updates implementiert.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
startIcon={<Save />}
|
||||
onClick={handleSaveSettings}
|
||||
>
|
||||
Einstellungen speichern
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
Reference in New Issue
Block a user