diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c1a44aa..5bc984b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,125 @@ TASKMATE - CHANGELOG ==================== +================================================================================ +30.12.2025 - Gitea-Integration: Server-Modus +================================================================================ + +FEATURE: SERVER-DATEIEN DIREKT INS GITEA PUSHEN +-------------------------------------------------------------------------------- +- Neuer "Server-Anwendung" Modus im Gitea-Tab +- Ermöglicht Git-Operationen direkt auf dem Server-Verzeichnis /home/claude-dev/TaskMate +- Alle Änderungen an der Anwendung können sofort ins Gitea gepusht werden +- Kein separates Konfigurieren nötig - sofort einsatzbereit + +FEATURE: ZWEI-MODUS-SCHALTER +-------------------------------------------------------------------------------- +- Modus-Schalter oben im Gitea-Tab: "Server-Anwendung" und "Projekt-Repository" +- Server-Modus: Zeigt TaskMate Server-Dateien +- Projekt-Modus: Bisherige Funktionalität für lokale Pfade pro Projekt + +SERVER-MODUS FUNKTIONEN +-------------------------------------------------------------------------------- +- Git-Status anzeigen (geänderte Dateien, aktueller Branch) +- Branch-Wechsel +- Fetch, Pull, Push, Commit +- Commit-Historie anzeigen +- Änderungsliste mit Statusanzeige (M, A, D, ?) + +TECHNISCHE ÄNDERUNGEN +-------------------------------------------------------------------------------- +- docker-compose.yml: Neues Volume-Mount ".:/app/taskmate-source" für Zugriff auf Source-Code +- backend/routes/git.js: Neue Server-Endpoints: + * GET /api/git/server/info - Repository-Informationen + * GET /api/git/server/status - Git-Status + * GET /api/git/server/branches - Branch-Liste + * GET /api/git/server/commits - Commit-Historie + * GET /api/git/server/remote - Remote-URL + * POST /api/git/server/stage - Alle Änderungen stagen + * POST /api/git/server/commit - Commit erstellen + * POST /api/git/server/push - Push ausführen + * POST /api/git/server/pull - Pull ausführen + * POST /api/git/server/fetch - Fetch ausführen + * POST /api/git/server/checkout - Branch wechseln +- frontend/js/api.js: Neue API-Funktionen für Server-Modus +- frontend/js/gitea.js: + * GiteaManager mit currentMode Property + * Server-Modus Methoden (loadServerData, renderServer*, handleServer*) + * Modus-Wechsel Handler +- frontend/index.html: Server-Modus UI mit Modus-Schalter +- frontend/css/gitea.css: Styles für Modus-Schalter (.gitea-mode-switch, .gitea-mode-btn) +- frontend/sw.js: Cache-Version auf 131 erhöht + +================================================================================ +30.12.2025 - Session-Countdown auf Hauptoberfläche +================================================================================ + +FEATURE: SITZUNGS-COUNTDOWN IM HEADER +-------------------------------------------------------------------------------- +- Countdown-Timer im Header zeigt verbleibende Sitzungszeit an +- Format: MM:SS (z.B. 09:45) +- Timer startet immer bei 10:00 Minuten +- Farbliche Warnung bei < 60 Sekunden (orange) +- Kritische Warnung bei < 30 Sekunden (rot, pulsierend) +- Automatischer Logout bei Ablauf mit Toast-Benachrichtigung + +FEATURE: INTERAKTIONS-BASIERTER SESSION-REFRESH +-------------------------------------------------------------------------------- +- Bei jedem Klick oder Tastendruck wird die Session automatisch verlängert +- Timer wird auf 10:00 zurückgesetzt bei Interaktion +- Debouncing verhindert zu viele Server-Anfragen (max. 1 pro Sekunde) +- Bei Browser-Aktualisierung (F5) wird Session automatisch refreshed + +ÄNDERUNG: SITZUNGSZEIT AUF 10 MINUTEN REDUZIERT +-------------------------------------------------------------------------------- +- SESSION_TIMEOUT in .env von 30 auf 10 Minuten geändert +- Erhöhte Sicherheit durch kürzere Sitzungsdauer + +ÄNDERUNGEN +-------------------------------------------------------------------------------- +- .env: SESSION_TIMEOUT=10 +- frontend/index.html: Session-Timer-Element im Header hinzugefügt +- frontend/js/auth.js: + * SessionTimerHandler-Klasse für Countdown-Logik + * JWT-Token-Parsing für Ablaufzeit + * Automatischer Logout bei Session-Ablauf + * Interaktions-basiertes Token-Refresh (Click/Keydown Events) + * initFromExistingSession() für korrekten Timer bei Seiten-Reload +- frontend/js/api.js: X-New-Token-Header verarbeiten und Event dispatchen +- frontend/css/board.css: + * .session-timer Styles + * .warning und .critical Zustände + * pulse-critical Animation +- frontend/sw.js: Cache-Version auf 128 erhöht + +================================================================================ +30.12.2025 - Kalender: Dots nach Statusspalte gruppiert +================================================================================ + +FEATURE: AUFGABEN-DOTS IM WOCHENSTREIFEN NACH SPALTE GRUPPIERT +-------------------------------------------------------------------------------- +- Im Wochenstreifen-Kalender (Board-Ansicht) wird jetzt nur ein Kreis pro + Statusspalte angezeigt statt einem Kreis pro Aufgabe +- Bei Mouseover werden alle Aufgaben dieser Spalte im Tooltip aufgelistet +- Tooltip zeigt: Spaltenname, Anzahl Aufgaben, Liste mit Start/Ende-Info +- Kreis-Typ basiert auf enthaltenen Aufgaben: + * Offener Kreis = alle Aufgaben haben nur Startdatum + * Gefüllter Kreis = alle Aufgaben haben nur Enddatum + * Kreis mit Ring = gemischt (Start und Ende) +- Klick auf Kreis öffnet die erste Aufgabe der Gruppe + +ÄNDERUNGEN +-------------------------------------------------------------------------------- +- frontend/js/board.js: + * getTasksForDay() gruppiert Aufgaben nach Spalte (Map statt Array) + * renderDayDots() rendert einen Dot pro Spalte mit data-task-ids + * showTaskTooltip() zeigt alle Aufgaben der Spalte im Tooltip + * openTaskFromDot() öffnet erste Aufgabe aus der Gruppe +- frontend/css/board.css: + * Neue Tooltip-Stile für gruppierte Aufgaben + * .week-strip-tooltip-header, -column-name, -count, -task-list, -task, etc. +- frontend/sw.js: Cache-Version auf 126 erhöht + ================================================================================ 29.12.2025 - Dokumentation aktualisiert ================================================================================ diff --git a/CLAUDE.md b/CLAUDE.md index acc6ee7..383437c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,55 +1,60 @@ - # TaskMate - Projektanweisungen - - ## Allgemein - - Sprache: Deutsch fuer Benutzer-Kommunikation - - Aenderungen immer in CHANGELOG.txt dokumentieren nach bisher bekannten Schema in der Datei - - Beim Start ANWENDUNGSBESCHREIBUNG.txt lesen - - Cache-Version in frontend/sw.js erhoehen nach Aenderungen - - Ich bin kein Mensch mit Fachwissen im Bereich Coding, daher musst du sämtliche Aufgaben in der Regel übernehmen. - - ## Technologie - - Frontend: Vanilla JavaScript (kein Framework) - - Backend: Node.js mit Express - - Datenbank: SQLite - - ## Konventionen - - CSS-Variablen in frontend/css/variables.css - - Deutsche Umlaute (ä, ö, ü) in Texten verwenden +# TaskMate - Projektanweisungen - ## Datumsformatierung (WICHTIG) - - NIEMALS `toISOString()` für Datumsvergleiche oder -anzeigen verwenden! - - `toISOString()` konvertiert in UTC und verursacht Zeitzonenverschiebungen (z.B. 28.12. wird zu 27.12.) - - Stattdessen lokale Formatierung verwenden: - ```javascript - // Richtig: Lokale Formatierung - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const dateStr = `${year}-${month}-${day}`; +## Infrastruktur/Server +- **Docker-Container**: `taskmate` (hauptsächlich Backend), läuft auf Port 3001 intern → 3000 im Container +- **Frontend-Domain**: https://taskmate.aegis-sight.de +- **Gitea-Repository**: https://gitea-undso.aegis-sight.de/AegisSight/TaskMate +- **Gitea-Token**: `7c62fea51bfe0506a25131bd50ac710ac5aa7e3a9dca37a962e7822bdc7db840` +- **Projektverzeichnis auf Server**: `/home/claude-dev/TaskMate` - // Falsch: UTC-Konvertierung - const dateStr = date.toISOString().split('T')[0]; // NICHT VERWENDEN! - ``` +## Allgemein +- Sprache: Deutsch für Benutzer-Kommunikation +- Änderungen immer in CHANGELOG.txt dokumentieren nach bisher bekanntem Schema in der Datei +- Beim Start ANWENDUNGSBESCHREIBUNG.txt lesen +- Cache-Version in frontend/sw.js erhöhen nach Änderungen +- Ich bin kein Mensch mit Fachwissen im Bereich Coding, daher musst du sämtliche Aufgaben in der Regel übernehmen. - ## Echtzeit-Aktualisierung (KRITISCH) - - ALLE Nutzeranpassungen müssen SOFORT und ÜBERALL in der Anwendung sichtbar sein - - Der Nutzer darf NIEMALS den Browser aktualisieren müssen (F5), um Änderungen zu sehen - - Beispiele für Änderungen, die sofort überall wirken müssen: - - Spaltenfarbe ändern → Board, Kalender, Wochenstreifen sofort aktualisieren - - Aufgabe erstellen/bearbeiten/löschen → alle Ansichten sofort aktualisieren - - Labels, Benutzer, Projekte ändern → überall sofort sichtbar - - Technische Umsetzung: - - `store.subscribe('tasks', callback)` - für Aufgabenänderungen - - `store.subscribe('columns', callback)` - für Spaltenänderungen - - `store.subscribe('labels', callback)` - für Label-Änderungen - - `store.subscribe('users', callback)` - für Benutzeränderungen - - `window.addEventListener('app:refresh', callback)` - für allgemeine Aktualisierungen - - `window.addEventListener('modal:close', callback)` - nach Modal-Schließung - - Bei JEDER neuen Komponente diese Event-Listener einbauen - - Bei JEDER Datenänderung prüfen: Welche UI-Bereiche müssen aktualisiert werden? +## Technologie +- Frontend: Vanilla JavaScript (kein Framework) +- Backend: Node.js mit Express +- Datenbank: SQLite - ## Berechtigungen/Aktionen - - Du sollst den Dockercontainer eigenständig - sofern erforderlich - neu starten/neu bauen, dass Änderungen wirksam werden - - Nach Änderungen immer nach dem Neubau des Dockercontainers oder allgemein ohne Neustart des Dockercontainers, den Browser im Inkognito-Modus starten mit localhost:3000, um die Änderungen sichtbar zu machen. - - \ No newline at end of file +## Konventionen +- CSS-Variablen in frontend/css/variables.css +- Deutsche Umlaute (ä, ö, ü) in Texten verwenden + +## Datumsformatierung (WICHTIG) +- NIEMALS `toISOString()` für Datumsvergleiche oder -anzeigen verwenden! +- `toISOString()` konvertiert in UTC und verursacht Zeitzonenverschiebungen (z.B. 28.12. wird zu 27.12.) +- Stattdessen lokale Formatierung verwenden: + ```javascript + // Richtig: Lokale Formatierung + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; + + // Falsch: UTC-Konvertierung + const dateStr = date.toISOString().split('T')[0]; // NICHT VERWENDEN! + ``` + +## Echtzeit-Aktualisierung (KRITISCH) +- ALLE Nutzeranpassungen müssen SOFORT und ÜBERALL in der Anwendung sichtbar sein +- Der Nutzer darf NIEMALS den Browser aktualisieren müssen (F5), um Änderungen zu sehen +- Beispiele für Änderungen, die sofort überall wirken müssen: + - Spaltenfarbe ändern → Board, Kalender, Wochenstreifen sofort aktualisieren + - Aufgabe erstellen/bearbeiten/löschen → alle Ansichten sofort aktualisieren + - Labels, Benutzer, Projekte ändern → überall sofort sichtbar +- Technische Umsetzung: + - `store.subscribe('tasks', callback)` - für Aufgabenänderungen + - `store.subscribe('columns', callback)` - für Spaltenänderungen + - `store.subscribe('labels', callback)` - für Label-Änderungen + - `store.subscribe('users', callback)` - für Benutzeränderungen + - `window.addEventListener('app:refresh', callback)` - für allgemeine Aktualisierungen + - `window.addEventListener('modal:close', callback)` - nach Modal-Schließung +- Bei JEDER neuen Komponente diese Event-Listener einbauen +- Bei JEDER Datenänderung prüfen: Welche UI-Bereiche müssen aktualisiert werden? + +## Berechtigungen/Aktionen +- Du sollst den Dockercontainer eigenständig - sofern erforderlich - neu starten/neu bauen, dass Änderungen wirksam werden +- Erreichbarkeit der Anwendung über https://taskmate.aegis-sight.de (keine automatische Browser-Öffnung) diff --git a/backend/routes/git.js b/backend/routes/git.js index ef268ee..3a5a232 100644 --- a/backend/routes/git.js +++ b/backend/routes/git.js @@ -11,6 +11,9 @@ const logger = require('../utils/logger'); const gitService = require('../services/gitService'); const giteaService = require('../services/giteaService'); +// Fester Pfad für Server-Modus (TaskMate Source-Code) +const SERVER_SOURCE_PATH = '/app/taskmate-source'; + /** * Hilfsfunktion: Anwendung für Projekt abrufen */ @@ -508,4 +511,250 @@ router.post('/rename-branch/:projectId', (req, res) => { } }); +// ============================================ +// SERVER-MODUS ENDPOINTS +// Für direkte Git-Operationen auf dem TaskMate Source-Code +// ============================================ + +/** + * GET /api/git/server/status + * Git-Status für Server-Dateien abrufen + */ +router.get('/server/status', (req, res) => { + try { + // Prüfe ob der Pfad existiert und ein Git-Repo ist + if (!gitService.isPathAccessible(SERVER_SOURCE_PATH)) { + return res.status(500).json({ + success: false, + error: 'Server-Verzeichnis nicht erreichbar' + }); + } + + if (!gitService.isGitRepository(SERVER_SOURCE_PATH)) { + return res.status(500).json({ + success: false, + error: 'Server-Verzeichnis ist kein Git-Repository' + }); + } + + const result = gitService.getStatus(SERVER_SOURCE_PATH); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Status:', error); + res.status(500).json({ error: 'Serverfehler', details: error.message }); + } +}); + +/** + * GET /api/git/server/branches + * Branches für Server-Dateien abrufen + */ +router.get('/server/branches', (req, res) => { + try { + const result = gitService.getBranches(SERVER_SOURCE_PATH); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Abrufen der Branches:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * GET /api/git/server/commits + * Commit-Historie für Server-Dateien abrufen + */ +router.get('/server/commits', (req, res) => { + try { + const limit = parseInt(req.query.limit) || 20; + const result = gitService.getCommitHistory(SERVER_SOURCE_PATH, limit); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Abrufen der Commits:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * GET /api/git/server/remote + * Remote-URL für Server-Dateien abrufen + */ +router.get('/server/remote', (req, res) => { + try { + const result = gitService.getRemoteUrl(SERVER_SOURCE_PATH); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Abrufen der Remote-URL:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * POST /api/git/server/stage + * Alle Änderungen für Server-Dateien stagen + */ +router.post('/server/stage', (req, res) => { + try { + const result = gitService.stageAll(SERVER_SOURCE_PATH); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Stagen:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * POST /api/git/server/commit + * Commit für Server-Dateien erstellen + */ +router.post('/server/commit', (req, res) => { + try { + const { message, stageAll } = req.body; + + if (!message) { + return res.status(400).json({ error: 'Commit-Nachricht ist erforderlich' }); + } + + // Optional: Alle Änderungen stagen + if (stageAll !== false) { + const stageResult = gitService.stageAll(SERVER_SOURCE_PATH); + if (!stageResult.success) { + return res.json(stageResult); + } + } + + // Autor aus eingeloggtem Benutzer + const author = req.user ? { + name: req.user.display_name || req.user.username, + email: req.user.email || `${req.user.username.toLowerCase()}@taskmate.local` + } : null; + + const result = gitService.commit(SERVER_SOURCE_PATH, message, author); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Commit:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * POST /api/git/server/push + * Push für Server-Dateien ausführen + */ +router.post('/server/push', (req, res) => { + try { + const { branch, force } = req.body; + + // Prüfe ob Remote existiert + if (!gitService.hasRemote(SERVER_SOURCE_PATH)) { + return res.json({ + success: false, + error: 'Kein Remote konfiguriert' + }); + } + + let result; + if (force) { + // Force Push + result = gitService.pushWithUpstream(SERVER_SOURCE_PATH, branch || null, 'origin', true); + } else { + // Normaler Push + result = gitService.pushChanges(SERVER_SOURCE_PATH, { branch }); + + // Falls Push wegen fehlendem Upstream fehlschlägt, versuche mit -u + if (!result.success && result.error && result.error.includes('no upstream')) { + result = gitService.pushWithUpstream(SERVER_SOURCE_PATH, branch || null); + } + } + + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Push:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * POST /api/git/server/pull + * Pull für Server-Dateien ausführen + */ +router.post('/server/pull', (req, res) => { + try { + const { branch } = req.body; + + // Fetch zuerst + gitService.fetchRemote(SERVER_SOURCE_PATH); + + // Dann Pull + const result = gitService.pullChanges(SERVER_SOURCE_PATH, { branch }); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Pull:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * POST /api/git/server/fetch + * Fetch für Server-Dateien ausführen + */ +router.post('/server/fetch', (req, res) => { + try { + const result = gitService.fetchRemote(SERVER_SOURCE_PATH); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Fetch:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * POST /api/git/server/checkout + * Branch für Server-Dateien wechseln + */ +router.post('/server/checkout', (req, res) => { + try { + const { branch } = req.body; + + if (!branch) { + return res.status(400).json({ error: 'Branch ist erforderlich' }); + } + + const result = gitService.checkoutBranch(SERVER_SOURCE_PATH, branch); + res.json(result); + } catch (error) { + logger.error('[Server-Git] Fehler beim Branch-Wechsel:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + +/** + * GET /api/git/server/info + * Grundlegende Infos über Server-Repository + */ +router.get('/server/info', (req, res) => { + try { + const isAccessible = gitService.isPathAccessible(SERVER_SOURCE_PATH); + const isRepo = isAccessible ? gitService.isGitRepository(SERVER_SOURCE_PATH) : false; + const hasRemote = isRepo ? gitService.hasRemote(SERVER_SOURCE_PATH) : false; + + let remoteUrl = null; + if (hasRemote) { + const remoteResult = gitService.getRemoteUrl(SERVER_SOURCE_PATH); + remoteUrl = remoteResult.url || null; + } + + res.json({ + path: SERVER_SOURCE_PATH, + hostPath: '/home/claude-dev/TaskMate', + accessible: isAccessible, + isRepository: isRepo, + hasRemote: hasRemote, + remoteUrl: remoteUrl + }); + } catch (error) { + logger.error('[Server-Git] Fehler beim Info-Abruf:', error); + res.status(500).json({ error: 'Serverfehler' }); + } +}); + module.exports = router; diff --git a/backups/backup_2025-12-22T22-10-28-996Z.db b/backups/backup_2025-12-22T22-10-28-996Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-22T22-10-28-996Z.db and /dev/null differ diff --git a/backups/backup_2025-12-22T22-10-28-996Z.db-wal b/backups/backup_2025-12-22T22-10-28-996Z.db-wal deleted file mode 100644 index 8de6bbb..0000000 Binary files a/backups/backup_2025-12-22T22-10-28-996Z.db-wal and /dev/null differ diff --git a/backups/backup_2025-12-22T22-13-25-791Z.db b/backups/backup_2025-12-22T22-13-25-791Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-22T22-13-25-791Z.db and /dev/null differ diff --git a/backups/backup_2025-12-22T22-13-25-791Z.db-wal b/backups/backup_2025-12-22T22-13-25-791Z.db-wal deleted file mode 100644 index 0334f9a..0000000 Binary files a/backups/backup_2025-12-22T22-13-25-791Z.db-wal and /dev/null differ diff --git a/backups/backup_2025-12-22T22-15-36-577Z.db b/backups/backup_2025-12-22T22-15-36-577Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-22T22-15-36-577Z.db and /dev/null differ diff --git a/backups/backup_2025-12-22T22-15-36-577Z.db-wal b/backups/backup_2025-12-22T22-15-36-577Z.db-wal deleted file mode 100644 index 0334f9a..0000000 Binary files a/backups/backup_2025-12-22T22-15-36-577Z.db-wal and /dev/null differ diff --git a/backups/backup_2025-12-22T22-34-14-857Z.db b/backups/backup_2025-12-22T22-34-14-857Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-22T22-34-14-857Z.db and /dev/null differ diff --git a/backups/backup_2025-12-22T22-34-14-857Z.db-wal b/backups/backup_2025-12-22T22-34-14-857Z.db-wal deleted file mode 100644 index 0334f9a..0000000 Binary files a/backups/backup_2025-12-22T22-34-14-857Z.db-wal and /dev/null differ diff --git a/backups/backup_2025-12-28T15-51-48-822Z.db b/backups/backup_2025-12-28T15-51-48-822Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-28T15-51-48-822Z.db and /dev/null differ diff --git a/backups/backup_2025-12-28T15-51-48-822Z.db-wal b/backups/backup_2025-12-28T15-51-48-822Z.db-wal deleted file mode 100644 index 0036717..0000000 Binary files a/backups/backup_2025-12-28T15-51-48-822Z.db-wal and /dev/null differ diff --git a/backups/backup_2025-12-28T15-58-05-197Z.db b/backups/backup_2025-12-28T15-58-05-197Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-28T15-58-05-197Z.db and /dev/null differ diff --git a/backups/backup_2025-12-28T15-58-05-197Z.db-wal b/backups/backup_2025-12-28T15-58-05-197Z.db-wal deleted file mode 100644 index 2ef0283..0000000 Binary files a/backups/backup_2025-12-28T15-58-05-197Z.db-wal and /dev/null differ diff --git a/backups/backup_2025-12-28T16-06-29-362Z.db b/backups/backup_2025-12-28T16-06-29-362Z.db deleted file mode 100644 index c09da33..0000000 Binary files a/backups/backup_2025-12-28T16-06-29-362Z.db and /dev/null differ diff --git a/backups/backup_2025-12-28T16-06-29-362Z.db-wal b/backups/backup_2025-12-28T16-06-29-362Z.db-wal deleted file mode 100644 index f03a529..0000000 Binary files a/backups/backup_2025-12-28T16-06-29-362Z.db-wal and /dev/null differ diff --git a/data/taskmate.db b/data/taskmate.db index 2f7292d..8c92234 100644 Binary files a/data/taskmate.db and b/data/taskmate.db differ diff --git a/data/taskmate.db-shm b/data/taskmate.db-shm index c576d1d..74142a2 100644 Binary files a/data/taskmate.db-shm and b/data/taskmate.db-shm differ diff --git a/data/taskmate.db-wal b/data/taskmate.db-wal index 92b468b..8c53d73 100644 Binary files a/data/taskmate.db-wal and b/data/taskmate.db-wal differ diff --git a/docker-compose.yml b/docker-compose.yml index 8b4991f..c3977c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - ./backups:/app/backups - ./logs:/app/logs - ./uploads:/app/uploads + - .:/app/taskmate-source environment: - NODE_ENV=production - JWT_SECRET=${JWT_SECRET} diff --git a/frontend/css/board.css b/frontend/css/board.css index 0682f27..bdc4ad8 100644 --- a/frontend/css/board.css +++ b/frontend/css/board.css @@ -314,6 +314,48 @@ 50% { opacity: 0.5; } } +/* Session Timer */ +.session-timer { + display: flex; + align-items: center; + gap: var(--spacing-1); + padding: var(--spacing-1) var(--spacing-2); + font-size: var(--text-sm); + font-family: var(--font-mono, monospace); + color: var(--text-secondary); + background: var(--bg-tertiary); + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.session-timer.hidden { + display: none; +} + +.session-timer-icon { + opacity: 0.7; +} + +.session-timer.warning { + color: var(--warning); + background: rgba(255, 165, 0, 0.15); +} + +.session-timer.warning .session-timer-icon { + opacity: 1; +} + +.session-timer.critical { + color: var(--error); + background: rgba(255, 59, 48, 0.15); + animation: pulse-critical 1s ease-in-out infinite; +} + +@keyframes pulse-critical { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + /* User Menu */ .user-menu { position: relative; @@ -1058,39 +1100,69 @@ pointer-events: none; } -.week-strip-tooltip-title { - font-weight: var(--font-semibold); - color: var(--text-primary); - margin-bottom: 4px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.week-strip-tooltip-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-2); + padding-bottom: var(--spacing-2); + margin-bottom: var(--spacing-2); + border-bottom: 1px solid var(--border-default); + border-left: 3px solid; + padding-left: var(--spacing-2); + margin-left: calc(-1 * var(--spacing-3)); } -.week-strip-tooltip-meta { - display: flex; - flex-direction: column; - gap: 2px; +.week-strip-tooltip-column-name { + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.week-strip-tooltip-count { + font-size: var(--text-2xs); color: var(--text-tertiary); } -.week-strip-tooltip-type { +.week-strip-tooltip-task-list { display: flex; - align-items: center; - gap: 4px; + flex-direction: column; + gap: var(--spacing-1); + max-height: 200px; + overflow-y: auto; } -.week-strip-tooltip-type .dot { +.week-strip-tooltip-task { + display: flex; + align-items: center; + gap: var(--spacing-1); +} + +.week-strip-tooltip-task-dot { width: 6px; height: 6px; border-radius: 50%; + flex-shrink: 0; } -.week-strip-tooltip-type .dot.start { +.week-strip-tooltip-task-dot.start { border: 1.5px solid currentColor; background: transparent; } -.week-strip-tooltip-type .dot.end { +.week-strip-tooltip-task-dot.end { background: currentColor; } + +.week-strip-tooltip-task-title { + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; +} + +.week-strip-tooltip-task-type { + color: var(--text-tertiary); + font-size: var(--text-2xs); + flex-shrink: 0; +} diff --git a/frontend/css/gitea.css b/frontend/css/gitea.css index c027cdf..3795fd6 100644 --- a/frontend/css/gitea.css +++ b/frontend/css/gitea.css @@ -14,6 +14,51 @@ margin: 0 auto; } +/* ============================================================================= + MODE SWITCH + ============================================================================= */ + +.gitea-mode-switch { + display: flex; + gap: var(--spacing-2); + margin-bottom: var(--spacing-4); + padding: var(--spacing-2); + background: var(--bg-tertiary); + border-radius: var(--radius-lg); +} + +.gitea-mode-btn { + display: flex; + align-items: center; + gap: var(--spacing-2); + flex: 1; + padding: var(--spacing-3) var(--spacing-4); + background: transparent; + border: none; + border-radius: var(--radius-md); + color: var(--text-secondary); + font-size: var(--text-sm); + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + justify-content: center; +} + +.gitea-mode-btn svg { + flex-shrink: 0; +} + +.gitea-mode-btn:hover { + color: var(--text-primary); + background: var(--bg-hover); +} + +.gitea-mode-btn.active { + background: var(--bg-primary); + color: var(--text-primary); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + /* ============================================================================= GITEA SECTIONS ============================================================================= */ @@ -641,4 +686,12 @@ .operation-btn { width: 100%; } + + .gitea-mode-switch { + flex-direction: column; + } + + .gitea-mode-btn { + justify-content: center; + } } diff --git a/frontend/index.html b/frontend/index.html index 88310d9..eeea9d9 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -282,6 +282,15 @@ + +