feat: bug fixes, layout improvements, and new features

Bug fixes:
- Remove non-existent `role` column from admin users SQL query (A1)
- Fix Nextcloud Talk chat API path v4 → v1 for messages/send/read (A2)
- Fix ServiceModeTab sync: useState → useEffect to reflect DB state (A3)
- Guard BookStack book_slug with book_id fallback to avoid broken URLs (A4)

Layout & UI:
- Chat panel: sticky full-height positioning, main content scrolls independently (B1)
- Vehicle booking datetime inputs: explicit text color for dark mode (B2)
- AnnouncementBanner moved into grid with full-width span (B3)

Features:
- Per-user widget visibility preferences stored in users.preferences JSONB (C1)
- Link collections: grouped external links in admin UI and dashboard widget (C2)
- Admin ping history: migration 026, checked_at timestamps, expandable history rows (C4)
- Service mode end date picker with scheduled deactivation display (C5)
- Vikunja startup config logging and configured:false warnings (C7)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-03-12 14:57:54 +01:00
parent 81174c2498
commit a5cd78f01f
29 changed files with 593 additions and 105 deletions

View File

@@ -11,13 +11,41 @@ import {
Box,
ToggleButtonGroup,
ToggleButton,
CircularProgress,
} from '@mui/material';
import { Settings as SettingsIcon, Notifications, Palette, Language, SettingsBrightness, LightMode, DarkMode } from '@mui/icons-material';
import { Settings as SettingsIcon, Notifications, Palette, Language, SettingsBrightness, LightMode, DarkMode, Widgets } from '@mui/icons-material';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import { useThemeMode } from '../contexts/ThemeContext';
import { preferencesApi } from '../services/settings';
import { WIDGETS, WidgetKey } from '../constants/widgets';
function Settings() {
const { themeMode, setThemeMode } = useThemeMode();
const queryClient = useQueryClient();
const { data: preferences, isLoading: prefsLoading } = useQuery({
queryKey: ['user-preferences'],
queryFn: preferencesApi.get,
});
const mutation = useMutation({
mutationFn: preferencesApi.update,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['user-preferences'] });
},
});
const isWidgetVisible = (key: WidgetKey) => {
return preferences?.widgets?.[key] !== false;
};
const toggleWidget = (key: WidgetKey) => {
const current = preferences ?? {};
const widgets = { ...(current.widgets ?? {}) };
widgets[key] = !isWidgetVisible(key);
mutation.mutate({ ...current, widgets });
};
return (
<DashboardLayout>
@@ -27,6 +55,40 @@ function Settings() {
</Typography>
<Grid container spacing={3}>
{/* Widget Visibility */}
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Widgets color="primary" sx={{ mr: 2 }} />
<Typography variant="h6">Dashboard-Widgets</Typography>
</Box>
<Divider sx={{ mb: 2 }} />
{prefsLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
<CircularProgress size={24} />
</Box>
) : (
<FormGroup>
{WIDGETS.map((w) => (
<FormControlLabel
key={w.key}
control={
<Switch
checked={isWidgetVisible(w.key)}
onChange={() => toggleWidget(w.key)}
disabled={mutation.isPending}
/>
}
label={w.label}
/>
))}
</FormGroup>
)}
</CardContent>
</Card>
</Grid>
{/* Notification Settings */}
<Grid item xs={12} md={6}>
<Card>
@@ -146,20 +208,6 @@ function Settings() {
</Card>
</Grid>
</Grid>
<Box
sx={{
mt: 3,
p: 2,
backgroundColor: 'info.light',
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>
</Container>
</DashboardLayout>
);