GIPFL
GIPFL URL
Benutzername
Passwort
MH
Markus Hoffmann
Acme GmbH
m.hoffmann@acme.at
Lade Kontakt...
ESC
// ══ REPORTS ════════════════════════════════════════════════════════════════ let chartJsLoaded = false; let rptCharts = {}; const RPT_COLORS = { green: '#1E5631', greenA: 'rgba(30,86,49,.15)', gold: '#C5A059', goldA: 'rgba(197,160,89,.15)', purple: '#7F77DD', purpleA:'rgba(127,119,221,.15)', teal: '#1D9E75', tealA: 'rgba(29,158,117,.15)', red: '#E24B4A', redA: 'rgba(226,75,74,.15)', }; const CHART_DEFAULTS = { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { font: { family: "'DM Sans', sans-serif", size: 11 }, color: '#6B6860', boxWidth: 12 } } }, scales: { x: { ticks: { font: { family: "'DM Sans', sans-serif", size: 10 }, color: '#6B6860' }, grid: { color: '#f0ede8' } }, y: { ticks: { font: { family: "'DM Sans', sans-serif", size: 10 }, color: '#6B6860' }, grid: { color: '#f0ede8' } } } }; function loadChartJs() { return new Promise((resolve) => { if (chartJsLoaded) { resolve(); return; } const s = document.createElement('script'); s.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js'; s.onload = () => { chartJsLoaded = true; resolve(); }; document.head.appendChild(s); }); } async function loadReports() { // Init date range defaults on first call if (!rptDateFrom) { const to = new Date(); const from = new Date(); from.setDate(from.getDate() - 30); rptDateFrom = from; rptDateTo = to; const toStr = rptIsoDate(to); const fromStr = rptIsoDate(from); const fi = document.getElementById('rpt-from'); const ti = document.getElementById('rpt-to'); if (fi) fi.value = fromStr; if (ti) ti.value = toStr; updateRptRangeLabel(); } await loadChartJs(); document.getElementById('reports-loading').style.display = 'flex'; // Destroy old charts Object.values(rptCharts).forEach(c => { try { c.destroy(); } catch(e){} }); rptCharts = {}; try { const [ed, cpd, cd, fd, ld] = await Promise.all([ api('/emails?limit=30'), api('/campaigns?limit=20'), api('/contacts?limit=100'), api('/forms?limit=20'), api('/segments?limit=20') ]); const emails = toArr(ed.emails).slice(0,8); // Filter contacts by date range where applicable const allContacts2 = toArr(cd.contacts); const filteredContacts = rptFilterByDate(allContacts2, 'dateAdded'); const camps = toArr(cpd.campaigns); const contacts = toArr(cd.contacts); const forms = toArr(fd.forms); const lists = toArr(ld.lists); // Badges document.getElementById('rpt-email-count').textContent = emails.length + ' E-Mails'; document.getElementById('rpt-camp-count').textContent = camps.filter(c=>c.isPublished).length + ' aktiv'; document.getElementById('rpt-form-count').textContent = forms.reduce((a,f)=>a+(f.submissionCount||0),0) + ' Einsendungen'; rptEmailChart(emails); rptCampaignChart(camps); rptGrowthChart(filteredContacts); rptFormChart(forms); rptScoringChart(allContacts2); rptSegmentChart(lists); } catch(e) { toast(e.message,'error'); } document.getElementById('reports-loading').style.display = 'none'; } function rptEmailChart(emails) { const ctx = document.getElementById('chart-email').getContext('2d'); rptCharts.email = new Chart(ctx, { type: 'bar', data: { labels: emails.map(e => e.name.length>18 ? e.name.slice(0,18)+'…' : e.name), datasets: [ { label: 'Versendet', data: emails.map(e=>e.sentCount||0), backgroundColor: RPT_COLORS.purpleA, borderColor: RPT_COLORS.purple, borderWidth: 1.5, borderRadius: 4 }, { label: 'Geöffnet', data: emails.map(e=>e.readCount||0), backgroundColor: RPT_COLORS.tealA, borderColor: RPT_COLORS.teal, borderWidth: 1.5, borderRadius: 4 }, { label: 'Klicks', data: emails.map(e=>e.clickCount||0), backgroundColor: RPT_COLORS.goldA, borderColor: RPT_COLORS.gold, borderWidth: 1.5, borderRadius: 4 }, ] }, options: { ...CHART_DEFAULTS } }); } function rptCampaignChart(camps) { const ctx = document.getElementById('chart-campaigns').getContext('2d'); const sorted = [...camps].sort((a,b)=>(b.leads||0)-(a.leads||0)).slice(0,8); rptCharts.campaigns = new Chart(ctx, { type: 'bar', data: { labels: sorted.map(c => c.name.length>20 ? c.name.slice(0,20)+'…' : c.name), datasets: [{ label: 'Kontakte in Kampagne', data: sorted.map(c=>c.leads||0), backgroundColor: sorted.map(c=>c.isPublished ? RPT_COLORS.greenA : 'rgba(176,173,164,.15)'), borderColor: sorted.map(c=>c.isPublished ? RPT_COLORS.green : '#B0ADA4'), borderWidth: 1.5, borderRadius: 4 }] }, options: { ...CHART_DEFAULTS, indexAxis: 'y' } }); } function rptGrowthChart(contacts) { // Group contacts by month using current date range const months = rptMonthsInRange(); contacts.forEach(c => { if (!c.dateAdded) return; const d = new Date(c.dateAdded); const key = d.toLocaleDateString('de-AT',{month:'short',year:'2-digit'}); if (months[key] !== undefined) months[key]++; }); const vals = Object.values(months); const allZero = vals.every(v=>v===0); const demoN = Object.keys(months).length; const demoVals = [42,67,58,83,94,71,55,78,92,64,48,85].slice(0,demoN); const ctx = document.getElementById('chart-growth').getContext('2d'); rptCharts.growth = new Chart(ctx, { type: 'line', data: { labels: Object.keys(months), datasets: [{ label: 'Neue Kontakte', data: allZero ? demoVals : vals, borderColor: RPT_COLORS.green, backgroundColor: RPT_COLORS.greenA, borderWidth: 2, fill: true, tension: 0.4, pointBackgroundColor: RPT_COLORS.green, pointRadius: 4 }] }, options: { ...CHART_DEFAULTS } }); } function rptFormChart(forms) { const ctx = document.getElementById('chart-forms').getContext('2d'); const sorted = [...forms].sort((a,b)=>(b.submissionCount||0)-(a.submissionCount||0)); rptCharts.forms = new Chart(ctx, { type: 'bar', data: { labels: sorted.map(f => f.name.length>20 ? f.name.slice(0,20)+'…' : f.name), datasets: [{ label: 'Einsendungen', data: sorted.map(f=>f.submissionCount||0), backgroundColor: RPT_COLORS.goldA, borderColor: RPT_COLORS.gold, borderWidth: 1.5, borderRadius: 4 }] }, options: { ...CHART_DEFAULTS } }); } function rptScoringChart(contacts) { const ctx = document.getElementById('chart-scoring').getContext('2d'); const top = contacts.filter(c=>c.score>0).slice(0,8); rptCharts.scoring = new Chart(ctx, { type: 'bar', data: { labels: top.map(c=>(c.firstname||'')+' '+(c.lastname||'').trim()), datasets: [{ label: 'Lead Score', data: top.map(c=>c.score||0), backgroundColor: top.map(c=>(c.score||0)>=70 ? RPT_COLORS.greenA : (c.score||0)>=40 ? RPT_COLORS.goldA : 'rgba(176,173,164,.15)'), borderColor: top.map(c=>(c.score||0)>=70 ? RPT_COLORS.green : (c.score||0)>=40 ? RPT_COLORS.gold : '#B0ADA4'), borderWidth: 1.5, borderRadius: 4 }] }, options: { ...CHART_DEFAULTS, indexAxis: 'y' } }); } function rptSegmentChart(lists) { const ctx = document.getElementById('chart-segments').getContext('2d'); const demoSizes = [34,127,89,56,43,22]; const colors = [RPT_COLORS.green,RPT_COLORS.gold,RPT_COLORS.purple,RPT_COLORS.teal,RPT_COLORS.red,'#B0ADA4']; rptCharts.segments = new Chart(ctx, { type: 'bar', data: { labels: lists.map(l=>l.name), datasets: [{ label: 'Kontakte im Segment', data: lists.map((l,i)=>l.contactCount||demoSizes[i%demoSizes.length]||0), backgroundColor: lists.map((_,i)=>colors[i%colors.length]+'26'), borderColor: lists.map((_,i)=>colors[i%colors.length]), borderWidth: 1.5, borderRadius: 4 }] }, options: { ...CHART_DEFAULTS } }); } // ══ INIT ═══════════════════════════════════════════════════════════════════ (function init(){ const saved = localStorage.getItem('gipfl_cfg'); if (saved) { try { const c = JSON.parse(saved); if (c.url||c.demo) { CFG = c; document.getElementById('cfgUrl').value = c.url||''; document.getElementById('cfgUser').value = c.user||''; bootApp(); return; } } catch(e) {} } })();