835 Zeilen
26 KiB
JavaScript
835 Zeilen
26 KiB
JavaScript
/**
|
|
* TASKMATE - Contacts Manager
|
|
* ===========================
|
|
* Kontaktverwaltung mit Tabellenansicht
|
|
*/
|
|
|
|
import api from './api.js';
|
|
import { $, $$, formatDate, debounce } from './utils.js';
|
|
import store from './store.js';
|
|
|
|
class ContactsManager {
|
|
constructor() {
|
|
this.contacts = [];
|
|
this.filteredContacts = [];
|
|
this.selectedContacts = new Set();
|
|
this.allTags = new Set();
|
|
this.searchQuery = '';
|
|
this.filterTag = '';
|
|
this.sortBy = 'created_at';
|
|
this.sortOrder = 'desc';
|
|
this.currentPage = 1;
|
|
this.itemsPerPage = 25;
|
|
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.contactsTable = $('#contacts-table');
|
|
this.contactsTbody = $('#contacts-tbody');
|
|
this.contactsEmpty = $('#contacts-empty');
|
|
this.tagFilter = $('#contacts-tag-filter');
|
|
this.selectAllCheckbox = $('#select-all-contacts');
|
|
this.bulkActions = $('#bulk-actions');
|
|
this.selectedCountSpan = $('#selected-count');
|
|
this.contactsCountSpan = $('#contacts-count');
|
|
this.newContactBtn = $('#btn-new-contact');
|
|
this.exportBtn = $('#btn-export-contacts');
|
|
this.bulkDeleteBtn = $('#btn-bulk-delete');
|
|
this.deselectAllBtn = $('#btn-deselect-all');
|
|
|
|
// Pagination
|
|
this.pagination = $('#contacts-pagination');
|
|
this.currentPageSpan = $('#current-page');
|
|
this.totalPagesSpan = $('#total-pages');
|
|
this.prevPageBtn = $('#btn-prev-page');
|
|
this.nextPageBtn = $('#btn-next-page');
|
|
|
|
console.log('[Contacts] DOM Elements check:');
|
|
console.log(' contactsView:', this.contactsView);
|
|
console.log(' newContactBtn:', this.newContactBtn);
|
|
console.log(' contactsTable:', this.contactsTable);
|
|
|
|
// 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.currentPage = 1;
|
|
this.filterContacts();
|
|
});
|
|
}
|
|
|
|
// Table sorting
|
|
$$('.sortable').forEach(th => {
|
|
th.addEventListener('click', (e) => {
|
|
const sortField = th.dataset.sort;
|
|
if (this.sortBy === sortField) {
|
|
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
this.sortBy = sortField;
|
|
this.sortOrder = 'asc';
|
|
}
|
|
|
|
// Update UI
|
|
$$('.sortable').forEach(el => el.classList.remove('sort-asc', 'sort-desc'));
|
|
th.classList.add(this.sortOrder === 'asc' ? 'sort-asc' : 'sort-desc');
|
|
|
|
this.sortContacts();
|
|
this.renderContacts();
|
|
});
|
|
});
|
|
|
|
// Select all checkbox
|
|
if (this.selectAllCheckbox) {
|
|
this.selectAllCheckbox.addEventListener('change', (e) => {
|
|
const checked = e.target.checked;
|
|
const visibleContacts = this.getPaginatedContacts();
|
|
|
|
if (checked) {
|
|
visibleContacts.forEach(contact => this.selectedContacts.add(contact.id));
|
|
} else {
|
|
visibleContacts.forEach(contact => this.selectedContacts.delete(contact.id));
|
|
}
|
|
|
|
this.updateBulkActions();
|
|
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!');
|
|
}
|
|
|
|
// Export Button
|
|
if (this.exportBtn) {
|
|
this.exportBtn.addEventListener('click', () => this.exportContacts());
|
|
}
|
|
|
|
// Bulk Delete Button
|
|
if (this.bulkDeleteBtn) {
|
|
this.bulkDeleteBtn.addEventListener('click', () => this.bulkDelete());
|
|
}
|
|
|
|
// Deselect All Button
|
|
if (this.deselectAllBtn) {
|
|
this.deselectAllBtn.addEventListener('click', () => {
|
|
this.selectedContacts.clear();
|
|
this.selectAllCheckbox.checked = false;
|
|
this.updateBulkActions();
|
|
this.renderContacts();
|
|
});
|
|
}
|
|
|
|
// Pagination buttons
|
|
if (this.prevPageBtn) {
|
|
this.prevPageBtn.addEventListener('click', () => {
|
|
if (this.currentPage > 1) {
|
|
this.currentPage--;
|
|
this.renderContacts();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (this.nextPageBtn) {
|
|
this.nextPageBtn.addEventListener('click', () => {
|
|
const totalPages = Math.ceil(this.filteredContacts.length / this.itemsPerPage);
|
|
if (this.currentPage < totalPages) {
|
|
this.currentPage++;
|
|
this.renderContacts();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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.selectedContacts.delete(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.updateContactsCount();
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
|
|
getPaginatedContacts() {
|
|
const start = (this.currentPage - 1) * this.itemsPerPage;
|
|
const end = start + this.itemsPerPage;
|
|
return this.filteredContacts.slice(start, end);
|
|
}
|
|
|
|
renderContacts() {
|
|
if (!this.contactsTbody) return;
|
|
|
|
if (this.filteredContacts.length === 0) {
|
|
this.contactsTable.parentElement.classList.add('hidden');
|
|
this.contactsEmpty.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
this.contactsTable.parentElement.classList.remove('hidden');
|
|
this.contactsEmpty.classList.add('hidden');
|
|
|
|
const paginatedContacts = this.getPaginatedContacts();
|
|
const html = paginatedContacts.map(contact => this.createContactRow(contact)).join('');
|
|
this.contactsTbody.innerHTML = html;
|
|
|
|
// Update pagination
|
|
this.updatePagination();
|
|
|
|
// Bind row events
|
|
this.bindRowEvents();
|
|
}
|
|
|
|
createContactRow(contact) {
|
|
const displayName = this.getContactDisplayName(contact);
|
|
const initials = this.getContactInitials(contact);
|
|
const tags = contact.tags || [];
|
|
const isSelected = this.selectedContacts.has(contact.id);
|
|
|
|
return `
|
|
<tr data-contact-id="${contact.id}" ${isSelected ? 'class="selected"' : ''}>
|
|
<td class="checkbox-cell">
|
|
<input type="checkbox" class="table-checkbox contact-checkbox" data-contact-id="${contact.id}" ${isSelected ? 'checked' : ''}>
|
|
</td>
|
|
<td>
|
|
<div class="name-cell">
|
|
<div class="contact-avatar-small">${initials}</div>
|
|
<a href="#" class="contact-name-link" data-contact-id="${contact.id}">${displayName}</a>
|
|
</div>
|
|
</td>
|
|
<td>${contact.company || '-'}</td>
|
|
<td class="hide-mobile">${contact.position || '-'}</td>
|
|
<td>${contact.email ? `<a href="mailto:${contact.email}">${contact.email}</a>` : '-'}</td>
|
|
<td class="hide-mobile">${contact.phone || '-'}</td>
|
|
<td>
|
|
<div class="tags-cell">
|
|
${tags.length > 0 ? tags.map(tag => `<span class="contact-tag">${tag}</span>`).join('') : '-'}
|
|
</div>
|
|
</td>
|
|
<td class="actions-cell">
|
|
<div class="table-actions">
|
|
<button class="btn-table-action btn-edit-contact" data-contact-id="${contact.id}" title="Bearbeiten">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
</svg>
|
|
</button>
|
|
<button class="btn-table-action btn-delete-contact-inline" data-contact-id="${contact.id}" title="Löschen">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v12a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14M10 11v6M14 11v6"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
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 || '?';
|
|
}
|
|
|
|
bindRowEvents() {
|
|
// Contact checkboxes
|
|
$$('.contact-checkbox').forEach(checkbox => {
|
|
checkbox.addEventListener('change', (e) => {
|
|
const contactId = parseInt(e.target.dataset.contactId);
|
|
if (e.target.checked) {
|
|
this.selectedContacts.add(contactId);
|
|
} else {
|
|
this.selectedContacts.delete(contactId);
|
|
}
|
|
this.updateBulkActions();
|
|
this.updateRowSelection(contactId, e.target.checked);
|
|
});
|
|
});
|
|
|
|
// Edit buttons
|
|
$$('.btn-edit-contact').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const contactId = parseInt(btn.dataset.contactId);
|
|
this.editContact(contactId);
|
|
});
|
|
});
|
|
|
|
// Delete inline buttons
|
|
$$('.btn-delete-contact-inline').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const contactId = parseInt(btn.dataset.contactId);
|
|
this.deleteContactInline(contactId);
|
|
});
|
|
});
|
|
|
|
// Name links
|
|
$$('.contact-name-link').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const contactId = parseInt(link.dataset.contactId);
|
|
this.editContact(contactId);
|
|
});
|
|
});
|
|
}
|
|
|
|
updateRowSelection(contactId, selected) {
|
|
const row = $(`tr[data-contact-id="${contactId}"]`);
|
|
if (row) {
|
|
if (selected) {
|
|
row.classList.add('selected');
|
|
} else {
|
|
row.classList.remove('selected');
|
|
}
|
|
}
|
|
}
|
|
|
|
updateBulkActions() {
|
|
const count = this.selectedContacts.size;
|
|
if (count > 0) {
|
|
this.bulkActions.classList.remove('hidden');
|
|
this.selectedCountSpan.textContent = count;
|
|
} else {
|
|
this.bulkActions.classList.add('hidden');
|
|
}
|
|
|
|
// Update select all checkbox state
|
|
const visibleContacts = this.getPaginatedContacts();
|
|
const allSelected = visibleContacts.length > 0 &&
|
|
visibleContacts.every(contact => this.selectedContacts.has(contact.id));
|
|
this.selectAllCheckbox.checked = allSelected;
|
|
}
|
|
|
|
updateContactsCount() {
|
|
const count = this.filteredContacts.length;
|
|
this.contactsCountSpan.textContent = `${count} ${count === 1 ? 'Kontakt' : 'Kontakte'}`;
|
|
}
|
|
|
|
updatePagination() {
|
|
const totalPages = Math.ceil(this.filteredContacts.length / this.itemsPerPage);
|
|
|
|
if (totalPages <= 1) {
|
|
this.pagination.classList.add('hidden');
|
|
return;
|
|
}
|
|
|
|
this.pagination.classList.remove('hidden');
|
|
this.currentPageSpan.textContent = this.currentPage;
|
|
this.totalPagesSpan.textContent = totalPages;
|
|
|
|
this.prevPageBtn.disabled = this.currentPage === 1;
|
|
this.nextPageBtn.disabled = this.currentPage === totalPages;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
async bulkDelete() {
|
|
const count = this.selectedContacts.size;
|
|
if (count === 0) return;
|
|
|
|
const contactNames = Array.from(this.selectedContacts).map(id => {
|
|
const contact = this.contacts.find(c => c.id === id);
|
|
return contact ? this.getContactDisplayName(contact) : '';
|
|
}).filter(Boolean);
|
|
|
|
const message = count === 1
|
|
? `Möchten Sie den Kontakt "${contactNames[0]}" wirklich löschen?`
|
|
: `Möchten Sie ${count} Kontakte wirklich löschen?`;
|
|
|
|
if (!confirm(message)) return;
|
|
|
|
try {
|
|
// Delete contacts one by one
|
|
for (const contactId of this.selectedContacts) {
|
|
await api.deleteContact(contactId);
|
|
}
|
|
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: { message: `${count} ${count === 1 ? 'Kontakt' : 'Kontakte'} gelöscht`, type: 'success' }
|
|
}));
|
|
|
|
this.selectedContacts.clear();
|
|
await this.loadContacts();
|
|
} catch (error) {
|
|
console.error('[Contacts] Error during bulk delete:', error);
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: { message: 'Fehler beim Löschen der Kontakte', type: 'error' }
|
|
}));
|
|
}
|
|
}
|
|
|
|
async deleteContactInline(contactId) {
|
|
const contact = this.contacts.find(c => c.id === 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' }
|
|
}));
|
|
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' }
|
|
}));
|
|
}
|
|
}
|
|
|
|
async exportContacts() {
|
|
try {
|
|
let contactsToExport = this.filteredContacts;
|
|
|
|
// Create CSV content
|
|
const headers = ['Vorname', 'Nachname', 'Firma', 'Position', 'E-Mail', 'Telefon', 'Mobil', 'Adresse', 'PLZ', 'Stadt', 'Land', 'Website', 'Tags', 'Notizen'];
|
|
const rows = [headers];
|
|
|
|
contactsToExport.forEach(contact => {
|
|
const row = [
|
|
contact.firstName || '',
|
|
contact.lastName || '',
|
|
contact.company || '',
|
|
contact.position || '',
|
|
contact.email || '',
|
|
contact.phone || '',
|
|
contact.mobile || '',
|
|
contact.address || '',
|
|
contact.postalCode || '',
|
|
contact.city || '',
|
|
contact.country || '',
|
|
contact.website || '',
|
|
(contact.tags || []).join(', '),
|
|
contact.notes || ''
|
|
];
|
|
rows.push(row);
|
|
});
|
|
|
|
// Convert to CSV
|
|
const csvContent = rows.map(row =>
|
|
row.map(cell => `"${cell.replace(/"/g, '""')}"`).join(',')
|
|
).join('\n');
|
|
|
|
// Create blob and download
|
|
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `kontakte_${new Date().toISOString().split('T')[0]}.csv`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(url);
|
|
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: { message: `${contactsToExport.length} Kontakte exportiert`, type: 'success' }
|
|
}));
|
|
} catch (error) {
|
|
console.error('[Contacts] Error exporting contacts:', error);
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: { message: 'Fehler beim Exportieren', type: 'error' }
|
|
}));
|
|
}
|
|
}
|
|
|
|
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();
|
|
} |