bug fixes

This commit is contained in:
Matthias Hochmeister
2026-03-03 13:27:27 +01:00
parent 02cf5138cf
commit 004b141cab
4 changed files with 42 additions and 233 deletions

View File

@@ -1,174 +0,0 @@
import React from 'react';
import {
Card,
CardContent,
Typography,
List,
ListItem,
ListItemAvatar,
ListItemText,
Avatar,
Box,
} from '@mui/material';
import {
LocalFireDepartment,
Person,
DirectionsCar,
Assignment,
} from '@mui/icons-material';
interface Activity {
id: string;
type: 'incident' | 'member' | 'vehicle' | 'task';
title: string;
description: string;
timestamp: string;
}
// Placeholder activities
const placeholderActivities: Activity[] = [
{
id: '1',
type: 'incident',
title: 'Brandeinsatz',
description: 'Kleinbrand in der Hauptstraße',
timestamp: 'Vor 2 Stunden',
},
{
id: '2',
type: 'member',
title: 'Neues Mitglied',
description: 'Max Mustermann ist der Feuerwehr beigetreten',
timestamp: 'Vor 5 Stunden',
},
{
id: '3',
type: 'vehicle',
title: 'Fahrzeugwartung',
description: 'LF 16/12 - Wartung abgeschlossen',
timestamp: 'Gestern',
},
{
id: '4',
type: 'task',
title: 'Aufgabe zugewiesen',
description: 'Neue Aufgabe: Inventur Atemschutzgeräte',
timestamp: 'Vor 2 Tagen',
},
];
const ActivityFeed: React.FC = () => {
const getActivityIcon = (type: Activity['type']) => {
switch (type) {
case 'incident':
return <LocalFireDepartment />;
case 'member':
return <Person />;
case 'vehicle':
return <DirectionsCar />;
case 'task':
return <Assignment />;
default:
return <LocalFireDepartment />;
}
};
const getActivityColor = (type: Activity['type']) => {
switch (type) {
case 'incident':
return 'error.main';
case 'member':
return 'success.main';
case 'vehicle':
return 'warning.main';
case 'task':
return 'info.main';
default:
return 'primary.main';
}
};
return (
<Card sx={{ height: '100%', overflow: 'hidden' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Letzte Aktivitäten
</Typography>
<List sx={{ pt: 2 }}>
{placeholderActivities.map((activity, index) => (
<React.Fragment key={activity.id}>
<ListItem
alignItems="flex-start"
sx={{
px: 0,
position: 'relative',
'&::before':
index < placeholderActivities.length - 1
? {
content: '""',
position: 'absolute',
left: 19,
top: 56,
bottom: 0,
width: 2,
bgcolor: 'divider',
}
: {},
}}
>
<ListItemAvatar>
<Avatar
sx={{
bgcolor: getActivityColor(activity.type),
width: 40,
height: 40,
}}
>
{getActivityIcon(activity.type)}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={
<Typography variant="subtitle2" component="span">
{activity.title}
</Typography>
}
secondary={
<Box>
<Typography
variant="body2"
color="text.secondary"
component="span"
sx={{ display: 'block' }}
>
{activity.description}
</Typography>
<Typography
variant="caption"
color="text.secondary"
component="span"
>
{activity.timestamp}
</Typography>
</Box>
}
/>
</ListItem>
</React.Fragment>
))}
</List>
{placeholderActivities.length === 0 && (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="body2" color="text.secondary">
Keine Aktivitäten vorhanden
</Typography>
</Box>
)}
</CardContent>
</Card>
);
};
export default ActivityFeed;

View File

@@ -61,6 +61,9 @@ const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
{/* User Info */} {/* User Info */}
<Box sx={{ flex: 1, textAlign: { xs: 'center', sm: 'left' } }}> <Box sx={{ flex: 1, textAlign: { xs: 'center', sm: 'left' } }}>
<Typography variant="h5" component="div" gutterBottom> <Typography variant="h5" component="div" gutterBottom>
Willkommen zurück, {user.given_name || user.name.split(' ')[0]}!
</Typography>
<Typography variant="body2" sx={{ opacity: 0.75, mb: 0.5 }}>
{user.name} {user.name}
</Typography> </Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}> <Typography variant="body2" sx={{ opacity: 0.9 }}>

View File

@@ -1,7 +1,6 @@
export { default as UserProfile } from './UserProfile'; export { default as UserProfile } from './UserProfile';
export { default as ServiceCard } from './ServiceCard'; export { default as ServiceCard } from './ServiceCard';
export { default as StatsCard } from './StatsCard'; export { default as StatsCard } from './StatsCard';
export { default as ActivityFeed } from './ActivityFeed';
export { default as DashboardLayout } from './DashboardLayout'; export { default as DashboardLayout } from './DashboardLayout';
export { default as PersonalWarningsBanner } from './PersonalWarningsBanner'; export { default as PersonalWarningsBanner } from './PersonalWarningsBanner';
export { default as UpcomingEventsWidget } from './UpcomingEventsWidget'; export { default as UpcomingEventsWidget } from './UpcomingEventsWidget';

View File

@@ -2,8 +2,6 @@ import { useState, useEffect } from 'react';
import { import {
Container, Container,
Box, Box,
Typography,
Grid,
Fade, Fade,
} from '@mui/material'; } from '@mui/material';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
@@ -12,7 +10,6 @@ import SkeletonCard from '../components/shared/SkeletonCard';
import UserProfile from '../components/dashboard/UserProfile'; import UserProfile from '../components/dashboard/UserProfile';
import NextcloudTalkWidget from '../components/dashboard/NextcloudTalkWidget'; import NextcloudTalkWidget from '../components/dashboard/NextcloudTalkWidget';
import UpcomingEventsWidget from '../components/dashboard/UpcomingEventsWidget'; import UpcomingEventsWidget from '../components/dashboard/UpcomingEventsWidget';
import ActivityFeed from '../components/dashboard/ActivityFeed';
import AtemschutzDashboardCard from '../components/atemschutz/AtemschutzDashboardCard'; import AtemschutzDashboardCard from '../components/atemschutz/AtemschutzDashboardCard';
import EquipmentDashboardCard from '../components/equipment/EquipmentDashboardCard'; import EquipmentDashboardCard from '../components/equipment/EquipmentDashboardCard';
import VehicleDashboardCard from '../components/vehicles/VehicleDashboardCard'; import VehicleDashboardCard from '../components/vehicles/VehicleDashboardCard';
@@ -33,25 +30,22 @@ function Dashboard() {
return ( return (
<DashboardLayout> <DashboardLayout>
<Container maxWidth={false} disableGutters> <Container maxWidth={false} disableGutters>
<Grid container spacing={3}> <Box
{/* Welcome Message */} sx={{
<Grid item xs={12}> display: 'grid',
{dataLoading ? ( gridTemplateColumns: {
<SkeletonCard variant="basic" /> xs: '1fr',
) : ( sm: 'repeat(2, 1fr)',
<Fade in={true} timeout={600}> lg: 'repeat(3, 1fr)',
<Box> xl: 'repeat(4, 1fr)',
<Typography variant="h4" gutterBottom> },
Willkommen zurück, {user?.given_name || user?.name.split(' ')[0]}! gap: 2.5,
</Typography> alignItems: 'start',
</Box> }}
</Fade> >
)} {/* User Profile Card — full width, contains welcome greeting */}
</Grid>
{/* User Profile Card */}
{user && ( {user && (
<Grid item xs={12}> <Box sx={{ gridColumn: '1 / -1' }}>
{dataLoading ? ( {dataLoading ? (
<SkeletonCard variant="detailed" /> <SkeletonCard variant="detailed" />
) : ( ) : (
@@ -61,82 +55,69 @@ function Dashboard() {
</Box> </Box>
</Fade> </Fade>
)} )}
</Grid> </Box>
)} )}
{/* Personal Atemschutz Warnings — shown only when relevant */} {/* Personal Warnings Banner — full width, conditionally rendered */}
{user && ( {user && (
<Grid item xs={12}> <Box sx={{ gridColumn: '1 / -1' }}>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '150ms' }}> <Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '150ms' }}>
<Box> <Box>
<PersonalWarningsBanner user={user} /> <PersonalWarningsBanner user={user} />
</Box> </Box>
</Fade> </Fade>
</Grid> </Box>
)} )}
{/* Vehicle Status Card */} {/* Vehicle Status Card */}
<Grid item xs={12} md={6} sx={{ display: 'flex' }}> <Box>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '380ms' }}> <Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '300ms' }}>
<Box sx={{ height: '100%' }}> <Box>
<VehicleDashboardCard /> <VehicleDashboardCard />
</Box> </Box>
</Fade> </Fade>
</Grid> </Box>
{/* Equipment Status Card */} {/* Equipment Status Card */}
<Grid item xs={12} md={6} sx={{ display: 'flex' }}> <Box>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '450ms' }}> <Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '350ms' }}>
<Box sx={{ height: '100%' }}> <Box>
<EquipmentDashboardCard /> <EquipmentDashboardCard />
</Box> </Box>
</Fade> </Fade>
</Grid> </Box>
{/* Atemschutz Status Card */} {/* Atemschutz Status Card */}
<Grid item xs={12} md={6} sx={{ display: 'flex' }}> <Box>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '420ms' }}> <Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '400ms' }}>
<Box sx={{ height: '100%' }}> <Box>
<AtemschutzDashboardCard /> <AtemschutzDashboardCard />
</Box> </Box>
</Fade> </Fade>
</Grid> </Box>
{/* Upcoming Events Widget */} {/* Upcoming Events Widget */}
<Grid item xs={12} md={6} sx={{ display: 'flex' }}> <Box>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '440ms' }}> <Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '440ms' }}>
<Box sx={{ height: '100%' }}> <Box>
<UpcomingEventsWidget /> <UpcomingEventsWidget />
</Box> </Box>
</Fade> </Fade>
</Grid> </Box>
{/* Nextcloud Talk Widget */} {/* Nextcloud Talk Widget */}
<Grid item xs={12} md={6} lg={5} sx={{ display: 'flex' }}> <Box>
{dataLoading ? ( {dataLoading ? (
<SkeletonCard variant="basic" /> <SkeletonCard variant="basic" />
) : ( ) : (
<Fade in={true} timeout={600} style={{ transitionDelay: '520ms' }}> <Fade in={true} timeout={600} style={{ transitionDelay: '480ms' }}>
<Box sx={{ height: '100%' }}> <Box>
<NextcloudTalkWidget /> <NextcloudTalkWidget />
</Box> </Box>
</Fade> </Fade>
)} )}
</Grid> </Box>
</Box>
{/* Activity Feed */}
<Grid item xs={12} md={6} lg={7} sx={{ display: 'flex' }}>
{dataLoading ? (
<SkeletonCard variant="detailed" />
) : (
<Fade in={true} timeout={600} style={{ transitionDelay: '550ms' }}>
<Box sx={{ height: '100%' }}>
<ActivityFeed />
</Box>
</Fade>
)}
</Grid>
</Grid>
</Container> </Container>
</DashboardLayout> </DashboardLayout>
); );