(function (root) {
'use strict';
const labels = {onboarding: 'Onboarding', offboarding: 'Offboarding'};
const statuses = {draft: 'Dato gemt · automatik slået fra', scheduled: 'Planlagt', needs_parent: 'Vælg hovedsag', created: 'Sag oprettet', manual_review: 'Kræver manuel behandling', cancelled: 'Annulleret', error: 'Oprettelse fejlede'};
const escape = value => String(value ?? '').replace(/[&<>"']/g, char => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
const date = value => value ? new Intl.DateTimeFormat('da-DK', {timeZone:'Europe/Copenhagen', dateStyle:'medium'}).format(new Date(value.length === 10 ? value + 'T12:00:00Z' : value)) : 'Ikke planlagt';
const schedule = value => value ? new Intl.DateTimeFormat('da-DK', {timeZone:'Europe/Copenhagen', dateStyle:'medium', timeStyle:'short'}).format(new Date(value)) : '';
function templateOptions(templates, customerId, type) {
return templates.filter(item => (['global','internal'].includes(item.template_type) || !item.customer_id || Number(item.customer_id) === Number(customerId)) && (!item.category || item.category === type));
}
function editPayload(payload, created) {
if (!created) return payload;
return {event_date:payload.event_date,lead_days:payload.lead_days,auto_create:payload.auto_create};
}
root.ContactLifecycle = {escape, templateOptions, editPayload};
if (typeof document === 'undefined') return;
document.addEventListener('DOMContentLoaded', () => {
const cards = document.getElementById('contactLifecycleCards');
if (!cards) return;
const contactId = Number(location.pathname.split('/').filter(Boolean).pop());
const base = `/api/v1/contacts/${contactId}/lifecycle`;
let data = {items:[],companies:[],users:[],templates:[]};
let current = null;
let previewVersion = 0;
const feedback = document.getElementById('contactLifecycleFeedback');
async function request(url, method = 'GET', body) {
const headers = {'Content-Type':'application/json'};
const token = localStorage.getItem('access_token');
if (token) headers.Authorization = `Bearer ${token}`;
const response = await fetch(url, {method, headers, credentials:'same-origin', ...(body === undefined ? {} : {body:JSON.stringify(body)})});
const result = await response.json();
if (!response.ok) throw new Error(typeof result.detail === 'string' ? result.detail : 'Kontrollér oplysningerne, og prøv igen.');
return result;
}
function notify(message, danger = false) {
feedback.className = `alert ${danger ? 'alert-danger' : 'alert-success'} py-2`;
feedback.textContent = message;
}
async function load() {
try {
data = await request(base);
cards.innerHTML = Object.entries(labels).map(([type,label]) => {
const items = data.items.filter(item => item.event_type === type);
const live = items.filter(item => item.status !== 'cancelled');
return `
${label}
${live.length ? live.map(item => `
${escape(date(item.event_date))}
${escape((data.companies.find(company => Number(company.id) === Number(item.customer_id)) || {}).name || '')}
${escape(statuses[item.status] || item.status)}
${item.auto_create && item.scheduled_at && !item.case_id ? `
Oprettelse: ${escape(schedule(item.scheduled_at))}
` : ''}${item.last_error ? `
${escape(item.last_error)}
` : ''}
`).join('') : '
Ingen dato planlagt
'}${items.some(item => item.status === 'cancelled') ? `
${items.filter(item => item.status === 'cancelled').length} annulleret · eksisterende sager er bevaret
` : ''}
`;
}).join('');
if (data.can_edit === false) cards.querySelectorAll('[data-lifecycle-new],[data-lifecycle-edit],[data-lifecycle-cancel]').forEach(button => button.remove());
} catch (error) {
cards.innerHTML = '';
notify(`Forløbene kunne ikke hentes: ${error.message}`, true);
}
}
cards.addEventListener('click', async event => {
const button = event.target.closest('button');
if (!button) return;
if (button.hasAttribute('data-lifecycle-reload')) return load();
if (button.dataset.lifecycleNew) return open(button.dataset.lifecycleNew);
if (button.dataset.lifecycleEdit) {
const item = data.items.find(item => Number(item.id) === Number(button.dataset.lifecycleEdit));
if (item) open(item.event_type, item);
}
if (button.dataset.lifecycleCancel) {
const item = data.items.find(item => Number(item.id) === Number(button.dataset.lifecycleCancel));
if (!item || !confirm(`Annuller ${labels[item.event_type].toLowerCase()} den ${date(item.event_date)}? Eksisterende sager bevares.`)) return;
button.disabled = true;
try { await request(`${base}/${item.id}/cancel`, 'POST'); await load(); notify('Forløbet er annulleret. Eksisterende sager er bevaret.'); }
catch (error) { notify(error.message, true); button.disabled = false; }
}
});
function open(type, item = null) {
if (data.can_edit === false) return;
current = item;
previewVersion++;
document.getElementById('contactLifecycleModal')?.remove();
const element = document.createElement('div');
element.id = 'contactLifecycleModal';
element.className = 'modal fade';
element.tabIndex = -1;
element.setAttribute('aria-labelledby','contactLifecycleModalTitle');
element.innerHTML = ``;
document.body.append(element);
const form = element.querySelector('form');
const field = name => form.elements.namedItem(name);
field('event_date').value = item?.event_date || '';
field('customer_id').value = item?.customer_id || (data.companies.length === 1 ? data.companies[0].id : '');
field('lead_days').value = item?.lead_days ?? 14;
field('auto_create').checked = Boolean(item?.auto_create);
field('responsible_user_id').value = item?.responsible_user_id || '';
if (item?.case_id) ['customer_id','template_id','parent_case_id','responsible_user_id'].forEach(name => { field(name).disabled = true; });
let templateId = item?.template_id || '';
function templates() {
const available = templateOptions(data.templates, field('customer_id').value, type);
field('template_id').innerHTML = '' + available.map(template => ``).join('');
field('template_id').value = available.some(template => Number(template.id) === Number(templateId)) ? templateId : '';
}
async function preview() {
const version = ++previewVersion;
const container = element.querySelector('#lifecyclePreview');
element.querySelector('#lifecycleAutoFields').classList.toggle('d-none', !field('auto_create').checked);
const customer = field('customer_id').value;
const eventDate = field('event_date').value;
const days = Number(field('lead_days').value);
if (!customer || !eventDate || !Number.isInteger(days) || days < 0) { container.textContent = 'Vælg dato og kunde.'; return; }
if (current?.case_id) {
field('parent_case_id').innerHTML = ``;
element.querySelector('#lifecycleParentHelp').textContent = 'Sagens tilknytninger bevares.';
container.textContent = `Sag #${current.case_id} bevares. Deadline ændres til ${date(eventDate)}. Afsluttede sager kræver manuel behandling.`;
return;
}
const selectedParent = field('parent_case_id').value || current?.parent_case_id;
container.textContent = 'Henter forslag…';
try {
const query = new URLSearchParams({customer_id:customer,event_type:type,event_date:eventDate,lead_days:String(days)});
const result = await request(`${base}/preview?${query}`);
if (version !== previewVersion || !element.isConnected) return;
const candidates = result.candidates || [];
field('parent_case_id').innerHTML = `` + candidates.map(candidate => ``).join('');
field('parent_case_id').value = candidates.some(candidate => Number(candidate.id) === Number(selectedParent)) ? selectedParent : (result.parent_case_id || '');
element.querySelector('#lifecycleParentHelp').textContent = candidates.length > 1 ? 'Flere relevante sager: vælg hovedsag. Uden valg afventer automatikken.' : candidates.length === 1 ? 'Der oprettes en undersag til denne åbne sag.' : 'Ingen relevant åben sag. Der oprettes en selvstændig sag.';
container.textContent = current?.case_id ? `Sag #${current.case_id} bevares. Deadline ændres til ${date(eventDate)}. Afsluttede sager kræver manuel behandling.` : field('auto_create').checked ? `Sagen oprettes ${schedule(result.scheduled_at)} med deadline ${date(eventDate)}.${candidates.length > 1 && !field('parent_case_id').value ? ' Afventer valg af hovedsag.' : ''}` : `Datoen ${date(eventDate)} gemmes. Ingen automatisk sagsoprettelse.`;
} catch (error) { if (version === previewVersion) container.textContent = `Forslag kunne ikke hentes: ${error.message}`; }
}
templates();
form.addEventListener('change', event => {
if (event.target.name === 'customer_id') templates();
if (event.target.name === 'template_id') templateId = event.target.value;
if (!['template_id','responsible_user_id'].includes(event.target.name)) preview();
});
form.addEventListener('click', event => {
const button = event.target.closest('[data-lead]');
if (button) { field('lead_days').value = button.dataset.lead; preview(); }
});
form.addEventListener('submit', async event => {
event.preventDefault();
if (!form.reportValidity()) return;
const button = element.querySelector('#lifecycleSave');
const errorBox = element.querySelector('#lifecycleError');
button.disabled = true;
errorBox.classList.add('d-none');
const payload = {customer_id:Number(field('customer_id').value),event_date:field('event_date').value,lead_days:Number(field('lead_days').value),auto_create:field('auto_create').checked,template_id:Number(field('template_id').value)||null,responsible_user_id:Number(field('responsible_user_id').value)||null,parent_case_id:Number(field('parent_case_id').value)||null};
if (!current) payload.event_type = type;
try {
const saved = await request(current ? `${base}/${current.id}` : base, current ? 'PATCH' : 'POST', editPayload(payload, Boolean(current?.case_id)));
modal.hide();
await load();
notify(saved.status === 'needs_parent' ? 'Forløbet er gemt. Vælg en hovedsag for at aktivere sagsoprettelsen.' : 'Forløbet er gemt.');
} catch (error) { errorBox.textContent = error.message; errorBox.classList.remove('d-none'); button.disabled = false; }
});
const modal = new bootstrap.Modal(element);
element.addEventListener('shown.bs.modal', () => field('event_date').focus(), {once:true});
element.addEventListener('hidden.bs.modal', () => { previewVersion++; modal.dispose(); element.remove(); }, {once:true});
modal.show();
preview();
}
load();
});
})(typeof window === 'undefined' ? globalThis : window);