/** * TASKMATE - Contacts Manager * =========================== * Kontaktverwaltung mit Kartenansicht */ import api from './api.js'; import { $, $$, formatDate, debounce } from './utils.js'; import store from './store.js'; class ContactsManager { constructor() { this.contacts = []; this.filteredContacts = []; this.allTags = new Set(); this.searchQuery = ''; this.filterTag = ''; this.sortBy = 'created_at'; this.sortOrder = 'desc'; this.initialized = false; } async init() { console.log('[Contacts] init() called, initialized:', this.initialized); if (this.initialized) { await this.loadContacts(); return; } // DOM Elements this.contactsView = $('#view-contacts'); this.contactsGrid = $('#contacts-grid'); this.contactsEmpty = $('#contacts-empty'); this.tagFilter = $('#contacts-tag-filter'); this.sortSelect = $('#contacts-sort'); this.newContactBtn = $('#btn-new-contact'); console.log('[Contacts] DOM Elements check:'); console.log(' contactsView:', this.contactsView); console.log(' newContactBtn:', this.newContactBtn); console.log(' contactsGrid:', this.contactsGrid); // Modal Elements this.contactModal = $('#contact-modal'); this.modalTitle = $('#contact-modal-title'); this.contactForm = $('#contact-form'); console.log('[Contacts] Modal Elements check:'); console.log(' contactModal:', this.contactModal); console.log(' contactForm:', this.contactForm); this.contactIdInput = $('#contact-id'); this.firstNameInput = $('#contact-first-name'); this.lastNameInput = $('#contact-last-name'); this.companyInput = $('#contact-company'); this.positionInput = $('#contact-position'); this.emailInput = $('#contact-email'); this.phoneInput = $('#contact-phone'); this.mobileInput = $('#contact-mobile'); this.addressInput = $('#contact-address'); this.postalCodeInput = $('#contact-postal-code'); this.cityInput = $('#contact-city'); this.countryInput = $('#contact-country'); this.websiteInput = $('#contact-website'); this.notesInput = $('#contact-notes'); this.tagsInput = $('#contact-tags'); this.deleteContactBtn = $('#btn-delete-contact'); this.bindEvents(); this.initialized = true; console.log('[Contacts] Initialization complete'); await this.loadContacts(); // Store subscriptions store.subscribe('contacts', this.handleContactsUpdate.bind(this)); // Window events window.addEventListener('app:refresh', () => this.loadContacts()); window.addEventListener('modal:close', () => this.loadContacts()); } bindEvents() { console.log('[Contacts] bindEvents() called'); // Tag Filter if (this.tagFilter) { this.tagFilter.addEventListener('change', (e) => { this.filterTag = e.target.value; this.filterContacts(); }); } // Sort if (this.sortSelect) { this.sortSelect.addEventListener('change', (e) => { const [sortBy, sortOrder] = e.target.value.split('-'); this.sortBy = sortBy; this.sortOrder = sortOrder; this.sortContacts(); this.renderContacts(); }); } // New Contact Button console.log('[Contacts] newContactBtn element:', this.newContactBtn); if (this.newContactBtn) { console.log('[Contacts] Binding click event to newContactBtn'); this.newContactBtn.addEventListener('click', () => { console.log('[Contacts] New contact button clicked!'); this.showContactModal(); }); } else { console.warn('[Contacts] newContactBtn not found!'); } // Modal Form if (this.contactForm) { this.contactForm.addEventListener('submit', (e) => { e.preventDefault(); this.saveContact(); }); } // Delete Button if (this.deleteContactBtn) { this.deleteContactBtn.addEventListener('click', () => this.deleteContact()); } // Modal Close const modalCloseBtn = this.contactModal?.querySelector('.modal-close'); if (modalCloseBtn) { modalCloseBtn.addEventListener('click', () => this.hideContactModal()); } const modalCancelBtn = this.contactModal?.querySelector('.modal-cancel'); if (modalCancelBtn) { modalCancelBtn.addEventListener('click', () => this.hideContactModal()); } // Socket Events const socket = window.socket; if (socket) { socket.on('contact:created', (data) => { console.log('[Contacts] Socket: contact created', data); this.contacts.unshift(data.contact); this.updateTagsList(); this.filterContacts(); }); socket.on('contact:updated', (data) => { console.log('[Contacts] Socket: contact updated', data); const index = this.contacts.findIndex(c => c.id === data.contact.id); if (index !== -1) { this.contacts[index] = data.contact; this.updateTagsList(); this.filterContacts(); } }); socket.on('contact:deleted', (data) => { console.log('[Contacts] Socket: contact deleted', data); this.contacts = this.contacts.filter(c => c.id !== data.contactId); this.updateTagsList(); this.filterContacts(); }); } } async loadContacts() { try { console.log('[Contacts] Loading contacts...'); const response = await api.getContacts(); this.contacts = response.data || response || []; this.updateTagsList(); this.filterContacts(); console.log(`[Contacts] Loaded ${this.contacts.length} contacts`); } catch (error) { console.error('[Contacts] Error loading contacts:', error); window.dispatchEvent(new CustomEvent('toast:show', { detail: { message: 'Fehler beim Laden der Kontakte', type: 'error' } })); } } filterContacts() { this.filteredContacts = this.contacts.filter(contact => { // Search filter if (this.searchQuery) { const query = this.searchQuery.toLowerCase(); const searchFields = [ contact.firstName, contact.lastName, contact.company, contact.email, contact.position, contact.phone, contact.mobile ].filter(Boolean).join(' ').toLowerCase(); if (!searchFields.includes(query)) { return false; } } // Tag filter if (this.filterTag && contact.tags) { if (!contact.tags.includes(this.filterTag)) { return false; } } return true; }); this.sortContacts(); this.renderContacts(); } sortContacts() { this.filteredContacts.sort((a, b) => { let aVal = a[this.sortBy] || ''; let bVal = b[this.sortBy] || ''; // Handle name sorting if (this.sortBy === 'name') { aVal = `${a.lastName || ''} ${a.firstName || ''}`.trim(); bVal = `${b.lastName || ''} ${b.firstName || ''}`.trim(); } if (typeof aVal === 'string') { aVal = aVal.toLowerCase(); bVal = bVal.toLowerCase(); } if (this.sortOrder === 'asc') { return aVal < bVal ? -1 : aVal > bVal ? 1 : 0; } else { return aVal > bVal ? -1 : aVal < bVal ? 1 : 0; } }); } renderContacts() { if (!this.contactsGrid) return; if (this.filteredContacts.length === 0) { this.contactsGrid.classList.add('hidden'); this.contactsEmpty.classList.remove('hidden'); return; } this.contactsGrid.classList.remove('hidden'); this.contactsEmpty.classList.add('hidden'); const html = this.filteredContacts.map(contact => this.createContactCard(contact)).join(''); this.contactsGrid.innerHTML = html; // Bind card events this.bindCardEvents(); } createContactCard(contact) { const displayName = this.getContactDisplayName(contact); const initials = this.getContactInitials(contact); const tags = contact.tags || []; return `
${initials}

${displayName}

${contact.company ? `
${contact.company}
` : ''} ${contact.position ? `
${contact.position}
` : ''} ${contact.email ? `
${contact.email}
` : ''} ${contact.phone ? `
${contact.phone}
` : ''} ${contact.mobile ? `
${contact.mobile}
` : ''}
${tags.length > 0 ? `
${tags.map(tag => `${tag}`).join('')}
` : ''}
`; } getContactDisplayName(contact) { const parts = []; if (contact.firstName) parts.push(contact.firstName); if (contact.lastName) parts.push(contact.lastName); if (parts.length > 0) { return parts.join(' '); } return contact.company || 'Unbenannt'; } getContactInitials(contact) { let initials = ''; if (contact.firstName) { initials += contact.firstName.charAt(0).toUpperCase(); } if (contact.lastName) { initials += contact.lastName.charAt(0).toUpperCase(); } if (!initials && contact.company) { initials = contact.company.charAt(0).toUpperCase(); } return initials || '?'; } bindCardEvents() { // Edit buttons $$('.btn-edit-contact').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const card = btn.closest('.contact-card'); const contactId = parseInt(card.dataset.contactId); this.editContact(contactId); }); }); // Card click $$('.contact-card').forEach(card => { card.addEventListener('click', () => { const contactId = parseInt(card.dataset.contactId); this.editContact(contactId); }); }); } updateTagsList() { // Collect all unique tags this.allTags.clear(); this.contacts.forEach(contact => { if (contact.tags) { contact.tags.forEach(tag => this.allTags.add(tag)); } }); // Update tag filter dropdown if (this.tagFilter) { const currentValue = this.tagFilter.value; this.tagFilter.innerHTML = ''; Array.from(this.allTags).sort().forEach(tag => { const option = document.createElement('option'); option.value = tag; option.textContent = tag; this.tagFilter.appendChild(option); }); this.tagFilter.value = currentValue; } } showContactModal(contact = null) { console.log('[Contacts] showContactModal called with:', contact); console.log('[Contacts] contactModal element:', this.contactModal); if (!this.contactModal) { console.error('[Contacts] Contact modal not found!'); return; } console.log('[Contacts] Resetting form and showing modal'); // Reset form this.contactForm.reset(); this.contactIdInput.value = ''; if (contact) { // Edit mode this.modalTitle.textContent = 'Kontakt bearbeiten'; this.deleteContactBtn.classList.remove('hidden'); // Fill form this.contactIdInput.value = contact.id; this.firstNameInput.value = contact.firstName || ''; this.lastNameInput.value = contact.lastName || ''; this.companyInput.value = contact.company || ''; this.positionInput.value = contact.position || ''; this.emailInput.value = contact.email || ''; this.phoneInput.value = contact.phone || ''; this.mobileInput.value = contact.mobile || ''; this.addressInput.value = contact.address || ''; this.postalCodeInput.value = contact.postalCode || ''; this.cityInput.value = contact.city || ''; this.countryInput.value = contact.country || ''; this.websiteInput.value = contact.website || ''; this.notesInput.value = contact.notes || ''; this.tagsInput.value = (contact.tags || []).join(', '); } else { // Create mode this.modalTitle.textContent = 'Neuer Kontakt'; this.deleteContactBtn.classList.add('hidden'); } // Show modal overlay const overlay = $('#modal-overlay'); if (overlay) { overlay.classList.remove('hidden'); overlay.classList.add('visible'); } this.contactModal.classList.remove('hidden'); this.contactModal.classList.add('visible'); } hideContactModal() { if (this.contactModal) { this.contactModal.classList.remove('visible'); this.contactModal.classList.add('hidden'); // Hide modal overlay const overlay = $('#modal-overlay'); if (overlay) { overlay.classList.remove('visible'); overlay.classList.add('hidden'); } window.dispatchEvent(new CustomEvent('modal:close')); } } async editContact(contactId) { const contact = this.contacts.find(c => c.id === contactId); if (contact) { this.showContactModal(contact); } } async saveContact() { const contactId = this.contactIdInput.value; const contactData = { firstName: this.firstNameInput.value.trim(), lastName: this.lastNameInput.value.trim(), company: this.companyInput.value.trim(), position: this.positionInput.value.trim(), email: this.emailInput.value.trim(), phone: this.phoneInput.value.trim(), mobile: this.mobileInput.value.trim(), address: this.addressInput.value.trim(), postalCode: this.postalCodeInput.value.trim(), city: this.cityInput.value.trim(), country: this.countryInput.value.trim(), website: this.websiteInput.value.trim(), notes: this.notesInput.value.trim(), tags: this.tagsInput.value.split(',').map(t => t.trim()).filter(Boolean) }; try { if (contactId) { // Update await api.updateContact(contactId, contactData); window.dispatchEvent(new CustomEvent('toast:show', { detail: { message: 'Kontakt aktualisiert', type: 'success' } })); } else { // Create await api.createContact(contactData); window.dispatchEvent(new CustomEvent('toast:show', { detail: { message: 'Kontakt erstellt', type: 'success' } })); } this.hideContactModal(); await this.loadContacts(); } catch (error) { console.error('[Contacts] Error saving contact:', error); const errorMsg = error.response?.data?.errors?.[0] || 'Fehler beim Speichern'; window.dispatchEvent(new CustomEvent('toast:show', { detail: { message: errorMsg, type: 'error' } })); } } async deleteContact() { const contactId = this.contactIdInput.value; if (!contactId) return; const contact = this.contacts.find(c => c.id === parseInt(contactId)); if (!contact) return; const displayName = this.getContactDisplayName(contact); if (!confirm(`Möchten Sie den Kontakt "${displayName}" wirklich löschen?`)) { return; } try { await api.deleteContact(contactId); window.dispatchEvent(new CustomEvent('toast:show', { detail: { message: 'Kontakt gelöscht', type: 'success' } })); this.hideContactModal(); await this.loadContacts(); } catch (error) { console.error('[Contacts] Error deleting contact:', error); window.dispatchEvent(new CustomEvent('toast:show', { detail: { message: 'Fehler beim Löschen', type: 'error' } })); } } handleContactsUpdate(contacts) { this.contacts = contacts; this.updateTagsList(); this.filterContacts(); } } // Singleton instance const contactsManager = new ContactsManager(); // Export instance for external access export { contactsManager }; // Export functions export async function initContacts() { await contactsManager.init(); } export function refreshContacts() { return contactsManager.loadContacts(); }