mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
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:
@@ -30,6 +30,7 @@ interface AdminUser {
|
||||
last_login?: string | null
|
||||
online?: boolean
|
||||
oidc_issuer?: string | null
|
||||
avatar_url?: string | null
|
||||
}
|
||||
|
||||
interface AdminStats {
|
||||
@@ -605,9 +606,13 @@ export default function AdminPage(): React.ReactElement {
|
||||
<td className="px-5 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative">
|
||||
<div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center text-sm font-medium text-slate-700">
|
||||
{u.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
{u.avatar_url ? (
|
||||
<img src={u.avatar_url} alt={u.username} className="w-8 h-8 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center text-sm font-medium text-slate-700">
|
||||
{u.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
<span className="absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2" style={{ borderColor: 'var(--bg-card)', background: u.online ? '#22c55e' : '#94a3b8' }} />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user