Dieser Commit ist enthalten in:
hendrik_gebhardt@gmx.de
2026-01-04 21:21:11 +00:00
committet von Server Deploy
Ursprung c21be47428
Commit 623bbdf5dd
19 geänderte Dateien mit 6290 neuen und 48 gelöschten Zeilen

Datei anzeigen

@ -48,7 +48,8 @@
"Bash(__NEW_LINE__ cp /app/data/taskmate.db.backup-20260103-201322 /app/data/taskmate.db)", "Bash(__NEW_LINE__ cp /app/data/taskmate.db.backup-20260103-201322 /app/data/taskmate.db)",
"Bash(docker system prune:*)", "Bash(docker system prune:*)",
"Bash(docker cp:*)", "Bash(docker cp:*)",
"Bash(mv:*)" "Bash(mv:*)",
"Bash(docker-compose up:*)"
] ]
} }
} }

Datei anzeigen

@ -1,6 +1,53 @@
TASKMATE - CHANGELOG TASKMATE - CHANGELOG
==================== ====================
================================================================================
04.01.2026 - KÜRZEL-SYSTEM KORRIGIERT
================================================================================
✅ Datenbankschema: username Feld in initials umbenannt
✅ Login erfolgt jetzt über E-Mail-Adresse (Admin kann weiter mit "admin" einloggen)
✅ E-Mail ist jetzt UNIQUE und NOT NULL
✅ Kürzel (initials) kann in Admin-Benutzerverwaltung bearbeitet werden
✅ Initialen-Anzeige vereinheitlicht - verwendet immer das initials Feld
✅ Redundantes "Initialen" Feld aus Benutzerformular entfernt
✅ Login-Response korrigiert - sendet jetzt initials Feld
✅ Alle Frontend-Komponenten verwenden jetzt das initials Feld
✅ User-API sendet initials statt username
✅ Changelog-Einträge in Wissensdatenbank übertragen
✅ Git-Repository Erkennung im Coding-Tab repariert
Wichtige Änderungen:
- Login nur noch über E-Mail (nicht mehr über Kürzel)
- Kürzel sind reine Anzeige-Elemente (2 Buchstaben)
- Admin-User hat spezielle E-Mail: admin@taskmate.local
- Initialen-Anzeige jetzt überall konsistent
- Changelog-System: Alle wichtigen Änderungen in Wissen > Changelog dokumentiert
- TaskMate-Kachel in "Gitea-Repo" umbenannt, Git-Repository wird wieder erkannt
================================================================================
04.01.2025 - NUTZER-INITIALEN IN ADMIN-BEREICH
================================================================================
✅ Initialen-Anzeige in Nutzer-Kreisen implementiert
✅ Intelligente Initialen-Generierung aus E-Mail oder Namen
✅ CSS für bessere Darstellung angepasst
✅ Benutzerdefinierte Initialen-Eingabe hinzugefügt
✅ Datenbank-Migration für custom_initials Spalte
Neue Features:
- Initialen werden jetzt aus dem Kürzel-Feld (username) angezeigt
- Kürzel können über die Admin-Einstellungen geändert werden
- Vereinfachte Logik: Kürzel = Initialen im Nutzerkreis
Initialen-Logik:
1. Username/Kürzel wird als Initialen angezeigt (z.B. HG, MO)
2. Fallback → Erste zwei Buchstaben des Display Names
Benutzer-Kürzel aktualisiert:
- hendrik_gebhardt@gmx.de → HG
- momohomma@googlemail.com → MO
Cache-Version auf 194 erhöht.
================================================================================ ================================================================================
03.01.2025 - GIT-INTEGRATION CODING-KACHELN 03.01.2025 - GIT-INTEGRATION CODING-KACHELN
================================================================================ ================================================================================

105
SSH_CLAUDE_ANLEITUNG.txt Normale Datei
Datei anzeigen

