resolve issues with new features
This commit is contained in:
47
frontend/src/components/shared/ChatAwareFab.tsx
Normal file
47
frontend/src/components/shared/ChatAwareFab.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Fab } from '@mui/material';
|
||||||
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
|
import { useLayout } from '../../contexts/LayoutContext';
|
||||||
|
|
||||||
|
interface ChatAwareFabProps {
|
||||||
|
onClick: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
color?: 'primary' | 'secondary' | 'default' | 'error' | 'info' | 'success' | 'warning';
|
||||||
|
size?: 'small' | 'medium' | 'large';
|
||||||
|
'aria-label'?: string;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Fab that automatically shifts right to avoid overlapping the chat panel.
|
||||||
|
* Must be rendered inside DashboardLayout (i.e. inside LayoutProvider).
|
||||||
|
*/
|
||||||
|
const ChatAwareFab = React.forwardRef<HTMLButtonElement, ChatAwareFabProps>(
|
||||||
|
({ onClick, children, color = 'primary', size, 'aria-label': ariaLabel, sx }, ref) => {
|
||||||
|
const { chatPanelOpen } = useLayout();
|
||||||
|
return (
|
||||||
|
<Fab
|
||||||
|
ref={ref}
|
||||||
|
color={color}
|
||||||
|
size={size}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
onClick={onClick}
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 32,
|
||||||
|
right: chatPanelOpen ? 376 : 80,
|
||||||
|
transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)',
|
||||||
|
},
|
||||||
|
...(Array.isArray(sx) ? sx : sx ? [sx] : []),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Fab>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ChatAwareFab.displayName = 'ChatAwareFab';
|
||||||
|
|
||||||
|
export default ChatAwareFab;
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Divider,
|
Divider,
|
||||||
Fab,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -44,7 +43,7 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom';
|
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import { useLayout } from '../contexts/LayoutContext';
|
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||||
import { equipmentApi } from '../services/equipment';
|
import { equipmentApi } from '../services/equipment';
|
||||||
import { fromGermanDate } from '../utils/dateInput';
|
import { fromGermanDate } from '../utils/dateInput';
|
||||||
import {
|
import {
|
||||||
@@ -352,7 +351,6 @@ interface WartungTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WartungTab: React.FC<WartungTabProps> = ({ equipmentId, wartungslog, onAdded, canWrite }) => {
|
const WartungTab: React.FC<WartungTabProps> = ({ equipmentId, wartungslog, onAdded, canWrite }) => {
|
||||||
const { chatPanelOpen } = useLayout();
|
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [saveError, setSaveError] = useState<string | null>(null);
|
const [saveError, setSaveError] = useState<string | null>(null);
|
||||||
@@ -444,15 +442,13 @@ const WartungTab: React.FC<WartungTabProps> = ({ equipmentId, wartungslog, onAdd
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{canWrite && (
|
{canWrite && (
|
||||||
<Fab
|
<ChatAwareFab
|
||||||
color="primary"
|
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Wartung eintragen"
|
aria-label="Wartung eintragen"
|
||||||
sx={{ position: 'fixed', bottom: 32, right: chatPanelOpen ? 376 : 80, transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)' }}
|
|
||||||
onClick={() => { setForm(emptyForm); setSaveError(null); setDialogOpen(true); }}
|
onClick={() => { setForm(emptyForm); setSaveError(null); setDialogOpen(true); }}
|
||||||
>
|
>
|
||||||
<Add />
|
<Add />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
|
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Divider,
|
Divider,
|
||||||
Fab,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -55,7 +54,7 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import { useLayout } from '../contexts/LayoutContext';
|
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||||
import { vehiclesApi } from '../services/vehicles';
|
import { vehiclesApi } from '../services/vehicles';
|
||||||
import { fromGermanDate } from '../utils/dateInput';
|
import { fromGermanDate } from '../utils/dateInput';
|
||||||
import { equipmentApi } from '../services/equipment';
|
import { equipmentApi } from '../services/equipment';
|
||||||
@@ -323,7 +322,6 @@ const WARTUNG_ART_ICONS: Record<string, React.ReactElement> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdded, canWrite }) => {
|
const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdded, canWrite }) => {
|
||||||
const { chatPanelOpen } = useLayout();
|
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [saveError, setSaveError] = useState<string | null>(null);
|
const [saveError, setSaveError] = useState<string | null>(null);
|
||||||
@@ -396,15 +394,13 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{canWrite && (
|
{canWrite && (
|
||||||
<Fab
|
<ChatAwareFab
|
||||||
color="primary"
|
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Wartung eintragen"
|
aria-label="Wartung eintragen"
|
||||||
sx={{ position: 'fixed', bottom: 32, right: chatPanelOpen ? 376 : 80, transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)' }}
|
|
||||||
onClick={() => { setForm(emptyForm); setDialogOpen(true); }}
|
onClick={() => { setForm(emptyForm); setDialogOpen(true); }}
|
||||||
>
|
>
|
||||||
<Add />
|
<Add />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
|
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Chip,
|
Chip,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Container,
|
Container,
|
||||||
Fab,
|
|
||||||
Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -29,7 +28,7 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import { useLayout } from '../contexts/LayoutContext';
|
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||||
import { vehiclesApi } from '../services/vehicles';
|
import { vehiclesApi } from '../services/vehicles';
|
||||||
import { equipmentApi } from '../services/equipment';
|
import { equipmentApi } from '../services/equipment';
|
||||||
import type { VehicleEquipmentWarning } from '../types/equipment.types';
|
import type { VehicleEquipmentWarning } from '../types/equipment.types';
|
||||||
@@ -272,7 +271,6 @@ const VehicleCard: React.FC<VehicleCardProps> = ({ vehicle, onClick, warnings =
|
|||||||
function Fahrzeuge() {
|
function Fahrzeuge() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isAdmin } = usePermissions();
|
const { isAdmin } = usePermissions();
|
||||||
const { chatPanelOpen } = useLayout();
|
|
||||||
const [vehicles, setVehicles] = useState<FahrzeugListItem[]>([]);
|
const [vehicles, setVehicles] = useState<FahrzeugListItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -414,14 +412,12 @@ function Fahrzeuge() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Fab
|
<ChatAwareFab
|
||||||
color="primary"
|
|
||||||
aria-label="Fahrzeug hinzufügen"
|
aria-label="Fahrzeug hinzufügen"
|
||||||
sx={{ position: 'fixed', bottom: 32, right: chatPanelOpen ? 376 : 80, transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)' }}
|
|
||||||
onClick={() => navigate('/fahrzeuge/neu')}
|
onClick={() => navigate('/fahrzeuge/neu')}
|
||||||
>
|
>
|
||||||
<Add />
|
<Add />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Divider,
|
Divider,
|
||||||
Fab,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -70,7 +69,7 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import { useLayout } from '../contexts/LayoutContext';
|
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||||
import { toGermanDate, toGermanDateTime, fromGermanDate, fromGermanDateTime, isValidGermanDate, isValidGermanDateTime } from '../utils/dateInput';
|
import { toGermanDate, toGermanDateTime, fromGermanDate, fromGermanDateTime, isValidGermanDate, isValidGermanDateTime } from '../utils/dateInput';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { useNotification } from '../contexts/NotificationContext';
|
import { useNotification } from '../contexts/NotificationContext';
|
||||||
@@ -1682,7 +1681,6 @@ export default function Kalender() {
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
const { chatPanelOpen } = useLayout();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
@@ -2554,16 +2552,14 @@ export default function Kalender() {
|
|||||||
|
|
||||||
{/* FAB: Create Veranstaltung */}
|
{/* FAB: Create Veranstaltung */}
|
||||||
{canWriteEvents && (
|
{canWriteEvents && (
|
||||||
<Fab
|
<ChatAwareFab
|
||||||
color="primary"
|
|
||||||
sx={{ position: 'fixed', bottom: 32, right: chatPanelOpen ? 376 : 80, transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)' }}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVeranstEditing(null);
|
setVeranstEditing(null);
|
||||||
setVeranstFormOpen(true);
|
setVeranstFormOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Add />
|
<Add />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Day Popover */}
|
{/* Day Popover */}
|
||||||
@@ -2927,13 +2923,9 @@ export default function Kalender() {
|
|||||||
|
|
||||||
{/* FAB */}
|
{/* FAB */}
|
||||||
{canCreateBookings && (
|
{canCreateBookings && (
|
||||||
<Fab
|
<ChatAwareFab onClick={openBookingCreate}>
|
||||||
color="primary"
|
|
||||||
sx={{ position: 'fixed', bottom: 32, right: chatPanelOpen ? 376 : 80, transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)' }}
|
|
||||||
onClick={openBookingCreate}
|
|
||||||
>
|
|
||||||
<Add />
|
<Add />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Booking detail popover */}
|
{/* Booking detail popover */}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
InputAdornment,
|
InputAdornment,
|
||||||
Chip,
|
Chip,
|
||||||
Avatar,
|
Avatar,
|
||||||
Fab,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Alert,
|
Alert,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
@@ -33,7 +32,7 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import { useLayout } from '../contexts/LayoutContext';
|
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { membersService } from '../services/members';
|
import { membersService } from '../services/members';
|
||||||
import {
|
import {
|
||||||
@@ -74,8 +73,7 @@ function useDebounce<T>(value: T, delay: number): T {
|
|||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
function Mitglieder() {
|
function Mitglieder() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth(); const canWrite = useCanWrite();
|
||||||
const { chatPanelOpen } = useLayout(); const canWrite = useCanWrite();
|
|
||||||
|
|
||||||
// --- redirect non-admin/non-kommando users to their own profile ---
|
// --- redirect non-admin/non-kommando users to their own profile ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -428,20 +426,13 @@ function Mitglieder() {
|
|||||||
{/* FAB — only visible to Kommandant/Admin */}
|
{/* FAB — only visible to Kommandant/Admin */}
|
||||||
{canWrite && (
|
{canWrite && (
|
||||||
<Tooltip title="Neues Mitglied anlegen">
|
<Tooltip title="Neues Mitglied anlegen">
|
||||||
<Fab
|
<ChatAwareFab
|
||||||
color="primary"
|
|
||||||
aria-label="Neues Mitglied anlegen"
|
aria-label="Neues Mitglied anlegen"
|
||||||
onClick={() => navigate('/mitglieder/neu')}
|
onClick={() => navigate('/mitglieder/neu')}
|
||||||
sx={{
|
sx={{ zIndex: (theme) => theme.zIndex.speedDial }}
|
||||||
position: 'fixed',
|
|
||||||
bottom: 32,
|
|
||||||
right: chatPanelOpen ? 376 : 80,
|
|
||||||
transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)',
|
|
||||||
zIndex: (theme) => theme.zIndex.speedDial,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Fab,
|
|
||||||
Stack,
|
Stack,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
@@ -51,7 +50,7 @@ import {
|
|||||||
Delete as DeleteIcon,
|
Delete as DeleteIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import { useLayout } from '../contexts/LayoutContext';
|
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||||
import { toGermanDate, toGermanDateTime, fromGermanDate, fromGermanDateTime } from '../utils/dateInput';
|
import { toGermanDate, toGermanDateTime, fromGermanDate, fromGermanDateTime } from '../utils/dateInput';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { useNotification } from '../contexts/NotificationContext';
|
import { useNotification } from '../contexts/NotificationContext';
|
||||||
@@ -1005,7 +1004,6 @@ function EventListView({ events, canWrite, onEdit, onCancel, onDelete }: ListVie
|
|||||||
|
|
||||||
export default function Veranstaltungen() {
|
export default function Veranstaltungen() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { chatPanelOpen } = useLayout();
|
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
@@ -1316,14 +1314,12 @@ export default function Veranstaltungen() {
|
|||||||
|
|
||||||
{/* FAB for creating events */}
|
{/* FAB for creating events */}
|
||||||
{canWrite && (
|
{canWrite && (
|
||||||
<Fab
|
<ChatAwareFab
|
||||||
color="primary"
|
|
||||||
aria-label="Veranstaltung erstellen"
|
aria-label="Veranstaltung erstellen"
|
||||||
sx={{ position: 'fixed', bottom: 32, right: chatPanelOpen ? 376 : 80, transition: 'right 225ms cubic-bezier(0.4, 0, 0.6, 1)' }}
|
|
||||||
onClick={() => { setEditingEvent(null); setFormOpen(true); }}
|
onClick={() => { setEditingEvent(null); setFormOpen(true); }}
|
||||||
>
|
>
|
||||||
<Add />
|
<Add />
|
||||||
</Fab>
|
</ChatAwareFab>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Day Popover */}
|
{/* Day Popover */}
|
||||||
|
|||||||
Reference in New Issue
Block a user