'use strict';const CATEGORIES = {
opslaan: 'Opslaan & organiseren',
zoeken: 'Zoeken & terugvinden',
delen: 'Delen & samenwerken',
beveiliging: 'Beveiliging & toegang',
efficient: 'Efficiënt gebruik'
};const QUESTIONS = [
{ id: 1, category: 'opslaan', text: 'Ik weet wanneer ik een document in OneDrive, Teams of SharePoint moet opslaan.' },
{ id: 2, category: 'zoeken', text: 'Ik kan documenten binnen Microsoft 365 snel terugvinden.' },
{ id: 3, category: 'beveiliging', text: 'Ik weet hoe ik voorkom dat er meerdere versies van hetzelfde document ontstaan.' },
{ id: 4, category: 'opslaan', text: 'Ik begrijp de relatie tussen Teams en SharePoint.' },
{ id: 5, category: 'beveiliging', text: "Ik weet hoe ik collega's de juiste toegangsrechten geef." },
{ id: 6, category: 'delen', text: 'Ik weet hoe ik documenten veilig deel zonder ze als bijlage rond te sturen.' },
{ id: 7, category: 'efficient', text: 'Ik begrijp hoe OneDrive-synchronisatie werkt.' },
{ id: 8, category: 'opslaan', text: 'Ik weet in welk Team of kanaal informatie thuishoort.' },
{ id: 9, category: 'opslaan', text: 'Ik kan documenten logisch organiseren zonder ingewikkelde mappenstructuren.' },
{ id: 10, category: 'zoeken', text: 'Ik gebruik de zoekmogelijkheden van Microsoft 365 effectief.' },
{ id: 11, category: 'zoeken', text: 'Ik weet hoe ik via versiegeschiedenis een eerdere versie terughaal.' },
{ id: 12, category: 'delen', text: 'Ik weet hoe meerdere collega’s tegelijkertijd in één document kunnen samenwerken.' },
{ id: 13, category: 'zoeken', text: 'Ik weet wat ik moet doen wanneer een bestand per ongeluk is verwijderd of verplaatst.' },
{ id: 14, category: 'delen', text: 'Ik weet hoe ik documenten correct en veilig met externe personen deel.' },
{ id: 15, category: 'beveiliging', text: 'Ik begrijp het verschil tussen persoonlijke documenten en teamdocumenten.' },
{ id: 16, category: 'efficient', text: 'Ik kan mijn Teams-meldingen zo instellen dat ik belangrijke informatie niet mis.' },
{ id: 17, category: 'efficient', text: 'Ik begrijp de verschillende OneDrive-statussymbolen, zoals het wolkje en groene vinkje.' },
{ id: 18, category: 'beveiliging', text: 'Ik begrijp het verschil tussen standaard-, privé- en gedeelde Teams-kanalen.' },
{ id: 19, category: 'delen', text: 'Ik weet wanneer ik Microsoft 365 beter als samenwerkingsomgeving kan inzetten dan alleen als bestandsopslag.' },
{ id: 20, category: 'efficient', text: 'Ik ken voldoende slimme functies van Microsoft 365 om mijn dagelijkse werkzaamheden sneller en efficiënter uit te voeren.' }
];const SCALE = [
{ value: 1, label: 'Helemaal niet' },
{ value: 2, label: 'Nauwelijks' },
{ value: 3, label: 'Redelijk' },
{ value: 4, label: 'Goed' },
{ value: 5, label: 'Volledig' }
];const LEVELS = [
{ min: 85, label: 'Uitstekend', tone: 'excellent', meaning: 'Goede kennis; optimalisatie is nog mogelijk.' },
{ min: 70, label: 'Goed', tone: 'good', meaning: 'Er zijn duidelijke kennislekken die productiviteit kosten.' },
{ min: 50, label: 'Voldoende', tone: 'fair', meaning: 'Waarschijnlijk regelmatig tijdverlies door inefficiënt gebruik.' },
{ min: 0, label: 'Onvoldoende', tone: 'poor', meaning: 'Grote kans op structureel tijdverlies, fouten, dubbel werk en extra helpdeskvragen.' }
];const RECOMMENDATIONS = {
opslaan: 'Leer wanneer je iets in OneDrive (persoonlijk), een Team of een SharePoint-bibliotheek opslaat, en spreek binnen het team één opslagstructuur af.',
zoeken: 'Oefen met de zoekfunctie van Microsoft 365 en de versiegeschiedenis, zodat terugvinden en herstellen geen tijd meer kost.',
delen: 'Gebruik deel-links met de juiste rechten in plaats van bijlages, en leer hoe gelijktijdig samenwerken in één document werkt.',
beveiliging: 'Maak het verschil tussen persoonlijke, team- en externe toegang expliciet, en controleer regelmatig wie toegang heeft tot wat.',
efficient: 'Verdiep je in OneDrive-statussymbolen, meldingsinstellingen en de slimmere functies van Microsoft 365 om dagelijks werk te versnellen.'
};// Trainingsadvies per kennisgebied, inclusief hoe een 5MOT (5 Minuten Online Training)
// video dat specifieke lek direct kan dichten.
const TRAININGS = {
opslaan: {
title: 'OneDrive, Teams & SharePoint — opslag & structuur',
mot: 'Een 5MOT-video van 5 minuten laat exact zien wanneer iets in OneDrive, een Team of een SharePoint-bibliotheek thuishoort — direct toepasbaar, geen hele cursus nodig.'
},
zoeken: {
title: 'Slim zoeken & terugvinden in Microsoft 365',
mot: 'Een 5MOT-video over de zoekfunctie en versiegeschiedenis lost dit knelpunt vaak al in één keer kijken op.'
},
delen: {
title: 'Veilig samenwerken & delen in Microsoft 365',
mot: 'Een 5MOT-video over deel-links en gelijktijdig samenwerken vervangt in 5 minuten de gewoonte om bijlages rond te mailen.'
},
beveiliging: {
title: 'Beveiliging & toegangsrechten in Microsoft 365',
mot: 'Een 5MOT-video over kanaaltypes en toegangsrechten maakt dit abstracte onderwerp in 5 minuten concreet en herkenbaar.'
},
efficient: {
title: 'Slimmer werken met Microsoft 365',
mot: 'Een 5MOT-video over statussymbolen en meldingsinstellingen bespaart per medewerker direct tijd, zonder een volledige training te plannen.'
}
};// Kennisgebieden waar DocuBird (documentatie/procesondersteuning) qua onderwerp op aansluit.
const DOCUBIRD_CATEGORIES = ['opslaan', 'zoeken', 'delen'];// Volledige catalogus van specifieke trainingen die als vervolgadvies aangeboden kunnen worden.
const TRAINING_CATALOG = {
outlook: 'Outlook',
teams: 'Teams',
word: 'Word',
excel: 'Excel',
powerpoint: 'PowerPoint',
onedrive: 'OneDrive',
onenote: 'OneNote',
sharepoint: 'SharePoint',
forms: 'Forms',
m365: 'Office 365 / M365',
copilot: 'Copilot M365',
diev: 'Beveiliging & AVG — DIEV™'
};// Welke trainingen uit de catalogus relevant zijn per kennisgebied.
const CATEGORY_TRAININGS = {
opslaan: ['onedrive', 'sharepoint', 'onenote'],
zoeken: ['onedrive', 'sharepoint', 'outlook'],
delen: ['teams', 'sharepoint', 'word', 'excel', 'powerpoint'],
beveiliging: ['diev', 'sharepoint', 'teams'],
efficient: ['outlook', 'teams', 'copilot', 'forms', 'm365']
};function trainingAdvice(weakestCats) {
return weakestCats.map(cat => ({
category: cat,
label: CATEGORIES[cat],
...TRAININGS[cat],
products: (CATEGORY_TRAININGS[cat] || []).map(key => TRAINING_CATALOG[key])
}));
}function docuBirdRelevant(weakestCats) {
return weakestCats.some(cat => DOCUBIRD_CATEGORIES.includes(cat));
}// ROI-berekening o.b.v. instelbare aannames (zie config.js). Dit is een indicatieve
// berekening voor gespreksdoeleinden, geen gegarandeerd rendement.
function calculateRoi(timeLost, roiConfig) {
const cfg = roiConfig || {};
const hourlyRateEur = cfg.hourlyRateEur || 45;
const trainingCostEur = cfg.trainingCostPerParticipant || 149;
const assumedImprovementPct = cfg.assumedImprovementPct || 50;const yearlyLossEur = round1(timeLost.hoursPerYear * hourlyRateEur);
const yearlySavingsEur = round1(yearlyLossEur * (assumedImprovementPct / 100));
const netFirstYearEur = round1(yearlySavingsEur - trainingCostEur);
const paybackMonths = yearlySavingsEur > 0 ? round1((trainingCostEur / yearlySavingsEur) * 12) : null;return {
hourlyRateEur,
trainingCostEur,
assumedImprovementPct,
yearlyLossEur,
yearlySavingsEur,
netFirstYearEur,
paybackMonths
};
}function getLevel(score) {
return LEVELS.find(l => score >= l.min) || LEVELS[LEVELS.length - 1];
}function categoryScores(answers) {
const result = {};
Object.keys(CATEGORIES).forEach(cat => {
const qs = QUESTIONS.filter(q => q.category === cat);
const sum = qs.reduce((acc, q) => acc + (answers[q.id] || 0), 0);
result[cat] = Math.round((sum / (qs.length * 5)) * 100);
});
return result;
}function totalScore(answers) {
return QUESTIONS.reduce((acc, q) => acc + (answers[q.id] || 0), 0);
}function estimatedMinutesLostPerDay(score) {
const clamped = Math.min(100, Math.max(20, score));
const minutes = 15 * ((100 - clamped) / (100 - 20));
return Math.round(minutes * 10) / 10;
}// Gebaseerd op een gemiddelde werkweek van 40 uur: 5 werkdagen/week, 52 weken/jaar,
// 4.33 weken/maand (52 / 12).
const WORKDAYS_PER_WEEK = 5;
const WEEKS_PER_MONTH = 52 / 12;
const WEEKS_PER_YEAR = 52;function round1(value) {
return Math.round(value * 10) / 10;
}function estimatedTimeLost(score) {
const perDayMinutes = estimatedMinutesLostPerDay(score);
const perWeekMinutes = round1(perDayMinutes * WORKDAYS_PER_WEEK);
const perMonthHours = round1((perWeekMinutes * WEEKS_PER_MONTH) / 60);
const perYearHours = round1((perWeekMinutes * WEEKS_PER_YEAR) / 60);
return {
minutesPerDay: perDayMinutes,
minutesPerWeek: perWeekMinutes,
hoursPerMonth: perMonthHours,
hoursPerYear: perYearHours
};
}function weakestCategories(catScores, thresholdCount = 2) {
return Object.entries(catScores)
.sort((a, b) => a[1] - b[1])
.slice(0, thresholdCount)
.map(([cat]) => cat);
}function orgFromUrl() {
const params = new URLSearchParams(location.search);
const slug = params.get('org') || '';
const name = params.get('orgnaam') || slug;
return { slug, name };
}async function sendToPowerAutomate(payload) {
const endpoint = window.KENNISSCAN_CONFIG && window.KENNISSCAN_CONFIG.powerAutomate && window.KENNISSCAN_CONFIG.powerAutomate.endpoint;
if (!endpoint) return { sent: false, reason: 'no-endpoint' };
try {
await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
keepalive: true
});
return { sent: true };
} catch (err) {
return { sent: false, reason: 'network-error', error: String(err) };
}
}function buildPayload({ organization, participant, answers }) {
const score = totalScore(answers);
const catScores = categoryScores(answers);
const level = getLevel(score);
return {
event: 'scan.completed',
eventId: (crypto.randomUUID ? crypto.randomUUID() : String(Date.now()) + Math.random().toString(16).slice(2)),
timestampUtc: new Date().toISOString(),
application: 'M365 Kennislek Scan',
organization,
participant,
result: {
totalScore: score,
level: level.label,
categoryScores: catScores,
estimatedMinutesLostPerDay: estimatedMinutesLostPerDay(score),
timeLost: estimatedTimeLost(score)
},
answers: QUESTIONS.map(q => ({ questionId: q.id, category: q.category, score: answers[q.id] || 0 }))
};
}