/**
* Modul: Kontakte (Contacts)
* Zweck: Kontaktliste mit Kategorie-Filter, Suche, CRUD, tel:/mailto:/maps-Links
* Abhängigkeiten: /api.js, /router.js (window.oikos)
*/
import { api } from '/api.js';
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
import { stagger, vibrate } from '/utils/ux.js';
import { t } from '/i18n.js';
import { esc } from '/utils/html.js';
// --------------------------------------------------------
// Konstanten
// --------------------------------------------------------
const CATEGORIES = ['Arzt', 'Schule/Kita', 'Behörde', 'Versicherung',
'Handwerker', 'Notfall', 'Sonstiges'];
const CATEGORY_ICONS = {
'Arzt': '🏥',
'Schule/Kita': '🏫',
'Behörde': '🏛️',
'Versicherung': '🛡️',
'Handwerker': '🔧',
'Notfall': '🚨',
'Sonstiges': '📋',
};
function CATEGORY_LABELS() {
return {
'Arzt': t('contacts.categoryDoctor'),
'Schule/Kita': t('contacts.categorySchool'),
'Behörde': t('contacts.categoryAuthority'),
'Versicherung': t('contacts.categoryInsurance'),
'Handwerker': t('contacts.categoryCraftsman'),
'Notfall': t('contacts.categoryEmergency'),
'Sonstiges': t('contacts.categoryOther'),
};
}
// --------------------------------------------------------
// State
// --------------------------------------------------------
let state = {
contacts: [],
activeCategory: null,
searchQuery: '',
};
let _container = null;
// --------------------------------------------------------
// Entry Point
// --------------------------------------------------------
export async function render(container, { user }) {
_container = container;
container.innerHTML = `
${t('contacts.title')}
${CATEGORIES.map((c) => `
`).join('')}
`;
if (window.lucide) lucide.createIcons();
const res = await api.get('/contacts');
state.contacts = res.data;
renderList();
// Suche
let searchTimer;
_container.querySelector('#contacts-search').addEventListener('input', (e) => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
state.searchQuery = e.target.value.trim();
renderList();
}, 200);
});
// Kategorie-Filter
_container.querySelector('#contacts-filters').addEventListener('click', (e) => {
const chip = e.target.closest('[data-cat]');
if (!chip) return;
_container.querySelectorAll('.contact-filter-chip').forEach((c) =>
c.classList.toggle('contact-filter-chip--active', c === chip)
);
state.activeCategory = chip.dataset.cat || null;
renderList();
});
// Neu
const addHandler = () => openContactModal({ mode: 'create' });
_container.querySelector('#contacts-add-btn').addEventListener('click', addHandler);
_container.querySelector('#fab-new-contact').addEventListener('click', addHandler);
// vCard-Import
_container.querySelector('#contacts-import-input').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
e.target.value = '';
try {
const text = await file.text();
const contact = parseVCard(text);
if (!contact.name) { window.oikos?.showToast(t('contacts.vcardNoName'), 'warning'); return; }
const res = await api.post('/contacts', contact);
state.contacts.push(res.data);
renderList();
window.oikos?.showToast(t('contacts.importedToast', { name: res.data.name }), 'success');
} catch (err) {
window.oikos?.showToast(t('contacts.importError', { error: err.message }), 'danger');
}
});
}
// --------------------------------------------------------
// Liste rendern
// --------------------------------------------------------
function filterContacts() {
let list = state.contacts;
if (state.activeCategory) {
list = list.filter((c) => c.category === state.activeCategory);
}
if (state.searchQuery) {
const q = state.searchQuery.toLowerCase();
list = list.filter((c) =>
c.name.toLowerCase().includes(q) ||
(c.phone && c.phone.toLowerCase().includes(q)) ||
(c.email && c.email.toLowerCase().includes(q))
);
}
return list;
}
function renderList() {
const container = _container.querySelector('#contacts-list');
if (!container) return;
const contacts = filterContacts();
if (!contacts.length) {
container.innerHTML = `
${t('contacts.emptyTitle')}
${t('contacts.emptyDescription')}
${t('emptyHint.contacts')}
`;
if (window.lucide) lucide.createIcons();
return;
}
// Nach Kategorie gruppieren
const groups = {};
for (const c of contacts) {
if (!groups[c.category]) groups[c.category] = [];
groups[c.category].push(c);
}
container.innerHTML = Object.entries(groups)
.sort(([a], [b]) => CATEGORIES.indexOf(a) - CATEGORIES.indexOf(b))
.map(([cat, items]) => `