fix: missing avatar URLs in notifications, admin panel, and budget

- Notifications: map raw avatar filename to /uploads/avatars/ URL in
  getNotifications, createNotification broadcasts, and respond handler
- Admin listUsers: include avatar field in SELECT and map to avatar_url
- Admin page: render actual avatar image instead of initial letter only
- Budget loadItemMembers: map avatar to avatar_url (fixed in prior commit)

Fixes #507
This commit is contained in:
Maurice
2026-04-08 18:17:08 +02:00
parent 9dc91b08a9
commit 2d17ec60db
3 changed files with 26 additions and 10 deletions
+3 -2
View File
@@ -40,8 +40,8 @@ export const isDocker = (() => {
export function listUsers() {
const users = db.prepare(
'SELECT id, username, email, role, created_at, updated_at, last_login FROM users ORDER BY created_at DESC'
).all() as Pick<User, 'id' | 'username' | 'email' | 'role' | 'created_at' | 'updated_at' | 'last_login'>[];
'SELECT id, username, email, role, avatar, created_at, updated_at, last_login FROM users ORDER BY created_at DESC'
).all() as (Pick<User, 'id' | 'username' | 'email' | 'role' | 'created_at' | 'updated_at' | 'last_login'> & { avatar?: string | null })[];
let onlineUserIds = new Set<number>();
try {
const { getOnlineUserIds } = require('../websocket');
@@ -49,6 +49,7 @@ export function listUsers() {
} catch { /* */ }
return users.map(u => ({
...u,
avatar_url: u.avatar ? `/uploads/avatars/${u.avatar}` : null,
created_at: utcSuffix(u.created_at),
updated_at: utcSuffix(u.updated_at as string),
last_login: utcSuffix(u.last_login),
+15 -5
View File
@@ -159,7 +159,7 @@ function createNotification(input: NotificationInput): number[] {
notification: {
...row,
sender_username: sender?.username ?? null,
sender_avatar: sender?.avatar ?? null,
sender_avatar: sender?.avatar ? `/uploads/avatars/${sender.avatar}` : null,
},
});
}
@@ -219,7 +219,7 @@ export function createNotificationForRecipient(
notification: {
...row,
sender_username: sender?.username ?? null,
sender_avatar: sender?.avatar ?? null,
sender_avatar: sender?.avatar ? `/uploads/avatars/${sender.avatar}` : null,
},
});
@@ -249,7 +249,12 @@ function getNotifications(
const { total } = db.prepare(`SELECT COUNT(*) as total FROM notifications ${wherePlain}`).get(userId) as { total: number };
const { unread_count } = db.prepare('SELECT COUNT(*) as unread_count FROM notifications WHERE recipient_id = ? AND is_read = 0').get(userId) as { unread_count: number };
return { notifications: rows, total, unread_count };
const mapped = rows.map(r => ({
...r,
sender_avatar: r.sender_avatar ? `/uploads/avatars/${r.sender_avatar}` : null,
}));
return { notifications: mapped, total, unread_count };
}
function getUnreadCount(userId: number): number {
@@ -326,9 +331,14 @@ async function respondToBoolean(
WHERE n.id = ?
`).get(notificationId) as NotificationRow;
broadcastToUser(userId, { type: 'notification:updated', notification: updated });
const mappedUpdated = {
...updated,
sender_avatar: updated.sender_avatar ? `/uploads/avatars/${updated.sender_avatar}` : null,
};
return { success: true, notification: updated };
broadcastToUser(userId, { type: 'notification:updated', notification: mappedUpdated });
return { success: true, notification: mappedUpdated };
}
export {