feat(profil): eigenes Profilbild hochladen
Jeder setzt sein Bild unter "Mein Konto", Vorgesetzte und Administratoren auch fuer die Benutzer, die sie verwalten. Wo bisher die Initialen standen, steht jetzt das Bild - Seitenleiste, Kopfzeile, Team, Dashboard, Reports, Projekte, Kunden und die Buchungsliste. Ohne Bild bleiben die Initialen. Der Browser verkleinert das Bild vor dem Hochladen auf ein Quadrat von 256 px und schneidet mittig zu (lib/imageCompress.ts): aus einem 4-MB-Foto werden wenige Kilobyte, und der Server muss kein Bild verarbeiten. Gespeichert wird es als Data-URL in pulse_users.avatar (LONGTEXT). Kein Upload-Verzeichnis, das gesichert und beim Deployment am Leben gehalten werden muesste. Geprueft wird serverseitig in lib/avatar.js: nur PNG, JPEG, GIF, WebP und AVIF als Data-URL, hoechstens 2 MB. SVG ist bewusst nicht erlaubt, es kann Skripte tragen. Externe http(s)-Adressen ebenso wenig - sonst laedt jeder Seitenaufruf ein Bild von einem fremden Server und verraet, wer wann mit Pulse arbeitet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2d3d843988
commit
9576d18abc
@@ -50,7 +50,7 @@ export async function buildTimeReport(viewer, query) {
|
||||
|
||||
const filter = userFilter(userIds, 'id');
|
||||
const [users] = await pool.query(
|
||||
`SELECT id, name, color, weekly_hours, is_external, company, is_active FROM pulse_users WHERE role <> 'admin' AND ${filter.sql} ORDER BY name`,
|
||||
`SELECT id, name, color, avatar, weekly_hours, is_external, company, is_active FROM pulse_users WHERE role <> 'admin' AND ${filter.sql} ORDER BY name`,
|
||||
filter.params
|
||||
);
|
||||
const capTo = to > today() ? today() : to;
|
||||
@@ -60,12 +60,12 @@ export async function buildTimeReport(viewer, query) {
|
||||
const amount = entries.reduce((s, e) => s + e.amount, 0);
|
||||
const fixedAmount = entries.reduce((s, e) => s + e.fixed_amount, 0);
|
||||
|
||||
const userRows = group(entries, (e) => e.user_id, (e) => ({ user_id: e.user_id, name: e.user_name, color: e.user_color, is_external: e.is_external }), includeFixed);
|
||||
const userRows = group(entries, (e) => e.user_id, (e) => ({ user_id: e.user_id, name: e.user_name, color: e.user_color, avatar: e.user_avatar ?? null, is_external: e.is_external }), includeFixed);
|
||||
// Mitarbeiter ohne Buchung trotzdem zeigen (nur aktive, nicht bei Kunden-/Projektfilter).
|
||||
if (!query.customer_id && !query.project_id) {
|
||||
for (const u of users) {
|
||||
if (u.is_active && !userRows.some((row) => row.user_id === u.id)) {
|
||||
userRows.push({ key: u.id, user_id: u.id, name: u.name, color: u.color, is_external: Boolean(u.is_external), minutes: 0, billable_minutes: 0, non_billable_minutes: 0, amount: 0 });
|
||||
userRows.push({ key: u.id, user_id: u.id, name: u.name, color: u.color, avatar: u.avatar ?? null, is_external: Boolean(u.is_external), minutes: 0, billable_minutes: 0, non_billable_minutes: 0, amount: 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user