@ -0,0 +1,105 @@
# Anleitung: Claude über SSH aus TaskMate starten
## Voraussetzungen
- SSH-Client auf dem eigenen Computer
- Zugang zu TaskMate
## Schritt 1: SSH-Schlüsselpaar erstellen
### Windows (PowerShell/Terminal):
```
ssh-keygen -t rsa -b 4096 -f %USERPROFILE%\.ssh\taskmate_claude
```
### Mac/Linux:
```
ssh-keygen -t rsa -b 4096 -f ~/.ssh/taskmate_claude
```
**Wichtig:**
- Bei der Frage nach einem Passwort ein sicheres Passwort vergeben und merken
- Es werden zwei Dateien erstellt:
- `taskmate_claude` (privater Schlüssel - GEHEIM HALTEN!)
- `taskmate_claude.pub` (öffentlicher Schlüssel)
## Schritt 2: Öffentlichen Schlüssel übermitteln
Den Inhalt der Datei `taskmate_claude.pub` an den Administrator senden:
### Windows:
```
type %USERPROFILE%\.ssh\taskmate_claude.pub
```
### Mac/Linux:
```
cat ~/.ssh/taskmate_claude.pub
```
Der Administrator muss diesen öffentlichen Schlüssel auf dem Server hinterlegen.
## Schritt 3: SSH-Konfiguration einrichten
Eine Konfigurationsdatei erstellen für einfacheren Zugriff:
### Windows:
Datei `%USERPROFILE%\.ssh\config` bearbeiten
### Mac/Linux:
Datei `~/.ssh/config` bearbeiten
Folgenden Inhalt hinzufügen:
```
Host taskmate-claude
HostName [SERVER-IP oder DOMAIN]
User [BENUTZERNAME]
Port [SSH-PORT, standard 22]
IdentityFile ~/.ssh/taskmate_claude
```
**Hinweis:** Die Werte in eckigen Klammern müssen vom Administrator bereitgestellt werden.
## Schritt 4: Verbindung testen
```
ssh taskmate-claude
```
Bei der ersten Verbindung:
1. Die Fingerprint-Warnung mit "yes" bestätigen
2. Das beim Erstellen des Schlüssels vergebene Passwort eingeben
## Schritt 5: Integration in TaskMate
Nach erfolgreicher SSH-Konfiguration:
1. In TaskMate einloggen
2. Zur Kachel "TaskMate -> Claude starten" navigieren
3. Die Funktion sollte nun verfügbar sein
## Sicherheitshinweise
- **Privaten Schlüssel niemals weitergeben!** (Datei ohne .pub Endung)
- Das Passwort für den SSH-Schlüssel sicher aufbewahren
- Bei Verlust des privaten Schlüssels muss ein neues Schlüsselpaar erstellt werden
## Fehlerbehebung
### "Permission denied"
- Prüfen ob der richtige Schlüssel verwendet wird
- Prüfen ob das Passwort korrekt ist
### "Connection refused"
- Server-IP/Domain und Port prüfen
- Firewall-Einstellungen prüfen
### "Host key verification failed"
- Die Datei `~/.ssh/known_hosts` prüfen
- Ggf. alten Eintrag für den Server entfernen
## Benötigte Informationen vom Administrator
Der Administrator muss folgende Daten bereitstellen:
1. Server-IP oder Domain
2. SSH-Port (falls nicht Standard 22)
3. Benutzername für SSH-Zugang
4. Bestätigung dass der öffentliche Schlüssel hinterlegt wurde

Datei anzeigen

