Upload index.html
Browse files- index.html +52 -16
index.html
CHANGED
|
@@ -140,6 +140,24 @@
|
|
| 140 |
#reportSheet .report-analysis p { margin-bottom:5px!important; }
|
| 141 |
#reportSheet .report-foot { margin-top:6px!important; padding-top:5px!important; font-size:8px!important; }
|
| 142 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
</style></head>
|
| 144 |
<body>
|
| 145 |
<div class="app">
|
|
@@ -167,6 +185,15 @@
|
|
| 167 |
<div class="content-grid">
|
| 168 |
<div class="left-content">
|
| 169 |
<div class="kpi-grid" id="kpis"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
<div class="charts-grid"><section class="panel chart-panel"><div class="panel-heading"><div><span class="section-kicker">TRENDANALYSE</span><h3>Bezoeken per locatie</h3></div><span class="subtle">Jul – Sep 2026</span></div><canvas id="chart" width="640" height="250" aria-label="Grafiek fictieve bezoeken per locatie"></canvas><div class="legend"><span><i style="background:#3ee1b6"></i> Soeda</span><span><i style="background:#ffc66a"></i> Rietdijk</span><span><i style="background:#5b9cff"></i> Vredehof</span><span><i style="background:#ad88ff"></i> Gouda</span></div></section>
|
| 171 |
<section class="panel verification"><div class="panel-heading"><div><span class="section-kicker">KWALITEITSBORGING</span><h3>Verificatie Businesscontroller</h3></div><span class="warn-label">DEMO</span></div><div id="checks" class="checks"></div><div class="warning">⚠ 1 fictieve afwijking. Rapport niet gevalideerd.</div></section></div>
|
| 172 |
<section class="panel logpanel"><div class="panel-heading"><div><span class="section-kicker">REAL-TIME PROCESS</span><h3>Agent activity log</h3></div><span class="subtle" id="logStatus">Stand-by</span></div><div class="logs" id="logs"><div class="logitem muted">◌ Klik op ‘Start demo-analyse’ om de workflow te bekijken.</div></div></section>
|
|
@@ -178,7 +205,7 @@
|
|
| 178 |
</main>
|
| 179 |
</div>
|
| 180 |
<div id="reportOverlay" role="dialog" aria-modal="true" aria-label="Reakt Monitor rapport" aria-hidden="true">
|
| 181 |
-
<div class="report-actions"><span><strong>REAKT · Rapportvoorvertoning v0.9</strong> — uitsluitend fictieve testgegevens</span><div><button id="reportClose" type="button">✕ Sluiten</button> <button id="reportPng" class="report-png" type="button">⬇ Download PNG</button> <button id="reportJpg" class="report-jpg" type="button">⬇ Download JPG</button> <button id="reportPrint" class="report-print" type="button">↓ Opslaan als PDF / Afdrukken</button></div></div>
|
| 182 |
<section id="reportSheet" aria-label="Witte Inloop Monitor"></section>
|
| 183 |
</div>
|
| 184 |
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
@@ -388,13 +415,13 @@ async function readExcelLocal(file){
|
|
| 388 |
unmatchedPasses:unmatchedPasses.size,linkedByPass,rowsWithPass};
|
| 389 |
}
|
| 390 |
function calculateImported(site='*'){
|
| 391 |
-
const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),monthSite=new Map();
|
| 392 |
const passByClient=new Map();let missingPass=0;
|
| 393 |
for(const r of all){
|
| 394 |
if(r.pass){if(!passByClient.has(r.id))passByClient.set(r.id,new Set());passByClient.get(r.id).add(r.pass)}else missingPass++;
|
| 395 |
const k=r.site+'|'+r.date+'|'+r.id;
|
| 396 |
if(keyset.has(k))continue;
|
| 397 |
-
keyset.add(k);ids.add(r.id);
|
| 398 |
const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
|
| 399 |
}
|
| 400 |
let multiPassClients=0;
|
|
@@ -402,7 +429,7 @@ function calculateImported(site='*'){
|
|
| 402 |
else multiPassClients=[...passByClient.values()].filter(p=>p.size>1).length;
|
| 403 |
const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
|
| 404 |
const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
|
| 405 |
-
return {site,rows:all.length,visits:keyset.size,unique:
|
| 406 |
invalid:importedData.invalid,passChecked:importedData.hasPassColumn||importedData.passSheetFound,
|
| 407 |
passSheetFound:importedData.passSheetFound,pasDates:importedData.pasDates,peildatum:importedData.peildatum,multiPassClients,passConflicts:importedData.passConflicts,
|
| 408 |
identityConflicts:importedData.identityConflicts,unmatchedPasses:importedData.unmatchedPasses,
|
|
@@ -428,11 +455,11 @@ function drawImportedChart(result){
|
|
| 428 |
function renderImported(result){
|
| 429 |
const num=n=>n.toLocaleString('nl-NL');
|
| 430 |
const kpis=[{label:'Bezoeken (voorlopig)',value:num(result.visits),change:'Unieke cliënt / dag / locatie'},
|
| 431 |
-
{label:'Unieke
|
| 432 |
{label:'Scanregels',value:num(result.rows),change:`${num(result.duplicates)} herhaalde scans`},
|
| 433 |
{label:'Verificatie',value:'—',change:'Extern niet uitgevoerd'}];
|
| 434 |
renderKPIs({kpis});drawImportedChart(result);
|
| 435 |
-
const lines=[['Herhaalde scans',num(result.duplicates)],['Unieke
|
| 436 |
['Scanpassen: apart werkblad',result.passSheetFound?num(result.passRows)+' registraties':'NIET AANWEZIG'],
|
| 437 |
['Cliënten met meerdere passen',result.passChecked?num(result.multiPassClients):'NIET TE CONTROLEREN'],
|
| 438 |
['Scanregels via pas gekoppeld',num(result.linkedByPass)],
|
|
@@ -462,7 +489,7 @@ async function startImported(){
|
|
| 462 |
const result=calculateImported($('siteSelect').value);importedData.last=result;
|
| 463 |
const steps=[
|
| 464 |
{agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans; Scanpassen: ${result.passSheetFound?result.passRows+' registraties':'geen apart werkblad'}`},
|
| 465 |
-
{agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke
|
| 466 |
{agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
|
| 467 |
{agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
|
| 468 |
{agent:'report',text:'Conceptpreview bijgewerkt · NIET GEVALIDEERD'}];
|
|
@@ -496,7 +523,7 @@ reply=function(q){
|
|
| 496 |
const r=importedData.last;
|
| 497 |
if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
|
| 498 |
if(/valid|controle|wouter|business|betrouw|verschil|akkoord/.test(q.toLowerCase()))return 'Deze Excel-proef is NIET extern gevalideerd. De schaduwberekening van de businesscontroller ontbreekt en de rekenregels moeten nog worden afgestemd.';
|
| 499 |
-
if(/bezoek|uniek|scan|groei|locatie|samenvat|rapport|analyse|kwartaal|cijfer/.test(q.toLowerCase()))return `Testbestand (${r.site==='*'?'alle locaties':r.site}): ${r.rows} scanregels, ${r.visits} voorlopige bezoeken na ${r.duplicates} herhaalde scans, en ${r.unique} unieke
|
| 500 |
return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
|
| 501 |
};
|
| 502 |
|
|
@@ -549,10 +576,10 @@ function buildWhiteReport(){
|
|
| 549 |
const summary=[`In de geselecteerde periode registreerde de proefmonitor ${reportNL(d.unique)} unieke cliëntnummers en ${reportNL(d.visits)} unieke bezoekdagen voor ${escapeHTML(d.site)}.`,
|
| 550 |
`De gemiddelde bezoekintensiteit bedraagt ${avg} bezoekdagen per cliëntnummer; de mediaan is ${String(d.median).replace('.',',')}.`,
|
| 551 |
`De meeste bezoekdagen vallen op ${busiest} (${reportNL(weekdayMax)}) en de minste op ${quietest} (${reportNL(weekdayMin)}).`,
|
| 552 |
-
`Nieuwe instroom en actieve
|
| 553 |
-
const kpis=[{klass:'purple',icon:'♟',label:'Unieke
|
| 554 |
-
{klass:'green',icon:'+',label:'Nieuwe instroom',value:'—',sub:'
|
| 555 |
-
{klass:'purple',icon:'♙',label:'Actieve
|
| 556 |
{klass:'orange',icon:'▦',label:'Unieke bezoekdagen',value:reportNL(d.visits),sub:'Eén cliëntnummer per locatie per kalenderdag'}];
|
| 557 |
const kpiHTML=kpis.map(k=>`<div class="report-kpi ${k.klass}"><div class="k-icon">${k.icon}</div><div><div class="k-label">${k.label}</div><div class="k-num">${k.value}</div><div class="k-sub">${k.sub}</div></div><div class="k-underline"></div></div>`).join('');
|
| 558 |
const maxI=Math.max(1,...d.intensities),ilabels=['1 – 5 dagen','6 – 20 dagen','21 – 50 dagen','51 – 100 dagen','101 dagen of meer'];
|
|
@@ -562,17 +589,26 @@ function buildWhiteReport(){
|
|
| 562 |
<div class="report-heading"><div><h1>Reakt Inloop Monitor ${escapeHTML(d.site)}</h1><p>Inzicht in bereik, betrokkenheid en bezoekpatronen</p></div>
|
| 563 |
<div class="report-meta"><div><b class="meta-icon">▦</b><span><strong>Periode</strong>${escapeHTML(range)}</span></div><div><b class="meta-icon">⌖</b><span><strong>Locatie</strong>${escapeHTML(d.site)}</span></div><div><b class="meta-icon">↻</b><span><strong>Peildatum</strong>${escapeHTML(d.latest)}</span></div></div></div>
|
| 564 |
<div class="report-kpi-grid">${kpiHTML}</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
<div class="report-chart-grid">
|
| 566 |
-
<section class="report-panel"><h2>Instroom per maand <small>(nieuwe deelnemers)</small></h2><div class="chart-box"><div class="report-no-data">${missing}<br>
|
| 567 |
<section class="report-panel"><h2>Unieke bezoekdagen per maand</h2><div class="chart-box">${reportChartLine(mon,values)}</div></section>
|
| 568 |
<section class="report-panel"><h2>Bezoekdagen per weekdag</h2><div class="chart-box">${reportChartBars(week,d.byWeek,'#ff6410',true)}</div></section>
|
| 569 |
</div>
|
| 570 |
<div class="report-bottom-grid">
|
| 571 |
-
<section class="report-panel"><h2>Kerncijfers locatie</h2><table class="report-mini-table"><thead><tr><th>Locatie</th><th>Unieke
|
| 572 |
<section class="report-panel"><h2>Bezoekintensiteit <small>(unieke bezoekdagen per cliëntnummer)</small></h2>${itHTML}<div class="report-intensity-summary"><span>Gemiddeld<br><strong>${avg}</strong></span><span>Mediaan<br><strong>${String(d.median).replace('.',',')}</strong></span><span>Hoogste aantal dagen<br><strong>${d.maxDays}</strong></span></div></section>
|
| 573 |
<section class="report-panel"><h2>Managementanalyse</h2><div class="report-analysis">${summary.map(t=>`<p>${t}</p>`).join('')}</div></section>
|
| 574 |
</div>
|
| 575 |
-
<div class="report-foot"><span>Bron: Fictieve Reakt-scanregistratie ·
|
| 576 |
}
|
| 577 |
function openWhiteReport(){
|
| 578 |
if(!importedData?.last){alert('Upload eerst het fictieve Excel-testbestand en rond de analyse af. Deze witte rapportage gebruikt geen verzonnen demo-KPI’s.');return;}
|
|
@@ -600,7 +636,7 @@ async function downloadReportImage(type='png'){
|
|
| 600 |
const quality=type==='jpg'?0.94:1;
|
| 601 |
const dataUrl=canvas.toDataURL(mime,quality);
|
| 602 |
const link=document.createElement('a');
|
| 603 |
-
const site=(
|
| 604 |
link.href=dataUrl;
|
| 605 |
link.download=`reakt_inloop_monitor_${site}.${type==='jpg'?'jpg':'png'}`;
|
| 606 |
document.body.appendChild(link);
|
|
|
|
| 140 |
#reportSheet .report-analysis p { margin-bottom:5px!important; }
|
| 141 |
#reportSheet .report-foot { margin-top:6px!important; padding-top:5px!important; font-size:8px!important; }
|
| 142 |
}
|
| 143 |
+
/* v0.9.1: transparante cliëntdefinities zonder aanpassing van de ORCHESTRA-huisstijl. */
|
| 144 |
+
.definition-guide{border:1px solid #2c4971;border-radius:11px;background:linear-gradient(110deg,#11243b,#111a33);padding:12px 16px;margin:11px 0 16px;color:#cadcf0}
|
| 145 |
+
.definition-guide-heading{display:flex;align-items:center;gap:10px;margin-bottom:9px;font-size:12px}
|
| 146 |
+
.definition-guide-heading strong{color:#f2f8ff}.definition-guide-heading>span:last-child{font-size:10px;color:#93abc9;margin-left:auto}
|
| 147 |
+
.definition-guide-icon{color:#57dcbc;font-size:17px}
|
| 148 |
+
.definition-guide-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:13px}
|
| 149 |
+
.definition-guide-grid>div{border-left:2px solid #3d7091;padding:1px 0 1px 10px}
|
| 150 |
+
.definition-guide-grid b{font-size:11px;color:#e3f3ff}.definition-guide-grid p{font-size:10px;line-height:1.42;margin:4px 0 0;color:#adbed6}
|
| 151 |
+
.definition-guide-note{font-size:10px;color:#77dbb7;margin-top:10px;line-height:1.35}
|
| 152 |
+
@media(max-width:760px){.definition-guide-grid{grid-template-columns:1fr}.definition-guide-heading{flex-wrap:wrap}.definition-guide-heading>span:last-child{margin-left:0}}
|
| 153 |
+
.report-definitions{border:1px solid #e2e6f1;border-radius:7px;background:#f8faff;padding:8px 12px;margin:-3px 0 10px}
|
| 154 |
+
.report-definitions-title{font-weight:800;color:#181c65;font-size:11px;margin-bottom:5px}
|
| 155 |
+
.report-definitions-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
|
| 156 |
+
.report-definitions-grid>div{border-left:3px solid #7850b9;padding:2px 0 2px 8px;color:#324062;font-size:10px;line-height:1.32}
|
| 157 |
+
.report-definitions-grid b{display:block;font-size:10px;color:#161c64;margin-bottom:2px}
|
| 158 |
+
.report-definitions-note{font-size:9px;color:#52618d;margin-top:5px}
|
| 159 |
+
@media(max-width:1000px){.report-definitions-grid{grid-template-columns:1fr}}
|
| 160 |
+
@media print{#reportSheet .report-definitions-grid{grid-template-columns:repeat(3,minmax(0,1fr))!important}#reportSheet .report-definitions{padding:4px 7px!important;margin:1px 0 6px!important}#reportSheet .report-definitions-grid>div{font-size:7px!important;line-height:1.2!important}#reportSheet .report-definitions-grid b,#reportSheet .report-definitions-title{font-size:8px!important}#reportSheet .report-definitions-note{font-size:7px!important}}
|
| 161 |
</style></head>
|
| 162 |
<body>
|
| 163 |
<div class="app">
|
|
|
|
| 185 |
<div class="content-grid">
|
| 186 |
<div class="left-content">
|
| 187 |
<div class="kpi-grid" id="kpis"></div>
|
| 188 |
+
<section class="definition-guide" aria-label="Betekenis van de cliëntcijfers">
|
| 189 |
+
<div class="definition-guide-heading"><span class="definition-guide-icon">ⓘ</span><strong>Wat betekenen deze cliëntcijfers?</strong><span>Rekenregels per inlooplocatie</span></div>
|
| 190 |
+
<div class="definition-guide-grid">
|
| 191 |
+
<div><b>Unieke cliënten</b><p>Iedere cliënt die deze locatie ooit minimaal één keer heeft bezocht én gescand, binnen de beschikbare scanhistorie.</p></div>
|
| 192 |
+
<div><b>Nieuwe instroom</b><p>Cliënten met hun allereerste scan op deze locatie in de gekozen rapportageperiode.</p></div>
|
| 193 |
+
<div><b>Actieve cliënten</b><p>Cliënten die in de afgelopen drie maanden t/m de peildatum minimaal één keer op deze locatie hebben gescand.</p></div>
|
| 194 |
+
</div>
|
| 195 |
+
<div class="definition-guide-note">Een verloren of vervangen scanpas telt niet als een nieuwe cliënt: meerdere passen van dezelfde cliënt worden samengevoegd.</div>
|
| 196 |
+
</section>
|
| 197 |
<div class="charts-grid"><section class="panel chart-panel"><div class="panel-heading"><div><span class="section-kicker">TRENDANALYSE</span><h3>Bezoeken per locatie</h3></div><span class="subtle">Jul – Sep 2026</span></div><canvas id="chart" width="640" height="250" aria-label="Grafiek fictieve bezoeken per locatie"></canvas><div class="legend"><span><i style="background:#3ee1b6"></i> Soeda</span><span><i style="background:#ffc66a"></i> Rietdijk</span><span><i style="background:#5b9cff"></i> Vredehof</span><span><i style="background:#ad88ff"></i> Gouda</span></div></section>
|
| 198 |
<section class="panel verification"><div class="panel-heading"><div><span class="section-kicker">KWALITEITSBORGING</span><h3>Verificatie Businesscontroller</h3></div><span class="warn-label">DEMO</span></div><div id="checks" class="checks"></div><div class="warning">⚠ 1 fictieve afwijking. Rapport niet gevalideerd.</div></section></div>
|
| 199 |
<section class="panel logpanel"><div class="panel-heading"><div><span class="section-kicker">REAL-TIME PROCESS</span><h3>Agent activity log</h3></div><span class="subtle" id="logStatus">Stand-by</span></div><div class="logs" id="logs"><div class="logitem muted">◌ Klik op ‘Start demo-analyse’ om de workflow te bekijken.</div></div></section>
|
|
|
|
| 205 |
</main>
|
| 206 |
</div>
|
| 207 |
<div id="reportOverlay" role="dialog" aria-modal="true" aria-label="Reakt Monitor rapport" aria-hidden="true">
|
| 208 |
+
<div class="report-actions"><span><strong>REAKT · Rapportvoorvertoning v0.9.1</strong> — uitsluitend fictieve testgegevens</span><div><button id="reportClose" type="button">✕ Sluiten</button> <button id="reportPng" class="report-png" type="button">⬇ Download PNG</button> <button id="reportJpg" class="report-jpg" type="button">⬇ Download JPG</button> <button id="reportPrint" class="report-print" type="button">↓ Opslaan als PDF / Afdrukken</button></div></div>
|
| 209 |
<section id="reportSheet" aria-label="Witte Inloop Monitor"></section>
|
| 210 |
</div>
|
| 211 |
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
|
|
| 415 |
unmatchedPasses:unmatchedPasses.size,linkedByPass,rowsWithPass};
|
| 416 |
}
|
| 417 |
function calculateImported(site='*'){
|
| 418 |
+
const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),siteClients=new Set(),monthSite=new Map();
|
| 419 |
const passByClient=new Map();let missingPass=0;
|
| 420 |
for(const r of all){
|
| 421 |
if(r.pass){if(!passByClient.has(r.id))passByClient.set(r.id,new Set());passByClient.get(r.id).add(r.pass)}else missingPass++;
|
| 422 |
const k=r.site+'|'+r.date+'|'+r.id;
|
| 423 |
if(keyset.has(k))continue;
|
| 424 |
+
keyset.add(k);ids.add(r.id);siteClients.add(r.site+'|'+r.id);
|
| 425 |
const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
|
| 426 |
}
|
| 427 |
let multiPassClients=0;
|
|
|
|
| 429 |
else multiPassClients=[...passByClient.values()].filter(p=>p.size>1).length;
|
| 430 |
const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
|
| 431 |
const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
|
| 432 |
+
return {site,rows:all.length,visits:keyset.size,unique:siteClients.size,duplicates:all.length-keyset.size,months:chosen,locations,
|
| 433 |
invalid:importedData.invalid,passChecked:importedData.hasPassColumn||importedData.passSheetFound,
|
| 434 |
passSheetFound:importedData.passSheetFound,pasDates:importedData.pasDates,peildatum:importedData.peildatum,multiPassClients,passConflicts:importedData.passConflicts,
|
| 435 |
identityConflicts:importedData.identityConflicts,unmatchedPasses:importedData.unmatchedPasses,
|
|
|
|
| 455 |
function renderImported(result){
|
| 456 |
const num=n=>n.toLocaleString('nl-NL');
|
| 457 |
const kpis=[{label:'Bezoeken (voorlopig)',value:num(result.visits),change:'Unieke cliënt / dag / locatie'},
|
| 458 |
+
{label:'Unieke cliënten',value:num(result.unique),change:result.site==='*'?'Som van de locatiecijfers':'Alle scans in beschikbare historie'},
|
| 459 |
{label:'Scanregels',value:num(result.rows),change:`${num(result.duplicates)} herhaalde scans`},
|
| 460 |
{label:'Verificatie',value:'—',change:'Extern niet uitgevoerd'}];
|
| 461 |
renderKPIs({kpis});drawImportedChart(result);
|
| 462 |
+
const lines=[['Herhaalde scans',num(result.duplicates)],['Unieke cliënten (per locatie)',num(result.unique)],
|
| 463 |
['Scanpassen: apart werkblad',result.passSheetFound?num(result.passRows)+' registraties':'NIET AANWEZIG'],
|
| 464 |
['Cliënten met meerdere passen',result.passChecked?num(result.multiPassClients):'NIET TE CONTROLEREN'],
|
| 465 |
['Scanregels via pas gekoppeld',num(result.linkedByPass)],
|
|
|
|
| 489 |
const result=calculateImported($('siteSelect').value);importedData.last=result;
|
| 490 |
const steps=[
|
| 491 |
{agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans; Scanpassen: ${result.passSheetFound?result.passRows+' registraties':'geen apart werkblad'}`},
|
| 492 |
+
{agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke cliënten per locatie berekend`},
|
| 493 |
{agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
|
| 494 |
{agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
|
| 495 |
{agent:'report',text:'Conceptpreview bijgewerkt · NIET GEVALIDEERD'}];
|
|
|
|
| 523 |
const r=importedData.last;
|
| 524 |
if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
|
| 525 |
if(/valid|controle|wouter|business|betrouw|verschil|akkoord/.test(q.toLowerCase()))return 'Deze Excel-proef is NIET extern gevalideerd. De schaduwberekening van de businesscontroller ontbreekt en de rekenregels moeten nog worden afgestemd.';
|
| 526 |
+
if(/bezoek|uniek|scan|groei|locatie|samenvat|rapport|analyse|kwartaal|cijfer/.test(q.toLowerCase()))return `Testbestand (${r.site==='*'?'alle locaties':r.site}): ${r.rows} scanregels, ${r.visits} voorlopige bezoeken na ${r.duplicates} herhaalde scans, en ${r.unique} unieke cliënten per locatie. Scanpassen: ${r.passSheetFound?r.passRows+' registraties': 'geen apart werkblad'}; cliënten met meerdere passen: ${r.multiPassClients}; tegenstrijdige koppelingen: ${r.passConflicts+r.identityConflicts}. Dit is een lokale proef zonder AI-API en zonder externe verificatie.`;
|
| 527 |
return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
|
| 528 |
};
|
| 529 |
|
|
|
|
| 576 |
const summary=[`In de geselecteerde periode registreerde de proefmonitor ${reportNL(d.unique)} unieke cliëntnummers en ${reportNL(d.visits)} unieke bezoekdagen voor ${escapeHTML(d.site)}.`,
|
| 577 |
`De gemiddelde bezoekintensiteit bedraagt ${avg} bezoekdagen per cliëntnummer; de mediaan is ${String(d.median).replace('.',',')}.`,
|
| 578 |
`De meeste bezoekdagen vallen op ${busiest} (${reportNL(weekdayMax)}) en de minste op ${quietest} (${reportNL(weekdayMin)}).`,
|
| 579 |
+
`Nieuwe instroom en actieve cliënten zijn wel gedefinieerd, maar worden in deze testversie nog niet berekend. Pas-startdatums bewijzen geen instroom. Externe verificatie ontbreekt.`];
|
| 580 |
+
const kpis=[{klass:'purple',icon:'♟',label:'Unieke cliënten',value:reportNL(d.unique),sub:'Minimaal één scan ooit op deze locatie'},
|
| 581 |
+
{klass:'green',icon:'+',label:'Nieuwe instroom',value:'—',sub:'Eerste scan in rapportageperiode · nog niet berekend'},
|
| 582 |
+
{klass:'purple',icon:'♙',label:'Actieve cliënten',value:'—',sub:'Min. 1 scan in afgelopen 3 maanden · nog niet berekend'},
|
| 583 |
{klass:'orange',icon:'▦',label:'Unieke bezoekdagen',value:reportNL(d.visits),sub:'Eén cliëntnummer per locatie per kalenderdag'}];
|
| 584 |
const kpiHTML=kpis.map(k=>`<div class="report-kpi ${k.klass}"><div class="k-icon">${k.icon}</div><div><div class="k-label">${k.label}</div><div class="k-num">${k.value}</div><div class="k-sub">${k.sub}</div></div><div class="k-underline"></div></div>`).join('');
|
| 585 |
const maxI=Math.max(1,...d.intensities),ilabels=['1 – 5 dagen','6 – 20 dagen','21 – 50 dagen','51 – 100 dagen','101 dagen of meer'];
|
|
|
|
| 589 |
<div class="report-heading"><div><h1>Reakt Inloop Monitor ${escapeHTML(d.site)}</h1><p>Inzicht in bereik, betrokkenheid en bezoekpatronen</p></div>
|
| 590 |
<div class="report-meta"><div><b class="meta-icon">▦</b><span><strong>Periode</strong>${escapeHTML(range)}</span></div><div><b class="meta-icon">⌖</b><span><strong>Locatie</strong>${escapeHTML(d.site)}</span></div><div><b class="meta-icon">↻</b><span><strong>Peildatum</strong>${escapeHTML(d.latest)}</span></div></div></div>
|
| 591 |
<div class="report-kpi-grid">${kpiHTML}</div>
|
| 592 |
+
<section class="report-definitions" aria-label="Definities cliëntindicatoren">
|
| 593 |
+
<div class="report-definitions-title">Zo leest u de cliëntcijfers</div>
|
| 594 |
+
<div class="report-definitions-grid">
|
| 595 |
+
<div><b>Unieke cliënten</b>Iedere cliënt die op deze locatie ooit minimaal één keer heeft gescand (binnen de beschikbare historie).</div>
|
| 596 |
+
<div><b>Nieuwe instroom</b>De eerste scan ooit van een cliënt op deze locatie valt in de rapportageperiode.</div>
|
| 597 |
+
<div><b>Actieve cliënten</b>Minimaal één scan op deze locatie in de afgelopen drie maanden tot de peildatum.</div>
|
| 598 |
+
</div>
|
| 599 |
+
<div class="report-definitions-note">Een vervangende scanpas telt niet als nieuwe cliënt. Alle passen van één cliënt worden gekoppeld.</div>
|
| 600 |
+
</section>
|
| 601 |
<div class="report-chart-grid">
|
| 602 |
+
<section class="report-panel"><h2>Instroom per maand <small>(nieuwe deelnemers)</small></h2><div class="chart-box"><div class="report-no-data">${missing}<br>Definitie vastgesteld · berekening volgt</div></div></section>
|
| 603 |
<section class="report-panel"><h2>Unieke bezoekdagen per maand</h2><div class="chart-box">${reportChartLine(mon,values)}</div></section>
|
| 604 |
<section class="report-panel"><h2>Bezoekdagen per weekdag</h2><div class="chart-box">${reportChartBars(week,d.byWeek,'#ff6410',true)}</div></section>
|
| 605 |
</div>
|
| 606 |
<div class="report-bottom-grid">
|
| 607 |
+
<section class="report-panel"><h2>Kerncijfers locatie</h2><table class="report-mini-table"><thead><tr><th>Locatie</th><th>Unieke cliënten</th><th>Unieke bezoekdagen</th><th>Gem. dagen / bezoeker</th></tr></thead><tbody><tr><td>⌖ ${escapeHTML(d.site)}</td><td>${reportNL(d.unique)}</td><td>${reportNL(d.visits)}</td><td>${avg}</td></tr><tr class="total"><td>Totaal / gemiddelde</td><td>${reportNL(d.unique)}</td><td>${reportNL(d.visits)}</td><td>${avg}</td></tr></tbody></table></section>
|
| 608 |
<section class="report-panel"><h2>Bezoekintensiteit <small>(unieke bezoekdagen per cliëntnummer)</small></h2>${itHTML}<div class="report-intensity-summary"><span>Gemiddeld<br><strong>${avg}</strong></span><span>Mediaan<br><strong>${String(d.median).replace('.',',')}</strong></span><span>Hoogste aantal dagen<br><strong>${d.maxDays}</strong></span></div></section>
|
| 609 |
<section class="report-panel"><h2>Managementanalyse</h2><div class="report-analysis">${summary.map(t=>`<p>${t}</p>`).join('')}</div></section>
|
| 610 |
</div>
|
| 611 |
+
<div class="report-foot"><span>Bron: Fictieve Reakt-scanregistratie · Eén cliënt per locatie per dag telt als één bezoekdag · Historie beperkt tot geladen bestand</span><span class="report-provisional">FICTIEVE TESTGEGEVENS · NIET GEVALIDEERD · NIET PUBLICEREN</span></div>`;
|
| 612 |
}
|
| 613 |
function openWhiteReport(){
|
| 614 |
if(!importedData?.last){alert('Upload eerst het fictieve Excel-testbestand en rond de analyse af. Deze witte rapportage gebruikt geen verzonnen demo-KPI’s.');return;}
|
|
|
|
| 636 |
const quality=type==='jpg'?0.94:1;
|
| 637 |
const dataUrl=canvas.toDataURL(mime,quality);
|
| 638 |
const link=document.createElement('a');
|
| 639 |
+
const site=(importedData?.last?.site||'monitor').toLowerCase().replace(/[^a-z0-9]+/gi,'_');
|
| 640 |
link.href=dataUrl;
|
| 641 |
link.download=`reakt_inloop_monitor_${site}.${type==='jpg'?'jpg':'png'}`;
|
| 642 |
document.body.appendChild(link);
|