/** * TASKMATE - Reminders Module * =========================== * Erinnerungsmanagement */ import store from './store.js'; import api from './api.js'; import { $, $$, createElement, clearElement, formatDate } from './utils.js'; class ReminderManager { constructor() { this.modal = null; this.form = null; this.mode = 'create'; this.reminderId = null; this.selectedColor = '#F59E0B'; this.init(); } init() { this.modal = $('#reminder-modal'); this.form = $('#reminder-form'); // Wait for DOM to be ready, then bind events if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.bindEvents()); } else { this.bindEvents(); } // Listen for modal events window.addEventListener('modal:open', (e) => { if (e.detail.modalId === 'reminder-modal') { this.open(e.detail.mode, e.detail.data); } }); window.addEventListener('modal:close', (e) => { if (e.detail.modalId === 'reminder-modal') { this.close(); } }); // Also bind button when calendar view is loaded window.addEventListener('app:view-changed', (e) => { if (e.detail.view === 'calendar') { setTimeout(() => this.bindCalendarButton(), 100); } }); } bindEvents() { // Form submission this.form?.addEventListener('submit', (e) => this.handleSubmit(e)); // Close buttons $$('.modal-close', this.modal)?.forEach(btn => { btn.addEventListener('click', () => this.close()); }); $('#btn-cancel-reminder')?.addEventListener('click', () => this.close()); // Delete button $('#btn-delete-reminder')?.addEventListener('click', () => this.handleDelete()); // Color picker trigger const colorTrigger = $('#color-picker-trigger'); const colorDropdown = $('#color-picker-dropdown'); if (colorTrigger) { colorTrigger.addEventListener('click', (e) => { e.stopPropagation(); colorDropdown.classList.toggle('hidden'); }); } // Color picker options $$('.color-picker-dropdown .color-option').forEach(option => { option.addEventListener('click', (e) => { e.stopPropagation(); const color = e.target.dataset.color; // Update selected state $$('.color-picker-dropdown .color-option').forEach(opt => opt.classList.remove('selected')); e.target.classList.add('selected'); // Update trigger color and form value colorTrigger.style.backgroundColor = color; this.selectedColor = color; $('#reminder-color').value = color; // Close dropdown colorDropdown.classList.add('hidden'); }); }); // Close dropdown when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.color-picker-wrapper')) { colorDropdown?.classList.add('hidden'); } }); // Setup custom select for user dropdown this.setupCustomSelect(); // Calendar button will be bound separately this.bindCalendarButton(); } bindCalendarButton() { const reminderBtn = $('#btn-new-reminder'); if (reminderBtn) { console.log('[Reminder] Button found, binding event'); // Remove existing event listeners const newBtn = reminderBtn.cloneNode(true); reminderBtn.parentNode.replaceChild(newBtn, reminderBtn); newBtn.addEventListener('click', (e) => { console.log('[Reminder] Button clicked!'); e.preventDefault(); e.stopPropagation(); console.log('[Reminder] Opening modal...'); console.log('[Reminder] Modal element:', this.modal); this.open('create', {}); }); } else { console.warn('[Reminder] Button #btn-new-reminder not found!'); } } async open(mode = 'create', data = {}) { console.log('[Reminder] open() called with mode:', mode, 'data:', data); console.log('[Reminder] Modal exists:', !!this.modal); this.mode = mode; this.reminderId = data.reminderId || null; // Find modal if not already set if (!this.modal) { this.modal = $('#reminder-modal'); console.log('[Reminder] Modal found on second attempt:', !!this.modal); } if (!this.modal) { console.error('[Reminder] Modal element not found!'); return; } // Modal title const titleEl = $('#reminder-modal-title'); if (titleEl) { titleEl.textContent = mode === 'edit' ? 'Erinnerung bearbeiten' : 'Neue Erinnerung'; console.log('[Reminder] Title set to:', titleEl.textContent); } // Button text const saveBtn = $('#btn-save-reminder .btn-text'); if (saveBtn) { saveBtn.textContent = mode === 'edit' ? 'Aktualisieren' : 'Speichern'; } // Show/hide delete button const deleteBtn = $('#btn-delete-reminder'); if (deleteBtn) { if (mode === 'edit' && this.reminderId) { deleteBtn.classList.remove('hidden'); } else { deleteBtn.classList.add('hidden'); } } this.resetForm(); await this.loadUsers(); // Benutzer laden if (mode === 'edit' && this.reminderId) { this.loadReminder(); } else if (data.prefill) { this.prefillForm(data.prefill); } // Show modal console.log('[Reminder] Showing modal...'); // Create overlay if it doesn't exist let overlay = document.querySelector('.modal-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.className = 'modal-overlay'; document.body.appendChild(overlay); } // Show overlay first overlay.classList.add('visible'); // Show modal this.modal.classList.remove('hidden'); this.modal.classList.add('visible'); console.log('[Reminder] Modal and overlay should now be visible'); } close() { // Hide modal this.modal.classList.remove('visible'); setTimeout(() => this.modal.classList.add('hidden'), 200); // Hide overlay const overlay = document.querySelector('.modal-overlay'); if (overlay) { overlay.classList.remove('visible'); } this.resetForm(); } resetForm() { if (this.form) { this.form.reset(); $('#reminder-time').value = '09:00'; // Reset color selection $$('.color-picker-dropdown .color-option').forEach(opt => opt.classList.remove('selected')); $$('.color-picker-dropdown .color-option[data-color="#F59E0B"]').forEach(opt => opt.classList.add('selected')); const colorTrigger = $('#color-picker-trigger'); if (colorTrigger) { colorTrigger.style.backgroundColor = '#F59E0B'; } this.selectedColor = '#F59E0B'; $('#reminder-color').value = '#F59E0B'; // Reset advance days $$('input[name="advance-days"]').forEach(cb => { cb.checked = cb.value === '1'; }); } } prefillForm(prefill) { if (prefill.date) { $('#reminder-date').value = prefill.date; } } async loadUsers() { console.log('[Reminder] Loading users...'); let users = store.get('users') || []; console.log('[Reminder] Users from store:', users); // If no users in store, try to load them if (users.length === 0) { try { console.log('[Reminder] No users in store, loading from API...'); const response = await api.request('/auth/users'); users = response.data || response; console.log('[Reminder] Users from API:', users); // Update store if (users.length > 0) { store.setUsers(users); } } catch (error) { console.error('[Reminder] Failed to load users:', error); users = []; } } const optionsContainer = $('#reminder-assignee-options'); if (!optionsContainer) { console.warn('[Reminder] Assignee options container not found!'); return; } // Clear existing options except first one optionsContainer.innerHTML = `
Alle Benutzer
`; // Add user options with avatars users.forEach(user => { console.log('[Reminder] Adding user:', user); // Get user info const displayName = user.displayName || user.display_name || user.username || user.email; const initials = user.initials || this.getInitials(displayName); const color = user.color || '#6366F1'; // Create option element const option = document.createElement('div'); option.className = 'custom-select-option'; option.dataset.value = user.id; option.dataset.initials = initials; option.dataset.color = color; option.dataset.displayName = displayName; option.innerHTML = `
${initials}
${displayName} `; optionsContainer.appendChild(option); }); // Setup custom select behavior this.setupCustomSelect(); console.log('[Reminder] Users loaded, total options:', optionsContainer.children.length); } setupCustomSelect() { const wrapper = $('#reminder-assignee-wrapper'); const trigger = $('#reminder-assignee-trigger'); const options = $('#reminder-assignee-options'); const hiddenInput = $('#reminder-assignee'); if (!wrapper || !trigger || !options || !hiddenInput) { console.log('[Reminder] Custom select elements not found, skipping setup'); return; } const valueDisplay = trigger.querySelector('.custom-select-value'); // Remove existing listeners by cloning elements const newTrigger = trigger.cloneNode(true); trigger.parentNode.replaceChild(newTrigger, trigger); const newOptions = options.cloneNode(true); options.parentNode.replaceChild(newOptions, options); // Re-get elements after cloning const freshTrigger = $('#reminder-assignee-trigger'); const freshOptions = $('#reminder-assignee-options'); const freshValueDisplay = freshTrigger.querySelector('.custom-select-value'); // Toggle dropdown freshTrigger.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); wrapper.classList.toggle('open'); console.log('[Reminder] Dropdown toggled, open:', wrapper.classList.contains('open')); }); // Handle option selection freshOptions.addEventListener('click', (e) => { const option = e.target.closest('.custom-select-option'); if (!option) return; const value = option.dataset.value; const displayName = option.dataset.displayName; const initials = option.dataset.initials; const color = option.dataset.color; // Update hidden input hiddenInput.value = value; // Update display if (value === '') { freshValueDisplay.innerHTML = 'Alle Benutzer'; } else { freshValueDisplay.innerHTML = `
${initials}
${displayName} `; } // Update selected state freshOptions.querySelectorAll('.custom-select-option').forEach(opt => { opt.classList.remove('selected'); }); option.classList.add('selected'); // Close dropdown wrapper.classList.remove('open'); }); // Close on outside click document.addEventListener('click', (e) => { if (!wrapper.contains(e.target)) { wrapper.classList.remove('open'); } }); } async loadReminder() { try { const response = await api.request(`/reminders/${this.reminderId}`); const reminder = response.data; $('#reminder-title').value = reminder.title || ''; $('#reminder-description').value = reminder.description || ''; $('#reminder-date').value = reminder.reminder_date || ''; $('#reminder-time').value = reminder.reminder_time || '09:00'; $('#reminder-assignee').value = reminder.assigned_to || ''; // Set color this.selectedColor = reminder.color || '#F59E0B'; $('#reminder-color').value = this.selectedColor; $$('.color-option').forEach(opt => opt.classList.remove('selected')); $$(`[data-color="${this.selectedColor}"]`).forEach(opt => opt.classList.add('selected')); // Set advance days const advanceDays = reminder.advance_days || ['1']; $$('input[name="advance-days"]').forEach(cb => { cb.checked = advanceDays.includes(cb.value); }); } catch (error) { console.error('Error loading reminder:', error); } } async handleSubmit(e) { e.preventDefault(); const saveBtn = $('#btn-save-reminder'); const btnText = saveBtn.querySelector('.btn-text'); const btnLoading = saveBtn.querySelector('.btn-loading'); // Show loading btnText.classList.add('hidden'); btnLoading.classList.remove('hidden'); saveBtn.disabled = true; try { const formData = new FormData(this.form); // Get advance days const advanceDays = []; $$('input[name="advance-days"]:checked').forEach(cb => { advanceDays.push(cb.value); }); if (advanceDays.length === 0) { throw new Error('Bitte wählen Sie mindestens eine Erinnerungszeit aus'); } const data = { project_id: store.get('currentProjectId'), title: formData.get('reminder-title') || $('#reminder-title').value, description: $('#reminder-description').value || null, reminder_date: $('#reminder-date').value, reminder_time: $('#reminder-time').value, assigned_to: $('#reminder-assignee').value || null, color: this.selectedColor, advance_days: advanceDays }; if (this.mode === 'edit') { await api.request(`/reminders/${this.reminderId}`, { method: 'PUT', body: data }); } else { await api.request('/reminders', { method: 'POST', body: data }); } // Update store and refresh calendar if (this.mode === 'edit') { const updatedData = await api.request(`/reminders/${this.reminderId}`); store.updateReminder(this.reminderId, updatedData.data); } else { // Load all reminders to get the new one const projectId = store.get('currentProjectId'); const allReminders = await api.getReminders(projectId); store.setReminders(allReminders); } window.dispatchEvent(new CustomEvent('app:refresh')); this.close(); } catch (error) { console.error('Error saving reminder:', error); alert(`Fehler beim Speichern: ${error.message || 'Unbekannter Fehler'}`); } finally { // Hide loading btnText.classList.remove('hidden'); btnLoading.classList.add('hidden'); saveBtn.disabled = false; } } // Helper Methods getInitials(name) { if (!name) return '?'; return name .split(' ') .map(part => part.charAt(0).toUpperCase()) .slice(0, 2) .join(''); } // API Methods async getRemindersByProject(projectId) { try { const response = await api.request(`/reminders?project_id=${projectId}`); return response.data || []; } catch (error) { console.error('Error fetching reminders:', error); return []; } } async handleDelete() { if (!this.reminderId) return; const reminderTitle = $('#reminder-title').value || 'diese Erinnerung'; if (!confirm(`Möchten Sie "${reminderTitle}" wirklich löschen?`)) { return; } try { await this.deleteReminder(this.reminderId); this.close(); // Reload reminders const projectId = store.get('currentProjectId'); const allReminders = await api.getReminders(projectId); store.setReminders(allReminders); window.dispatchEvent(new CustomEvent('app:refresh')); } catch (error) { console.error('Error deleting reminder:', error); alert('Fehler beim Löschen der Erinnerung. Bitte versuche es erneut.'); } } async deleteReminder(id) { try { await api.request(`/reminders/${id}`, { method: 'DELETE' }); window.dispatchEvent(new CustomEvent('app:refresh')); } catch (error) { console.error('Error deleting reminder:', error); throw error; } } } // Export singleton instance const reminderManager = new ReminderManager(); export default reminderManager;