@ -170,6 +170,63 @@ function createTables() {
logger.info('Migration: repositories_base_path Spalte zu users hinzugefuegt'); logger.info('Migration: repositories_base_path Spalte zu users hinzugefuegt');
} }
// Migration: Add custom_initials column to users
const hasCustomInitials = userColumns.some(col => col.name === 'custom_initials');
if (!hasCustomInitials) {
db.exec("ALTER TABLE users ADD COLUMN custom_initials TEXT");
logger.info('Migration: custom_initials Spalte zu users hinzugefuegt');
}
// Migration: Add initials column and prepare email
const hasInitials = userColumns.some(col => col.name === 'initials');
if (!hasInitials && userColumns.some(col => col.name === 'username')) {
logger.info('Migration: Füge initials Spalte hinzu und bereite E-Mail vor');
// Zuerst Daten vorbereiten
const users = db.prepare('SELECT id, username, email, custom_initials FROM users').all();
for (const user of users) {
// Stelle sicher dass jeder Benutzer eine E-Mail hat
if (!user.email || user.email === '') {
if (user.username === 'admin') {
// Admin bekommt eine spezielle E-Mail
db.prepare('UPDATE users SET email = ? WHERE id = ?').run('admin@taskmate.local', user.id);
} else if (user.username.includes('@')) {
// Username enthält bereits E-Mail (wie bei bestehenden Benutzern)
db.prepare('UPDATE users SET email = ? WHERE id = ?').run(user.username, user.id);
}
}
// Initialen setzen (aus custom_initials oder generieren)
if (!user.custom_initials || user.custom_initials === '') {
let initials = 'XX';
if (user.username === 'admin') {
initials = 'AD';
} else if (user.email || user.username.includes('@')) {
// Generiere Initialen aus E-Mail
const emailPart = (user.email || user.username).split('@')[0];
if (emailPart.includes('_')) {
const parts = emailPart.split('_');
initials = (parts[0][0] + parts[1][0]).toUpperCase();
} else if (emailPart.includes('.')) {
const parts = emailPart.split('.');
initials = (parts[0][0] + parts[1][0]).toUpperCase();
} else {
initials = emailPart.substring(0, 2).toUpperCase();
}
}
db.prepare('UPDATE users SET custom_initials = ? WHERE id = ?').run(initials, user.id);
}
}
// Neue initials Spalte hinzufügen
db.exec("ALTER TABLE users ADD COLUMN initials TEXT");
// Daten von custom_initials nach initials kopieren
db.exec("UPDATE users SET initials = custom_initials");
logger.info('Migration: initials Spalte hinzugefügt und E-Mail-Daten vorbereitet');
}
// Proposals (Vorschlaege) // Proposals (Vorschlaege)
db.exec(` db.exec(`
CREATE TABLE IF NOT EXISTS proposals ( CREATE TABLE IF NOT EXISTS proposals (
@ -536,7 +593,7 @@ async function createDefaultUsers() {
const existingUsers = db.prepare('SELECT COUNT(*) as count FROM users').get(); const existingUsers = db.prepare('SELECT COUNT(*) as count FROM users').get();
// Admin-Passwort korrigieren (falls aus .env verschieden) // Admin-Passwort korrigieren (falls aus .env verschieden)
const adminExists = db.prepare('SELECT id, password_hash FROM users WHERE username = ? AND role = ?').get('admin', 'admin'); const adminExists = db.prepare('SELECT id, password_hash FROM users WHERE email = ? AND role = ?').get('admin@taskmate.local', 'admin');
if (adminExists) { if (adminExists) {
const correctAdminPassword = process.env.ADMIN_PASSWORD || 'admin123'; const correctAdminPassword = process.env.ADMIN_PASSWORD || 'admin123';
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');

Datei anzeigen

@ -31,8 +31,8 @@ router.get('/users', (req, res) => {
try { try {
const db = getDb(); const db = getDb();
const users = db.prepare(` const users = db.prepare(`
SELECT id, username, display_name, color, role, permissions, email, SELECT id, email, initials, display_name, color, role, permissions,
created_at, last_login, failed_attempts, locked_until created_at, last_login, failed_attempts, locked_until, custom_initials
FROM users FROM users
ORDER BY id ORDER BY id
`).all(); `).all();
@ -55,10 +55,10 @@ router.get('/users', (req, res) => {
*/ */
router.post('/users', async (req, res) => { router.post('/users', async (req, res) => {
try { try {
const { username, password, displayName, email, role, permissions } = req.body; const { initials, password, displayName, email, role, permissions } = req.body;
// Validierung // Validierung
if (!username || !password || !displayName || !email) { if (!initials || !password || !displayName || !email) {
return res.status(400).json({ error: 'Kürzel, Passwort, Anzeigename und E-Mail erforderlich' }); return res.status(400).json({ error: 'Kürzel, Passwort, Anzeigename und E-Mail erforderlich' });
} }
@ -69,8 +69,8 @@ router.post('/users', async (req, res) => {
} }
// Kürzel muss genau 2 Buchstaben sein // Kürzel muss genau 2 Buchstaben sein
const usernameUpper = username.toUpperCase(); const initialsUpper = initials.toUpperCase();
if (!/^[A-Z]{2}$/.test(usernameUpper)) { if (!/^[A-Z]{2}$/.test(initialsUpper)) {
return res.status(400).json({ error: 'Kürzel muss genau 2 Buchstaben sein (z.B. HG)' }); return res.status(400).json({ error: 'Kürzel muss genau 2 Buchstaben sein (z.B. HG)' });
} }
@ -80,18 +80,18 @@ router.post('/users', async (req, res) => {
const db = getDb(); const db = getDb();
// Prüfen ob Kürzel bereits existiert
const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(usernameUpper);
if (existing) {
return res.status(400).json({ error: 'Kürzel bereits vergeben' });
}
// Prüfen ob E-Mail bereits existiert // Prüfen ob E-Mail bereits existiert
const existingEmail = db.prepare('SELECT id FROM users WHERE email = ?').get(email.toLowerCase()); const existingEmail = db.prepare('SELECT id FROM users WHERE email = ?').get(email.toLowerCase());
if (existingEmail) { if (existingEmail) {
return res.status(400).json({ error: 'E-Mail bereits vergeben' }); return res.status(400).json({ error: 'E-Mail bereits vergeben' });
} }
// Prüfen ob Kürzel bereits existiert
const existingInitials = db.prepare('SELECT id FROM users WHERE initials = ?').get(initialsUpper);
if (existingInitials) {
return res.status(400).json({ error: 'Kürzel bereits vergeben' });
}
// Passwort hashen // Passwort hashen
const passwordHash = await bcrypt.hash(password, 12); const passwordHash = await bcrypt.hash(password, 12);
@ -100,23 +100,24 @@ router.post('/users', async (req, res) => {
// Benutzer erstellen // Benutzer erstellen
const result = db.prepare(` const result = db.prepare(`
INSERT INTO users (username, password_hash, display_name, color, role, permissions, email) INSERT INTO users (email, initials, password_hash, display_name, color, role, permissions, custom_initials)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run( `).run(
usernameUpper, email.toLowerCase(),
initialsUpper,
passwordHash, passwordHash,
displayName, displayName,
defaultColor, defaultColor,
role || 'user', role || 'user',
JSON.stringify(permissions || []), JSON.stringify(permissions || []),
email.toLowerCase() initialsUpper
); );
logger.info(`Admin ${req.user.username} hat Benutzer ${usernameUpper} erstellt`); logger.info(`Admin ${req.user.email} hat Benutzer ${initialsUpper} erstellt`);
res.status(201).json({ res.status(201).json({
id: result.lastInsertRowid, id: result.lastInsertRowid,
username: usernameUpper, initials: initialsUpper,
displayName, displayName,
email: email.toLowerCase(), email: email.toLowerCase(),
color: defaultColor, color: defaultColor,
@ -135,7 +136,7 @@ router.post('/users', async (req, res) => {
router.put('/users/:id', async (req, res) => { router.put('/users/:id', async (req, res) => {
try { try {
const userId = parseInt(req.params.id); const userId = parseInt(req.params.id);
const { displayName, color, role, permissions, password, unlockAccount, email } = req.body; const { displayName, color, role, permissions, password, unlockAccount, email, initials } = req.body;
const db = getDb(); const db = getDb();
@ -206,6 +207,24 @@ router.put('/users/:id', async (req, res) => {
params.push(email.toLowerCase()); params.push(email.toLowerCase());
} }
if (initials !== undefined) {
// Validierung: Genau 2 Buchstaben
const initialsUpper = initials.toUpperCase();
if (!/^[A-Z]{2}$/.test(initialsUpper)) {
return res.status(400).json({ error: 'Kürzel muss genau 2 Buchstaben sein (z.B. HG)' });
}
// Prüfen ob Kürzel bereits von anderem Benutzer verwendet wird
const existingInitials = db.prepare('SELECT id FROM users WHERE initials = ? AND id != ?').get(initialsUpper, userId);
if (existingInitials) {
return res.status(400).json({ error: 'Kürzel bereits vergeben' });
}
updates.push('initials = ?');
params.push(initialsUpper);
// Auch custom_initials aktualisieren für Kompatibilität
updates.push('custom_initials = ?');
params.push(initialsUpper);
}
if (updates.length === 0) { if (updates.length === 0) {
return res.status(400).json({ error: 'Keine Änderungen angegeben' }); return res.status(400).json({ error: 'Keine Änderungen angegeben' });
} }
@ -218,7 +237,7 @@ router.put('/users/:id', async (req, res) => {
// Aktualisierten Benutzer zurueckgeben // Aktualisierten Benutzer zurueckgeben
const updatedUser = db.prepare(` const updatedUser = db.prepare(`
SELECT id, username, display_name, color, role, permissions, email, SELECT id, username, display_name, color, role, permissions, email,
created_at, last_login, failed_attempts, locked_until created_at, last_login, failed_attempts, locked_until, custom_initials
FROM users WHERE id = ? FROM users WHERE id = ?
`).get(userId); `).get(userId);

Datei anzeigen

@ -35,10 +35,15 @@ router.post('/login', async (req, res) => {
const db = getDb(); const db = getDb();
// Benutzer suchen: Zuerst nach Username "admin", dann nach E-Mail // Benutzer suchen: Admin kann mit "admin" einloggen, alle anderen mit E-Mail
let user; let user;
// User per Username suchen (kann E-Mail-Adresse oder admin sein) if (username.toLowerCase() === 'admin') {
user = db.prepare('SELECT * FROM users WHERE username = ?').get(username); // Admin-Login über spezielle E-Mail
user = db.prepare('SELECT * FROM users WHERE email = ? AND role = ?').get('admin@taskmate.local', 'admin');
} else {
// Normale User loggen sich mit E-Mail ein
user = db.prepare('SELECT * FROM users WHERE email = ?').get(username);
}
// Audit-Log Eintrag vorbereiten // Audit-Log Eintrag vorbereiten
const logAttempt = (userId, success) => { const logAttempt = (userId, success) => {
@ -131,7 +136,8 @@ router.post('/login', async (req, res) => {
csrfToken, csrfToken,
user: { user: {
id: user.id, id: user.id,
username: user.username, email: user.email,
initials: user.initials,
displayName: user.display_name, displayName: user.display_name,
color: user.color, color: user.color,
role: user.role || 'user', role: user.role || 'user',
@ -345,14 +351,15 @@ router.get('/users', authenticateToken, (req, res) => {
const db = getDb(); const db = getDb();
// Nur regulaere Benutzer (nicht Admins) fuer Aufgaben-Zuweisung // Nur regulaere Benutzer (nicht Admins) fuer Aufgaben-Zuweisung
const users = db.prepare(` const users = db.prepare(`
SELECT id, username, display_name, color SELECT id, email, initials, display_name, color
FROM users FROM users
WHERE role != 'admin' OR role IS NULL WHERE role != 'admin' OR role IS NULL
`).all(); `).all();
res.json(users.map(u => ({ res.json(users.map(u => ({
id: u.id, id: u.id,
username: u.username, email: u.email,
initials: u.initials,
displayName: u.display_name, displayName: u.display_name,
color: u.color color: u.color
}))); })));

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -108,6 +108,14 @@
font-weight: 600; font-weight: 600;
font-size: 1rem; font-size: 1rem;
flex-shrink: 0; flex-shrink: 0;
position: relative;
}
.admin-user-initials {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
.admin-user-info { .admin-user-info {

Datei anzeigen

@ -150,15 +150,34 @@ class AdminManager {
}); });
} }
/**
* Holt die Initialen des Benutzers
*/
getInitials(user) {
// Verwende primär das initials Feld
if (user.initials) {
return user.initials.toUpperCase();
}
// Fallback auf custom_initials für Kompatibilität
if (user.custom_initials) {
return user.custom_initials.toUpperCase();
}
// Letzer Fallback
return 'XX';
}
renderUserCard(user) { renderUserCard(user) {
const initial = (user.display_name || user.username).charAt(0).toUpperCase(); const initials = this.getInitials(user);
console.log('Rendering user card for:', user.email || user.username, '- Initials:', initials);
const isLocked = user.locked_until && new Date(user.locked_until) > new Date(); const isLocked = user.locked_until && new Date(user.locked_until) > new Date();
const permissions = user.permissions || []; const permissions = user.permissions || [];
return ` return `
<div class="admin-user-card" data-user-id="${user.id}"> <div class="admin-user-card" data-user-id="${user.id}">
<div class="admin-user-avatar" style="background-color: ${user.color || '#808080'}"> <div class="admin-user-avatar" style="background-color: ${user.color || '#808080'}">
${initial} <span class="admin-user-initials">${initials}</span>
</div> </div>
<div class="admin-user-info"> <div class="admin-user-info">
<div class="admin-user-name">${this.escapeHtml(user.display_name)}</div> <div class="admin-user-name">${this.escapeHtml(user.display_name)}</div>
@ -236,8 +255,8 @@ class AdminManager {
this.currentEditUser = user; this.currentEditUser = user;
this.userModalTitle.textContent = 'Benutzer bearbeiten'; this.userModalTitle.textContent = 'Benutzer bearbeiten';
this.editUserId.value = user.id; this.editUserId.value = user.id;
this.usernameInput.value = user.username; this.usernameInput.value = user.initials || '';
this.usernameInput.disabled = true; // Username cannot be changed this.usernameInput.disabled = false; // Initials can be changed
this.displayNameInput.value = user.display_name; this.displayNameInput.value = user.display_name;
this.emailInput.value = user.email || ''; this.emailInput.value = user.email || '';
this.emailInput.disabled = false; this.emailInput.disabled = false;
@ -285,9 +304,8 @@ class AdminManager {
permissions: this.roleSelect.value === 'admin' ? [] : this.getSelectedPermissions() permissions: this.roleSelect.value === 'admin' ? [] : this.getSelectedPermissions()
}; };
if (!isEdit) { // Kürzel immer mitschicken (bei Create und Update)
data.username = this.usernameInput.value.trim().toUpperCase(); data.initials = this.usernameInput.value.trim().toUpperCase();
}
if (this.passwordInput.value) { if (this.passwordInput.value) {
data.password = this.passwordInput.value; data.password = this.passwordInput.value;

Datei anzeigen

@ -136,11 +136,13 @@ class AuthManager {
getUserInitials() { getUserInitials() {
if (!this.user) return '?'; if (!this.user) return '?';
return this.user.username // Verwende das initials Feld direkt
.split(' ') if (this.user.initials) {
.map(part => part.charAt(0).toUpperCase()) return this.user.initials.toUpperCase();
.slice(0, 2) }
.join('');
// Fallback für alte Daten
return '??';
} }
// Get user color // Get user color

Datei anzeigen

@ -372,6 +372,7 @@ class BoardManager {
const currentUser = users.find(u => u.id === assignee.id); const currentUser = users.find(u => u.id === assignee.id);
const color = currentUser?.color || assignee.color || '#888'; const color = currentUser?.color || assignee.color || '#888';
const name = currentUser?.display_name || assignee.display_name || assignee.username || 'Benutzer'; const name = currentUser?.display_name || assignee.display_name || assignee.username || 'Benutzer';
const initials = currentUser?.initials || assignee.initials || getInitials(name);
const avatar = createElement('span', { const avatar = createElement('span', {
className: 'avatar task-assignee-avatar stacked', className: 'avatar task-assignee-avatar stacked',
@ -380,7 +381,7 @@ class BoardManager {
zIndex: 10 - index zIndex: 10 - index
}, },
title: name title: name
}, [getInitials(name)]); }, [initials]);
assigneesContainer.appendChild(avatar); assigneesContainer.appendChild(avatar);
}); });

Datei anzeigen

@ -868,7 +868,7 @@ class CalendarViewManager {
if (task.assignees && task.assignees.length > 0) { if (task.assignees && task.assignees.length > 0) {
task.assignees.forEach(assignee => { task.assignees.forEach(assignee => {
const currentUser = users.find(u => u.id === assignee.id); const currentUser = users.find(u => u.id === assignee.id);
const initials = currentUser?.username || assignee.username || '??'; const initials = currentUser?.initials || assignee.initials || '??';
const color = currentUser?.color || assignee.color || '#6B7280'; const color = currentUser?.color || assignee.color || '#6B7280';
badges.push({ initials, color }); badges.push({ initials, color });
}); });
@ -879,7 +879,7 @@ class CalendarViewManager {
if (task.assignedTo) { if (task.assignedTo) {
const user = users.find(u => u.id === task.assignedTo); const user = users.find(u => u.id === task.assignedTo);
if (user) { if (user) {
const initials = user.username || '??'; const initials = user.initials || '??';
badges.push({ initials, color: user.color || '#6B7280' }); badges.push({ initials, color: user.color || '#6B7280' });
return badges; return badges;
} }

Datei anzeigen

@ -251,7 +251,7 @@ class DashboardManager {
createElement('span', { createElement('span', {
className: 'avatar avatar-sm', className: 'avatar avatar-sm',
style: { backgroundColor: task.assignee.color || '#888' } style: { backgroundColor: task.assignee.color || '#888' }
}, [getInitials(task.assignee.username)]) }, [task.assignee.initials || getInitials(task.assignee.username || '??')])
]) : null ]) : null
].filter(Boolean)); ].filter(Boolean));

Datei anzeigen

@ -443,7 +443,7 @@ class ListViewManager {
className: 'avatar', className: 'avatar',
style: { backgroundColor: user.color || '#6366F1' }, style: { backgroundColor: user.color || '#6366F1' },
title: user.displayName // Tooltip zeigt Name beim Hover title: user.displayName // Tooltip zeigt Name beim Hover
}, [getInitials(user.displayName)]); }, [user.initials || getInitials(user.displayName)]);
avatarContainer.appendChild(avatar); avatarContainer.appendChild(avatar);
} }
}); });

Datei anzeigen

@ -818,7 +818,7 @@ class TaskModalManager {
const avatar = createElement('div', { const avatar = createElement('div', {
class: 'multi-select-option-avatar', class: 'multi-select-option-avatar',
style: `background-color: ${user.color || '#6366F1'}` style: `background-color: ${user.color || '#6366F1'}`
}, [getInitials(user.display_name || user.username)]); }, [user.initials || getInitials(user.display_name || user.username)]);
const name = createElement('span', { class: 'multi-select-option-name' }, [user.display_name || user.username]); const name = createElement('span', { class: 'multi-select-option-name' }, [user.display_name || user.username]);

Datei anzeigen

@ -4,7 +4,7 @@
* Offline support and caching * Offline support and caching
*/ */
const CACHE_VERSION = '189'; const CACHE_VERSION = '197';
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION; const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION; const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION; const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;

Datei-Diff unterdrückt, da er zu groß ist Diff laden