543 Zeilen
16 KiB
JavaScript
543 Zeilen
16 KiB
JavaScript
/**
|
|
* 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 `
|
|
<div class="contact-card" data-contact-id="${contact.id}">
|
|
<div class="contact-card-header">
|
|
<div class="contact-avatar">
|
|
${initials}
|
|
</div>
|
|
<div class="contact-actions">
|
|
<button class="btn-icon btn-edit-contact" title="Bearbeiten">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="contact-card-body">
|
|
<h3 class="contact-name">${displayName}</h3>
|
|
${contact.company ? `<div class="contact-company">${contact.company}</div>` : ''}
|
|
${contact.position ? `<div class="contact-position">${contact.position}</div>` : ''}
|
|
${contact.email ? `<div class="contact-email"><i class="fas fa-envelope"></i> ${contact.email}</div>` : ''}
|
|
${contact.phone ? `<div class="contact-phone"><i class="fas fa-phone"></i> ${contact.phone}</div>` : ''}
|
|
${contact.mobile ? `<div class="contact-mobile"><i class="fas fa-mobile"></i> ${contact.mobile}</div>` : ''}
|
|
</div>
|
|
${tags.length > 0 ? `
|
|
<div class="contact-tags">
|
|
${tags.map(tag => `<span class="contact-tag">${tag}</span>`).join('')}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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 = '<option value="">Alle Tags</option>';
|
|
|
|
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();
|
|
} |