{"id":27,"date":"2025-09-29T18:12:57","date_gmt":"2025-09-29T18:12:57","guid":{"rendered":"https:\/\/ernst.bureaukiem.nl\/?page_id=27"},"modified":"2025-09-29T18:36:36","modified_gmt":"2025-09-29T18:36:36","slug":"vrijwilligers","status":"publish","type":"page","link":"https:\/\/ernst.bureaukiem.nl\/?page_id=27","title":{"rendered":"Vrijwilligers"},"content":{"rendered":"\n<div id=\"app\" data-app=\"adressen\" data-sheet-id=\"1YPB3EGw_rjscl9Mqdqs-MZ6aMIaZjInd\" data-gid=\"1763044737\">\n  <!-- hier staat je adres-HTML met #t, #zoek, #sheetLink, #status-pill, etc. -->\n<\/div>\n\n\n\n\n<!doctype html>\n<html lang=\"nl\" data-theme=\"dark\">\n<head>\n  <meta charset=\"utf-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n  <title>Adreslijst &#038; Beschikbaarheid<\/title>\n  <style>\n    :root{\n      --radius:18px;\n      --bg:#0b1020; --card:#121936; --muted:#8fa3c8; --text:#e8eefc;\n      --accent:#5b8cff; --accent-2:#76e0a5; --card-line:#243466;\n      --shadow-panel:0 10px 30px rgba(0,0,0,.08);\n      --shadow-table:0 16px 40px rgba(10,20,60,.10)\n    }\n    [data-theme=\"light\"]{\n      --bg:#f7f8fb; --card:#ffffff; --muted:#64748b; --text:#0f172a;\n      --accent:#3b82f6; --accent-2:#16a34a; --card-line:#e6ebf4;\n      --shadow-panel:0 2px 6px rgba(0,0,0,.04);\n      --shadow-table:0 3px 8px rgba(0,0,0,.05)\n    }\n\n    html,body{margin:0}\n    body{\n      background:var(--bg);\n      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, \"Noto Sans\", \"Helvetica Neue\", \"Apple Color Emoji\", \"Segoe UI Emoji\";\n      color:var(--text);\n      transition: background-color .25s ease, color .25s ease\n    }\n\n    .wrap{max-width:1200px; margin:40px auto; padding:0 20px}\n    header{display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:18px}\n    .title{font-weight:800; letter-spacing:.3px; font-size:clamp(22px, 3vw, 28px)}\n    .subtitle{color:var(--muted); font-size:14px}\n\n    .panel{display:flex; flex-wrap:wrap; gap:10px; background:var(--card);\n           padding:14px; border:1px solid var(--card-line); border-radius:var(--radius); box-shadow:var(--shadow-panel)}\n    .panel .group{display:flex; gap:10px; align-items:center}\n\n    input[type=\"search\"]{\n      background:var(--card);\n      color:var(--text); border:1px solid var(--card-line); border-radius:12px;\n      padding:10px 12px; font-size:14px; outline:none; appearance:none; min-width:260px\n    }\n    .pill{border-radius:999px; padding:6px 10px; font-size:12px; color:#0b1a10; background:var(--accent-2); font-weight:700}\n\n    .table-wrap{margin-top:16px; background:var(--card);\n                border:1px solid var(--card-line); border-radius:var(--radius); overflow:auto;\n                box-shadow:var(--shadow-table)}\n    table{width:100%; border-collapse:separate; border-spacing:0; min-width:1000px}\n    thead th{position:sticky; top:0; background:var(--card); color:var(--text);\n             text-align:left; font-weight:700; font-size:13px; padding:12px 12px; border-bottom:1px solid var(--card-line)}\n    tbody td{padding:10px 12px; border-bottom:1px dashed color-mix(in oklab, var(--card-line) 70%, #000 30%); vertical-align:top; font-size:14px}\n    tbody tr:hover{background:color-mix(in oklab, var(--card) 90%, #000 10%)}\n\n    .toolbar{display:flex; gap:10px; align-items:center}\n    .btn{cursor:pointer; border:1px solid var(--card-line); background:var(--card); color:var(--text); padding:8px 12px; border-radius:12px; font-weight:600}\n    .btn:active{transform:translateY(1px)}\n    .muted{color:var(--muted)}\n    .chip{display:inline-block; padding:4px 8px; border-radius:999px; background:var(--card); border:1px solid var(--card-line); margin:2px 4px 2px 0; font-size:12px}\n\n    a.link{color:color-mix(in oklab, var(--accent) 85%, #fff 15%); text-decoration:none}\n  <\/style>\n<\/head>\n<body>\n  <div class=\"wrap\">\n    <header>\n      <div>\n        <div class=\"title\">Adreslijst &#038; Beschikbaarheid<\/div>\n        <div class=\"subtitle\">Overzicht uit Google Sheet<\/div>\n      <\/div>\n      <div class=\"toolbar\">\n        <button id=\"themeToggle\" class=\"btn\" title=\"Wissel thema\">\ud83c\udf17 Licht \/ Donker<\/button>\n        <span class=\"pill\" id=\"status-pill\">Laden\u2026<\/span>\n      <\/div>\n    <\/header>\n\n    <section class=\"panel\" id=\"filters\">\n      <div class=\"group\">\n        <input id=\"zoek\" type=\"search\" placeholder=\"Zoek op naam, taak, dag of opmerking\u2026\" \/>\n      <\/div>\n      <div class=\"group\" style=\"margin-left:auto\">\n        <a id=\"sheetLink\" class=\"link\" target=\"_blank\" rel=\"noopener\">Open sheet<\/a>\n      <\/div>\n    <\/section>\n\n    <div class=\"table-wrap\">\n      <table id=\"t\">\n        <thead>\n          <tr>\n            <th>Naam<\/th>\n            <th>Adres<\/th>\n            <th>Telefoon<\/th>\n            <th>E-mail<\/th>\n            <th>Leeftijd<\/th>\n            <th>Achtergrond\/Opmerking<\/th>\n            <th>Gewenste taken<\/th>\n            <th>Beschikbaarheid<\/th>\n            <th>Beschikbare dagen<\/th>\n          <\/tr>\n        <\/thead>\n        <tbody><\/tbody>\n      <\/table>\n    <\/div>\n\n    <p class=\"muted\" id=\"count\" style=\"margin-top:10px\">0 rijen<\/p>\n  <\/div>\n\n<script>\n  \/\/ ==== CONFIG: jouw sheet ====\n  const SHEET_ID = '1YPB3EGw_rjscl9Mqdqs-MZ6aMIaZjInd';\n  const GID = '1763044737';\n  \/\/ ============================\n\n  \/\/ Links\n  const sheetURL = `https:\/\/docs.google.com\/spreadsheets\/d\/${SHEET_ID}\/edit#gid=${GID}`;\n  document.getElementById('sheetLink').href = sheetURL;\n\n  const gvizURL = `https:\/\/docs.google.com\/spreadsheets\/d\/${SHEET_ID}\/gviz\/tq?gid=${GID}&tqx=out:json`;\n\n  \/\/ DOM\n  const zoek = document.getElementById('zoek');\n  const tbody = document.querySelector('#t tbody');\n  const count = document.getElementById('count');\n  const statusPill = document.getElementById('status-pill');\n\n  \/\/ Theme toggle\n  const rootHtml = document.documentElement;\n  const savedTheme = localStorage.getItem('theme-pref');\n  rootHtml.setAttribute('data-theme', savedTheme || 'dark');\n  document.getElementById('themeToggle').addEventListener('click', ()=>{\n    const next = rootHtml.getAttribute('data-theme') === 'light' ? 'dark' : 'light';\n    rootHtml.setAttribute('data-theme', next);\n    localStorage.setItem('theme-pref', next);\n  });\n\n  \/\/ Helpers\n  function norm(s){\n    return String(s||'')\n      .toLowerCase()\n      .normalize('NFD').replace(\/\\p{Diacritic}\/gu,'')\n      .replace(\/[^a-z0-9]+\/g,'')\n  }\n  function telHref(v){\n    const digits = String(v||'').replace(\/[^\\d+]\/g,'');\n    return digits ? `tel:${digits}` : '';\n  }\n  function mailHref(v){\n    const s = String(v||'').trim();\n    return s ? `mailto:${s}` : '';\n  }\n\n  \/\/ JSONP loader (werkt lokaal)\n  function jsonpGviz(url){\n    return new Promise((resolve, reject)=>{\n      const prev = window.google?.visualization?.Query?.setResponse;\n      window.google = window.google || {};\n      google.visualization = google.visualization || {};\n      google.visualization.Query = google.visualization.Query || {};\n      google.visualization.Query.setResponse = (payload)=>{\n        cleanup(); resolve(payload);\n      };\n      const s = document.createElement('script');\n      s.src = url + `&cachebust=${Date.now()}`;\n      s.async = true;\n      s.onerror = ()=>{ cleanup(); reject(new Error('Script kon niet laden (controleer sheet-permissies en internetverbinding)')); };\n      document.head.appendChild(s);\n      function cleanup(){\n        if(s && s.parentNode) s.parentNode.removeChild(s);\n        if(prev){ google.visualization.Query.setResponse = prev; }\n      }\n    });\n  }\n\n  \/\/ Vind kolomindex op basis van label\n  function makeColIndex(cols){\n    const index = {};\n    \/\/ gewenste labels en mogelijke varianten\n    const wanted = {\n      naam: ['naam'],\n      adres: ['adres'],\n      telefoon: ['telefoon','tel','mobiel','gsm','phone'],\n      email: ['email','e-mail','mail'],\n      leeftijd: ['leeftijd','age'],\n      achtergrond: ['achtergrondopmerking','achtergrond','opmerking','opmerkingen','toelichting','notitie','notities'],\n      taken: ['gewenstetaken','taken','voorkeurstaken'],\n      beschikbaarheid: ['beschikbaarheid','beschikbaar'],\n      dagen: ['beschikbaredagen','dagen','welkedagen','dagenbeschikbaarheid']\n    };\n    const labelNorms = cols.map(c => norm(c?.label || ''));\n    for(const [key, variants] of Object.entries(wanted)){\n      let found = -1;\n      for(let i=0;i<labelNorms.length;i++){\n        const L = labelNorms[i];\n        if(variants.some(v=> L === norm(v))) { found = i; break; }\n        \/\/ fuzzy: bevat variant\n        if(found<0 &#038;&#038; variants.some(v=> L.includes(norm(v)))) { found = i; }\n      }\n      index[key] = found;\n    }\n    return index;\n  }\n\n  let rows = [];\n\n  function render(){\n    const q = zoek.value.trim().toLowerCase();\n    const filtered = rows.filter(r=>{\n      if(!q) return true;\n      const text = (r.naam+' '+r.adres+' '+r.tel+' '+r.email+' '+r.leeftijd+' '+r.achtergrond+' '+r.taken+' '+r.beschikbaarheid+' '+r.dagen).toLowerCase();\n      return text.includes(q);\n    });\n\n    tbody.innerHTML = filtered.map(r=>{\n      const telLink = telHref(r.tel);\n      const mailLink = mailHref(r.email);\n      return `<tr>\n        <td>${r.naam||''}<\/td>\n        <td>${r.adres||''}<\/td>\n        <td>${telLink ? `<a class=\"link\" href=\"${telLink}\">${r.tel}<\/a>` : (r.tel||'')}<\/td>\n        <td>${mailLink ? `<a class=\"link\" href=\"${mailLink}\">${r.email}<\/a>` : (r.email||'')}<\/td>\n        <td>${r.leeftijd||''}<\/td>\n        <td>${r.achtergrond||''}<\/td>\n        <td>${r.taken||''}<\/td>\n        <td>${r.beschikbaarheid||''}<\/td>\n        <td>${\n          (r.dagen||'').split(\/[,;\/|]+\/).map(x=>x.trim()).filter(Boolean).map(d=>`<span class=\"chip\">${d}<\/span>`).join('') || ''\n        }<\/td>\n      <\/tr>`;\n    }).join('');\n\n    count.textContent = `${filtered.length} rijen`;\n  }\n\n  async function load(){\n    try{\n      const json = await jsonpGviz(gvizURL);\n      const cols = json.table?.cols || [];\n      const dataRows = json.table?.rows || [];\n      if(!Array.isArray(dataRows)) throw new Error('Onverwacht formaat: rows ontbreekt');\n\n      const idx = makeColIndex(cols);\n\n      rows = dataRows.map(r=>{\n        const c = r.c || [];\n        const get = (i)=> i>=0 && c[i] ? (c[i].f ?? c[i].v ?? '') : '';\n        return {\n          naam:           get(idx.naam),\n          adres:          get(idx.adres),\n          tel:            get(idx.telefoon),\n          email:          get(idx.email),\n          leeftijd:       get(idx.leeftijd),\n          achtergrond:    get(idx.achtergrond),\n          taken:          get(idx.taken),\n          beschikbaarheid:get(idx.beschikbaarheid),\n          dagen:          get(idx.dagen)\n        };\n      })\n      \/\/ headers\/lege rijen eruit filteren\n      .filter(r=> String(r.naam||'').trim() && !\/naam\/i.test(String(r.naam)));\n\n      statusPill.textContent = `\u2713 ${rows.length} personen`;\n      render();\n    }catch(err){\n      console.error('[LOAD] Fout:', err);\n      statusPill.textContent = 'Kon data niet laden';\n      statusPill.style.background = 'var(--card)';\n      statusPill.style.color = 'var(--muted)';\n      statusPill.title = 'Tip: zet de Google Sheet op \"Iedereen met de link \u2013 Bekijken\".';\n    }\n  }\n\n  zoek.addEventListener('input', render);\n\n  \/\/ Zelf-test\n  (function(){\n    try{\n      console.assert(norm('E-mail') === 'email', 'diakritische normalisatie');\n      console.assert(telHref('06 123 456 78').startsWith('tel:'), 'tel link');\n      console.log('[Self-test] OK');\n    }catch(e){ console.warn('[Self-test] Mislukt', e); }\n  })();\n\n  load();\n<\/script>\n<\/body>\n<\/html>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Adreslijst &#038; Beschikbaarheid Adreslijst &#038; Beschikbaarheid Overzicht uit Google Sheet \ud83c\udf17 Licht \/ Donker Laden\u2026 Open sheet Naam Adres Telefoon E-mail Leeftijd Achtergrond\/Opmerking Gewenste taken Beschikbaarheid Beschikbare dagen 0 rijen<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-no-title","meta":{"footnotes":""},"class_list":["post-27","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=\/wp\/v2\/pages\/27","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=27"}],"version-history":[{"count":4,"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=\/wp\/v2\/pages\/27\/revisions"}],"predecessor-version":[{"id":36,"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=\/wp\/v2\/pages\/27\/revisions\/36"}],"wp:attachment":[{"href":"https:\/\/ernst.bureaukiem.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=27"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}