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

@@ -1,6 +1,7 @@
import { Request, Response } from 'express';
import { z } from 'zod';
import settingsService from '../services/settings.service';
import pool from '../config/database';
import logger from '../utils/logger';
const updateSchema = z.object({
@@ -8,8 +9,12 @@ const updateSchema = z.object({
});
const externalLinkSchema = z.array(z.object({
id: z.string().min(1),
name: z.string().min(1).max(200),
url: z.string().url().max(500),
links: z.array(z.object({
name: z.string().min(1).max(200),
url: z.string().url().max(500),
})),
}));
class SettingsController {
@@ -57,6 +62,32 @@ class SettingsController {
res.status(500).json({ success: false, message: 'Failed to update setting' });
}
}
async getUserPreferences(req: Request, res: Response): Promise<void> {
try {
const userId = (req as any).user.id;
const result = await pool.query('SELECT preferences FROM users WHERE id = $1', [userId]);
const prefs = result.rows[0]?.preferences ?? {};
res.json({ success: true, data: prefs });
} catch (error) {
logger.error('Failed to get user preferences', { error });
res.status(500).json({ success: false, message: 'Failed to get user preferences' });
}
}
async updateUserPreferences(req: Request, res: Response): Promise<void> {
try {
const userId = (req as any).user.id;
const preferences = req.body;
await pool.query(
'UPDATE users SET preferences = $1 WHERE id = $2',
[JSON.stringify(preferences), userId]
);
res.json({ success: true });
} catch (error) {
logger.error('Failed to update user preferences', { error });
res.status(500).json({ success: false, message: 'Failed to update user preferences' });
}
}
}
export default new SettingsController();