From daf742e3095ddd9be1566490401826f7ae644d2f Mon Sep 17 00:00:00 2001 From: Vibe Nuage Agent Date: Fri, 5 Jun 2026 19:08:10 +0000 Subject: [PATCH] =?UTF-8?q?Feature:=20Tea-Timer=20hinzugef=C3=BCgt=20(Vore?= =?UTF-8?q?instellung:=203=20Minuten,=20einstellbar=201-15=20Minuten)=20mi?= =?UTF-8?q?t=20Start/Pause/Reset-Funktionen=20und=20Benachrichtigung=20bei?= =?UTF-8?q?=20Ablauf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: trevor1969 --- app.js | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 20 ++++++ styles.css | 99 +++++++++++++++++++++++++ 3 files changed, 326 insertions(+) diff --git a/app.js b/app.js index 5a1f2e5..6cc5aac 100644 --- a/app.js +++ b/app.js @@ -224,9 +224,17 @@ class TeeTracker { this.charts = {}; this.currentBgImage = null; + // Tea Timer + this.timer = null; + this.timerSeconds = 180; // 3 minutes in seconds + this.timerRunning = false; + this.timerPaused = false; + this.remainingSeconds = 180; + // Initialize this.initStorage(); this.setupEventListeners(); + this.setupTimerListeners(); this.loadData().then(() => { this.renderAll(); this.initCharts(); @@ -742,6 +750,39 @@ class TeeTracker { // Nextcloud settings this.setupNextcloudListeners(); + + // Tea Timer controls + this.setupTimerListeners(); + } + + setupTimerListeners() { + // Timer control buttons + const startBtn = document.getElementById('timer-start'); + const pauseBtn = document.getElementById('timer-pause'); + const resetBtn = document.getElementById('timer-reset'); + const decreaseBtn = document.getElementById('timer-decrease'); + const increaseBtn = document.getElementById('timer-increase'); + const timerInput = document.getElementById('timer-input'); + + if (startBtn) { + startBtn.addEventListener('click', () => this.startTimer()); + } + if (pauseBtn) { + pauseBtn.addEventListener('click', () => this.pauseTimer()); + } + if (resetBtn) { + resetBtn.addEventListener('click', () => this.resetTimer()); + } + if (decreaseBtn) { + decreaseBtn.addEventListener('click', () => this.decreaseTimer()); + } + if (increaseBtn) { + increaseBtn.addEventListener('click', () => this.increaseTimer()); + } + if (timerInput) { + timerInput.addEventListener('change', (e) => this.updateTimerFromInput(e)); + timerInput.addEventListener('input', (e) => this.validateTimerInput(e)); + } } setupNextcloudListeners() { @@ -1890,6 +1931,172 @@ class TeeTracker { } } +// ============================================ +// Tea Timer Methods +// ============================================ + +TeeTracker.prototype.startTimer = function() { + if (this.timerRunning && !this.timerPaused) return; + + if (this.timerPaused) { + // Resume from paused state + this.timerPaused = false; + } else { + // Start new timer + const input = document.getElementById('timer-input'); + let minutes = parseInt(input.value) || 3; + + // Clamp to valid range + minutes = Math.max(1, Math.min(15, minutes)); + this.timerSeconds = minutes * 60; + this.remainingSeconds = this.timerSeconds; + input.value = minutes; + } + + this.timerRunning = true; + this.updateTimerButtons(); + this.updateTimerDisplay(); + + this.timer = setInterval(() => { + this.remainingSeconds--; + this.updateTimerDisplay(); + + if (this.remainingSeconds <= 0) { + this.stopTimer(); + this.showTimerNotification('⏰ Timer abgelaufen! Zeit für deinen Tee!'); + + // Play notification sound if possible + if (typeof Audio !== 'undefined') { + try { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + oscillator.frequency.value = 800; + oscillator.type = 'sine'; + gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); + oscillator.start(audioContext.currentTime); + oscillator.stop(audioContext.currentTime + 0.5); + } catch (e) { + console.log('Audio notification not supported'); + } + } + } + }, 1000); +}; + +TeeTracker.prototype.pauseTimer = function() { + if (!this.timerRunning || this.timerPaused) return; + + this.timerPaused = true; + this.timerRunning = false; + this.updateTimerButtons(); + + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } +}; + +TeeTracker.prototype.stopTimer = function() { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + this.timerRunning = false; + this.timerPaused = false; + this.updateTimerButtons(); +}; + +TeeTracker.prototype.resetTimer = function() { + this.stopTimer(); + const input = document.getElementById('timer-input'); + const minutes = parseInt(input.value) || 3; + this.timerSeconds = Math.max(1, Math.min(15, minutes)) * 60; + this.remainingSeconds = this.timerSeconds; + this.updateTimerDisplay(); + this.updateTimerButtons(); +}; + +TeeTracker.prototype.decreaseTimer = function() { + const input = document.getElementById('timer-input'); + let minutes = parseInt(input.value) || 3; + minutes = Math.max(1, minutes - 1); + input.value = minutes; + this.timerSeconds = minutes * 60; + this.remainingSeconds = this.timerSeconds; + this.updateTimerDisplay(); +}; + +TeeTracker.prototype.increaseTimer = function() { + const input = document.getElementById('timer-input'); + let minutes = parseInt(input.value) || 3; + minutes = Math.min(15, minutes + 1); + input.value = minutes; + this.timerSeconds = minutes * 60; + this.remainingSeconds = this.timerSeconds; + this.updateTimerDisplay(); +}; + +TeeTracker.prototype.updateTimerFromInput = function(e) { + let minutes = parseInt(e.target.value) || 3; + minutes = Math.max(1, Math.min(15, minutes)); + e.target.value = minutes; + this.timerSeconds = minutes * 60; + this.remainingSeconds = this.timerSeconds; + this.updateTimerDisplay(); +}; + +TeeTracker.prototype.validateTimerInput = function(e) { + let value = e.target.value; + // Remove non-numeric characters + value = value.replace(/\D/g, ''); + if (value === '') value = '3'; + let minutes = parseInt(value) || 3; + minutes = Math.max(1, Math.min(15, minutes)); + e.target.value = minutes; +}; + +TeeTracker.prototype.updateTimerDisplay = function() { + const display = document.getElementById('timer-display'); + if (!display) return; + + const minutes = Math.floor(this.remainingSeconds / 60); + const seconds = this.remainingSeconds % 60; + display.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; +}; + +TeeTracker.prototype.updateTimerButtons = function() { + const startBtn = document.getElementById('timer-start'); + const pauseBtn = document.getElementById('timer-pause'); + const resetBtn = document.getElementById('timer-reset'); + const decreaseBtn = document.getElementById('timer-decrease'); + const increaseBtn = document.getElementById('timer-increase'); + const timerInput = document.getElementById('timer-input'); + + if (startBtn) startBtn.disabled = this.timerRunning && !this.timerPaused; + if (pauseBtn) pauseBtn.disabled = !this.timerRunning || this.timerPaused; + if (resetBtn) resetBtn.disabled = false; + if (decreaseBtn) decreaseBtn.disabled = this.timerRunning && !this.timerPaused; + if (increaseBtn) increaseBtn.disabled = this.timerRunning && !this.timerPaused; + if (timerInput) timerInput.disabled = this.timerRunning && !this.timerPaused; +}; + +TeeTracker.prototype.showTimerNotification = function(message) { + const notification = document.getElementById('timer-notification'); + if (notification) { + notification.textContent = message; + notification.className = 'timer-notification success'; + notification.style.display = 'block'; + + // Hide after 5 seconds + setTimeout(() => { + notification.style.display = 'none'; + }, 5000); + } +}; + // Initialize the application let app; document.addEventListener('DOMContentLoaded', () => { diff --git a/index.html b/index.html index bbbcaad..e7f670b 100644 --- a/index.html +++ b/index.html @@ -43,6 +43,26 @@ + +
+

⏱️ Tee-Timer

+
+
03:00
+
+ + + + Minuten +
+
+ + + +
+
+ +
+

Letzte Aktivitäten

diff --git a/styles.css b/styles.css index c52871d..e3afc9b 100644 --- a/styles.css +++ b/styles.css @@ -1036,3 +1036,102 @@ header p { box-shadow: var(--box-shadow); margin-right: 10px; } + +/* Tea Timer Styles */ +.timer-section { + background: var(--card-bg); + border-radius: var(--border-radius); + padding: 20px; + margin-top: 20px; + box-shadow: var(--box-shadow); +} + +.timer-section h3 { + margin-bottom: 15px; + color: var(--primary-color); +} + +.timer-controls { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; +} + +.timer-display { + font-size: 3rem; + font-weight: bold; + color: var(--primary-color); + font-family: monospace; +} + +.timer-input-group { + display: flex; + align-items: center; + gap: 10px; +} + +.timer-btn { + width: 30px; + height: 30px; + border: none; + background-color: var(--primary-color); + color: white; + border-radius: 50%; + cursor: pointer; + font-size: 1.2rem; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); +} + +.timer-btn:hover { + background-color: var(--primary-dark); +} + +.timer-btn:disabled { + background-color: #cccccc; + cursor: not-allowed; +} + +.timer-input { + width: 60px; + padding: 8px; + text-align: center; + font-size: 1rem; + border: 1px solid #ddd; + border-radius: var(--border-radius); + background-color: var(--card-bg); +} + +.timer-unit { + color: var(--text-light); + font-size: 0.9rem; +} + +.timer-actions { + display: flex; + gap: 10px; +} + +.timer-notification { + margin-top: 15px; + padding: 10px; + border-radius: var(--border-radius); + text-align: center; + font-weight: bold; + animation: pulse 1s infinite; +} + +.timer-notification.success { + background-color: rgba(40, 167, 69, 0.2); + color: var(--success-color); + border: 1px solid var(--success-color); +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } +}