{"id":1107,"date":"2025-11-27T16:10:24","date_gmt":"2025-11-27T14:10:24","guid":{"rendered":"http:\/\/www.jemtronics.be\/?page_id=1107"},"modified":"2026-05-13T10:22:01","modified_gmt":"2026-05-13T08:22:01","slug":"test","status":"publish","type":"page","link":"https:\/\/www.jemtronics.be\/index.php\/test\/","title":{"rendered":"Test"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<div id=\"betracked-app\">\n  <style>\n    \/* Basis layout *\/\n    #betracked-app {\n      font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n      background: #ffffff;\n      color: #222;\n      padding: 16px;\n      box-sizing: border-box;\n    }\n    #betracked-app * { box-sizing: border-box; }\n    #betracked-app h2 { margin: 0 0 12px; font-size: 20px; }\n\n    .bt-card {\n      background: #ffffff;\n      border: 1px solid #e0e0e0;\n      border-radius: 6px;\n      padding: 12px 14px;\n      margin-bottom: 12px;\n      box-shadow: 0 1px 2px rgba(0,0,0,0.03);\n    }\n\n    .bt-row {\n      display: flex;\n      flex-wrap: wrap;\n      align-items: center;\n      gap: 8px 16px;\n      margin-bottom: 6px;\n    }\n\n    .bt-row label { font-size: 13px; }\n\n    input.bt-input, select.bt-input {\n      padding: 4px 8px;\n      font-size: 13px;\n      border-radius: 4px;\n      border: 1px solid #ccc;\n      min-width: 200px;\n    }\n    input.bt-input[type=\"time\"] { min-width: 110px; }\n\n    \/* Buttons *\/\n    .bt-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      padding: 6px 12px;\n      border-radius: 4px;\n      border: 1px solid #ccc;\n      background: #f5f5f5;\n      font-size: 13px;\n      cursor: pointer;\n      transition: background 0.15s, box-shadow 0.15s, transform 0.05s;\n      text-decoration: none;\n      color: inherit;\n    }\n    .bt-btn:hover { background: #eeeeee; box-shadow: 0 1px 2px rgba(0,0,0,0.08); }\n    .bt-btn:active { transform: translateY(1px); box-shadow: none; }\n\n    .bt-btn-primary {\n      border-color: #0073aa;\n      background: #0073aa;\n      color: #ffffff;\n    }\n    .bt-btn-primary:hover { background: #006297; }\n\n    .bt-btn-small { padding: 4px 8px; font-size: 12px; }\n\n    .bt-btn-ghost {\n      background: #fafafa;\n      border-color: #d0d0d0;\n    }\n    .bt-btn-ghost:hover { background: #f0f0f0; }\n\n    .bt-status-text { font-size: 12px; color: #555; }\n    .bt-muted { color: #888; font-size: 12px; }\n\n    \/* Tabel *\/\n    table.bt-table {\n      width: 100%;\n      border-collapse: collapse;\n      margin-top: 8px;\n      font-size: 13px;\n    }\n    .bt-table th, .bt-table td {\n      border-bottom: 1px solid #eee;\n      padding: 6px 4px;\n      text-align: left;\n      vertical-align: middle;\n    }\n    .bt-table th {\n      background: #f7f7f7;\n      font-weight: 600;\n      font-size: 12px;\n    }\n    .bt-table tr:nth-child(even) td { background: #fafafa; }\n    .bt-table tr:hover td { background: #f0f8ff; }\n\n    .bt-table-checkbox { text-align: center; width: 32px; }\n    .bt-table-actions { white-space: nowrap; }\n\n    \/* Hele rij klikbaar \u2192 pointer cursor, behalve op knoppen & checkbox *\/\n    .bt-table tbody tr { cursor: pointer; }\n    .bt-table tbody tr td input,\n    .bt-table tbody tr td button { cursor: auto; }\n\n    \/* Relay status bolletje *\/\n    .bt-status-dot {\n      display: inline-block;\n      width: 10px;\n      height: 10px;\n      border-radius: 50%;\n      margin-right: 6px;\n      vertical-align: middle;\n    }\n    .bt-status-free { background: #2ecc71; }     \/* groen *\/\n    .bt-status-blocked { background: #e74c3c; }  \/* rood *\/\n\n    .bt-status-label { font-size: 12px; }\n\n    .bt-badge {\n      display: inline-block;\n      padding: 2px 6px;\n      border-radius: 10px;\n      font-size: 11px;\n      background: #f1f1f1;\n      color: #555;\n      margin-left: 4px;\n    }\n    .bt-badge-planner-on { background: #e6f4ea; color: #1e7e34; }\n    .bt-badge-planner-off { background: #f3e7e7; color: #8b3a3a; }\n\n    \/* Logboek *\/\n    .bt-log {\n      max-height: 220px;\n      overflow-y: auto;\n      white-space: pre-line;\n      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n      background: #fafafa;\n      padding: 8px;\n      border-radius: 4px;\n      border: 1px solid #e0e0e0;\n    }\n\n    @media (max-width: 768px) {\n      .bt-row { flex-direction: column; align-items: flex-start; }\n      .bt-table th, .bt-table td { font-size: 12px; }\n    }\n  <\/style>\n\n  <h2>BeTracked voertuigblokkering<\/h2>\n\n  <!-- Config \/ API key -->\n  <div class=\"bt-card\">\n    <div class=\"bt-row\">\n      <label>\n        API key:\n        <input id=\"bt-api-key\" class=\"bt-input\" type=\"password\" placeholder=\"Voer uw BeTracked API key in\">\n      <\/label>\n    <\/div>\n    <div class=\"bt-row\">\n      <button id=\"bt-load-btn\" class=\"bt-btn bt-btn-primary\">Voertuigen laden<\/button>\n      <span id=\"bt-load-status\" class=\"bt-status-text\"><\/span>\n    <\/div>\n    <div class=\"bt-row\">\n      <span id=\"bt-time-now\" class=\"bt-muted\">Huidige tijd: \u2014<\/span>\n    <\/div>\n  <\/div>\n\n  <!-- Herhaalinstellingen -->\n  <div class=\"bt-card\">\n    <div class=\"bt-row\"><strong>Herhaalinstellingen blokkering<\/strong><\/div>\n    <div class=\"bt-row\">\n      <label>\n        Aantal pogingen:\n        <select id=\"bt-retry-count\" class=\"bt-input\">\n          <option value=\"1\">1 poging<\/option>\n          <option value=\"2\">2 pogingen<\/option>\n          <option value=\"3\" selected>3 pogingen<\/option>\n          <option value=\"4\">4 pogingen<\/option>\n          <option value=\"5\">5 pogingen<\/option>\n          <option value=\"6\">6 pogingen<\/option>\n          <option value=\"7\">7 pogingen<\/option>\n          <option value=\"8\">8 pogingen<\/option>\n          <option value=\"9\">9 pogingen<\/option>\n          <option value=\"10\">10 pogingen<\/option>\n        <\/select>\n      <\/label>\n\n      <label>\n        Interval tussen pogingen:\n        <select id=\"bt-retry-interval\" class=\"bt-input\">\n          <option value=\"1\">1 minuut<\/option>\n          <option value=\"2\">2 minuten<\/option>\n          <option value=\"3\">3 minuten<\/option>\n          <option value=\"4\">4 minuten<\/option>\n          <option value=\"5\" selected>5 minuten<\/option>\n          <option value=\"10\">10 minuten<\/option>\n          <option value=\"15\">15 minuten<\/option>\n          <option value=\"20\">20 minuten<\/option>\n          <option value=\"25\">25 minuten<\/option>\n          <option value=\"30\">30 minuten<\/option>\n        <\/select>\n      <\/label>\n    <\/div>\n    <div class=\"bt-row\">\n      <span class=\"bt-muted\">Deze instellingen gelden voor handmatige acties \u00e9n voor de planner.<\/span>\n    <\/div>\n  <\/div>\n\n  <!-- Planner -->\n  <div class=\"bt-card\">\n    <div class=\"bt-row\">\n      <strong>Weekplanner (alle voertuigen met relay 2)<\/strong>\n      <span id=\"bt-planner-status\" class=\"bt-badge bt-badge-planner-off\">Planner uit<\/span>\n    <\/div>\n\n    <div class=\"bt-row\">\n      <label>\n        Blokkeren op dag:\n        <select id=\"bt-plan-block-day\" class=\"bt-input\">\n          <option value=\"1\">Maandag<\/option>\n          <option value=\"2\">Dinsdag<\/option>\n          <option value=\"3\">Woensdag<\/option>\n          <option value=\"4\">Donderdag<\/option>\n          <option value=\"5\" selected>Vrijdag<\/option>\n          <option value=\"6\">Zaterdag<\/option>\n          <option value=\"7\">Zondag<\/option>\n        <\/select>\n      <\/label>\n      <label>\n        om:\n        <input id=\"bt-plan-block-time\" class=\"bt-input\" type=\"time\" value=\"18:00\">\n      <\/label>\n    <\/div>\n\n    <div class=\"bt-row\">\n      <label>\n        Deblokkeren op dag:\n        <select id=\"bt-plan-unblock-day\" class=\"bt-input\">\n          <option value=\"1\">Maandag<\/option>\n          <option value=\"2\">Dinsdag<\/option>\n          <option value=\"3\">Woensdag<\/option>\n          <option value=\"4\">Donderdag<\/option>\n          <option value=\"5\">Vrijdag<\/option>\n          <option value=\"6\">Zaterdag<\/option>\n          <option value=\"7\" selected>Zondag<\/option>\n        <\/select>\n      <\/label>\n      <label>\n        om:\n        <input id=\"bt-plan-unblock-time\" class=\"bt-input\" type=\"time\" value=\"22:00\">\n      <\/label>\n    <\/div>\n\n    <div class=\"bt-row\">\n      <button id=\"bt-plan-save\" class=\"bt-btn bt-btn-primary bt-btn-small\">Planner opslaan<\/button>\n      <button id=\"bt-plan-toggle\" class=\"bt-btn bt-btn-ghost bt-btn-small\">Planner aanzetten<\/button>\n      <span id=\"bt-plan-info\" class=\"bt-status-text\"><\/span>\n    <\/div>\n\n    <div class=\"bt-row\">\n      <span class=\"bt-muted\">Let op: deze planner werkt alleen zolang deze pagina geopend is (client-side, geen server cron).<\/span>\n    <\/div>\n  <\/div>\n\n  <!-- Acties & selectie -->\n  <div class=\"bt-card\">\n    <div class=\"bt-row\">\n      <label>\n        <input type=\"checkbox\" id=\"bt-select-all\">\n        Alles selecteren \/ deselecteren\n      <\/label>\n      <span id=\"bt-selection-summary\" class=\"bt-muted\"><\/span>\n    <\/div>\n\n    <div class=\"bt-row\">\n      <button id=\"bt-block-selected\" class=\"bt-btn bt-btn-ghost bt-btn-small\">Blokkeer geselecteerde voertuigen<\/button>\n      <button id=\"bt-unblock-selected\" class=\"bt-btn bt-btn-ghost bt-btn-small\">Deblokkeer geselecteerde voertuigen<\/button>\n      <button id=\"bt-block-all\" class=\"bt-btn bt-btn-ghost bt-btn-small\">Blokkeer alle voertuigen<\/button>\n      <button id=\"bt-unblock-all\" class=\"bt-btn bt-btn-ghost bt-btn-small\">Deblokkeer alle voertuigen<\/button>\n      <span id=\"bt-action-status\" class=\"bt-status-text\"><\/span>\n    <\/div>\n  <\/div>\n\n  <!-- Voertuigtabel -->\n  <div class=\"bt-card\">\n    <div id=\"bt-vehicles-container\" class=\"bt-status-text\">\n      Voer een API key in en klik op \u201cVoertuigen laden\u201d.\n    <\/div>\n  <\/div>\n\n  <!-- Logboek -->\n  <div class=\"bt-card\">\n    <div class=\"bt-row\"><strong>Logboek foutpogingen<\/strong><\/div>\n    <div id=\"bt-log\" class=\"bt-status-text bt-log\">Nog geen fouten gelogd.<\/div>\n  <\/div>\n\n  <script>\n    (function () {\n      const apiBase = 'https:\/\/eu.betracked.nl\/api\/v1';\n      const RELAY_ID = 2;\n\n      let apiKey = '';\n      let vehicles = [];           \/\/ Alleen voertuigen met relay_id 2 \u00e9n enabled=1\n      let selectedUnits = new Set();\n      let plannerConfig = null;    \/\/ {blockDay,blockTime,unblockDay,unblockTime,enabled}\n      let plannerForcedState = null; \/\/ 'blocked' | 'unblocked' | null\n\n      \/\/ Retry-instellingen (via dropdowns)\n      let maxVerifyAttempts = 3;\n      let verifyIntervalMs = 5 * 60 * 1000; \/\/ standaard 5 minuten\n\n      \/\/ Element helpers\n      const $ = (id) => document.getElementById(id);\n\n      const elApiKey = $('bt-api-key');\n      const elLoadBtn = $('bt-load-btn');\n      const elLoadStatus = $('bt-load-status');\n      const elTimeNow = $('bt-time-now');\n\n      const elSelectAll = $('bt-select-all');\n      const elSelectionSummary = $('bt-selection-summary');\n      const elVehiclesContainer = $('bt-vehicles-container');\n\n      const elBlockSelected = $('bt-block-selected');\n      const elUnblockSelected = $('bt-unblock-selected');\n      const elBlockAll = $('bt-block-all');\n      const elUnblockAll = $('bt-unblock-all');\n      const elActionStatus = $('bt-action-status');\n\n      const elPlanBlockDay = $('bt-plan-block-day');\n      const elPlanBlockTime = $('bt-plan-block-time');\n      const elPlanUnblockDay = $('bt-plan-unblock-day');\n      const elPlanUnblockTime = $('bt-plan-unblock-time');\n      const elPlanSave = $('bt-plan-save');\n      const elPlanToggle = $('bt-plan-toggle');\n      const elPlanInfo = $('bt-plan-info');\n      const elPlannerStatus = $('bt-planner-status');\n\n      const elRetryCount = $('bt-retry-count');\n      const elRetryInterval = $('bt-retry-interval');\n\n      const elLog = $('bt-log');\n\n      \/* ===== TIJD- EN LOG-HELPERS ===== *\/\n      function pad2(n) { return n.toString().padStart(2, '0'); }\n\n      function timeStampNow() {\n        const now = new Date();\n        return pad2(now.getHours()) + ':' + pad2(now.getMinutes()) + ':' + pad2(now.getSeconds());\n      }\n\n      function setStatus(message) {\n        if (elActionStatus) elActionStatus.textContent = '[' + timeStampNow() + '] ' + message;\n      }\n\n      function logErrorLine(message) {\n        const line = '[' + timeStampNow() + '] ' + message;\n        if (!elLog) return;\n        if (elLog.textContent.trim() === 'Nog geen fouten gelogd.') {\n          elLog.textContent = line;\n        } else {\n          elLog.textContent += '\\n' + line;\n        }\n      }\n\n      \/* ===== HUIDIGE TIJD WEERGAVE ===== *\/\n      function updateTime() {\n        const now = new Date();\n        const dateStr =\n          pad2(now.getDate()) + '-' +\n          pad2(now.getMonth() + 1) + '-' +\n          now.getFullYear() + ' ' +\n          pad2(now.getHours()) + ':' +\n          pad2(now.getMinutes()) + ':' +\n          pad2(now.getSeconds());\n        if (elTimeNow) elTimeNow.textContent = 'Huidige tijd: ' + dateStr;\n      }\n      updateTime();\n      setInterval(updateTime, 1000);\n\n      \/* ===== UNIT\/RELAY HELPERS ===== *\/\n      function getUnitById(id) {\n        return vehicles.find((u) => u.unit_id === id);\n      }\n\n      function getRelay2(unit) {\n        if (!unit || !Array.isArray(unit.relays)) return null;\n        return unit.relays.find((r) => r.relay_id === RELAY_ID) || null;\n      }\n\n      \/\/ BELANGRIJK (zoals jij wil):\n      \/\/ Blokkeren = startonderbreking actief = relay_state = 1\n      \/\/ Deblokkeren = startonderbreking niet actief = relay_state = 0\n      \/\/ (Status-weergave is al goed en laten we zo.)\n      function isRelayBlocked(relay) {\n        return !!relay && Number(relay.relay_state) === 1;\n      }\n\n      \/* ===== API ===== *\/\n      async function fetchUnits() {\n        const url = apiBase + '\/unit\/list.json?key=' + encodeURIComponent(apiKey) + '&include=relays';\n        const res = await fetch(url);\n        if (!res.ok) throw new Error('HTTP ' + res.status);\n\n        const json = await res.json();\n        const allUnits = (json && json.data && json.data.units) || [];\n\n        \/\/ Alleen voertuigen met relay 2 \u00e9n enabled=1 (relais actief) \u2192 de rest buiten de lijst\n        vehicles = allUnits.filter((u) =>\n          Array.isArray(u.relays) &&\n          u.relays.some((r) => r.relay_id === RELAY_ID && Number(r.enabled) === 1)\n        );\n\n        return vehicles;\n      }\n\n      async function updateRelay(unitId, shouldBlock) {\n        const relayState = shouldBlock ? 1 : 0; \/\/ blokkeren=1, deblokkeren=0\n\n        const body = new URLSearchParams();\n        body.append('key', apiKey);\n        body.append('unit_id', unitId);\n        body.append('relay_id', RELAY_ID);\n        body.append('relay_state', relayState);\n\n        const res = await fetch(apiBase + '\/unit\/change_relay.json', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application\/x-www-form-urlencoded;charset=UTF-8' },\n          body\n        });\n\n        let json = null;\n        try { json = await res.json(); } catch (e) { json = null; }\n\n        if (!res.ok) {\n          const msg = json && json.error && (json.error.msg || json.error.message)\n            ? (json.error.msg || json.error.message)\n            : ('HTTP ' + res.status);\n          throw new Error(msg);\n        }\n\n        if (!json || !json.data || json.data.status !== 'ok') {\n          const errMsg = json && json.error && (json.error.msg || json.error.message)\n            ? (json.error.msg || json.error.message)\n            : 'Onbekende fout bij change_relay';\n          throw new Error(errMsg);\n        }\n      }\n\n      async function refreshUnitsAndRender() {\n        try {\n          if (elLoadStatus) elLoadStatus.textContent = 'Vernieuwen...';\n          await fetchUnits();\n          renderVehiclesTable();\n          if (elLoadStatus) elLoadStatus.textContent = 'Laatst bijgewerkt om ' + new Date().toLocaleTimeString();\n        } catch (e) {\n          if (elLoadStatus) elLoadStatus.textContent = 'Fout bij verversen: ' + e.message;\n          logErrorLine('Fout bij verversen: ' + e.message);\n        }\n      }\n\n      \/* ===== VERIFICATIE \/ RETRY ===== *\/\n      async function verifyAndMaybeRetry(unitIds, shouldBlock, attempt, isRetry, startTime) {\n        const attemptLabel = attempt + '\/' + maxVerifyAttempts;\n\n        try {\n          if (isRetry) {\n            setStatus('Retry ' + attemptLabel + ': gevraagde status toepassen op ' + unitIds.length + ' voertuig(en)...');\n            for (const uidRetry of unitIds) {\n              try {\n                await updateRelay(uidRetry, shouldBlock);\n              } catch (e) {\n                logErrorLine('Fout tijdens retry bij ' + (shouldBlock ? 'blokkeren' : 'deblokkeren') +\n                  ' van unit_id ' + uidRetry + ': ' + e.message);\n              }\n            }\n          }\n\n          setStatus('Controle relais-status (poging ' + attemptLabel + ')...');\n          await refreshUnitsAndRender();\n\n          const notOk = [];\n          for (const uid of unitIds) {\n            const unit = getUnitById(uid);\n            const relay = unit ? getRelay2(unit) : null;\n            const blocked = isRelayBlocked(relay);\n            if (blocked !== shouldBlock) notOk.push(uid);\n          }\n\n          if (notOk.length === 0) {\n            const startStr = startTime ? startTime.toLocaleString() : 'onbekend';\n            setStatus('Controle geslaagd (gestart op ' + startStr + '): alle geselecteerde voertuigen hebben de gevraagde status bereikt (poging ' + attemptLabel + ').');\n            return;\n          }\n\n          if (attempt >= maxVerifyAttempts) {\n            setStatus('Na ' + attemptLabel + ' hebben ' + notOk.length + ' voertuig(en) de gevraagde status nog steeds niet bereikt.');\n\n            const details = notOk.map((uid) => {\n              const unit = getUnitById(uid);\n              const kenteken = unit && unit.number ? unit.number : '(onbekend kenteken)';\n              const naam = unit && unit.label ? unit.label : '';\n              return kenteken + (naam ? ' ' + naam : '') + ' (unit_id ' + uid + ')';\n            }).join('; ');\n\n            logErrorLine('Maximale pogingen bereikt bij ' + (shouldBlock ? 'blokkeren' : 'deblokkeren') +\n              ' voor ' + notOk.length + ' voertuig(en): ' + details);\n            return;\n          }\n\n          const mins = Math.round(verifyIntervalMs \/ 60000);\n          setStatus('Niet alle voertuigen in gewenste status. Volgende poging (' + (attempt + 1) + '\/' + maxVerifyAttempts + ') over ' + mins + ' minuten...');\n\n          setTimeout(() => {\n            verifyAndMaybeRetry(notOk, shouldBlock, attempt + 1, true, startTime);\n          }, verifyIntervalMs);\n\n        } catch (e) {\n          setStatus('Fout tijdens controle\/automatische retry: ' + e.message);\n          logErrorLine('Fout tijdens controle\/automatische retry: ' + e.message);\n        }\n      }\n\n      function scheduleVerification(unitIds, shouldBlock) {\n        if (!unitIds || unitIds.length === 0) return;\n        const startTime = new Date();\n        verifyAndMaybeRetry(unitIds, shouldBlock, 1, false, startTime);\n      }\n\n      \/* ===== RENDER TABEL ===== *\/\n      function renderVehiclesTable() {\n        if (!vehicles || vehicles.length === 0) {\n          if (elVehiclesContainer) {\n            elVehiclesContainer.innerHTML =\n              '<span class=\"bt-muted\">Geen voertuigen gevonden met actief relais ID ' + RELAY_ID + '.<\/span>';\n          }\n          selectedUnits.clear();\n          if (elSelectAll) { elSelectAll.checked = false; elSelectAll.indeterminate = false; }\n          updateSelectionSummary();\n          return;\n        }\n\n        const validIds = new Set(vehicles.map((v) => v.unit_id));\n        selectedUnits.forEach((id) => { if (!validIds.has(Number(id))) selectedUnits.delete(id); });\n\n        const rowsHtml = vehicles.map((unit) => {\n          const relay = getRelay2(unit);\n          const blocked = isRelayBlocked(relay);\n          const statusClass = blocked ? 'bt-status-blocked' : 'bt-status-free';\n          const statusLabel = blocked ? 'Geblokkeerd' : 'Vrij';\n          const unitId = unit.unit_id;\n          const isChecked = selectedUnits.has(unitId) ? 'checked' : '';\n\n          const driverName =\n            unit.driver_name ||\n            (unit.driver && unit.driver.name) ||\n            unit.current_driver ||\n            unit.currentDriver ||\n            '';\n\n          return `\n            <tr data-unit-id=\"${unitId}\">\n              <td class=\"bt-table-checkbox\">\n                <input type=\"checkbox\" class=\"bt-unit-checkbox\" data-unit-id=\"${unitId}\" ${isChecked}>\n              <\/td>\n              <td>${unit.number || ''}<\/td>\n              <td>${unit.label || ''}<\/td>\n              <td>${driverName || ''}<\/td>\n              <td>\n                <span class=\"bt-status-label\">\n                  <span class=\"bt-status-dot ${statusClass}\"><\/span>\n                  ${statusLabel}\n                <\/span>\n              <\/td>\n              <td class=\"bt-table-actions\">\n                <button class=\"bt-btn bt-btn-small bt-btn-ghost bt-btn-block\" data-unit-id=\"${unitId}\">Blokkeer<\/button>\n                <button class=\"bt-btn bt-btn-small bt-btn-ghost bt-btn-unblock\" data-unit-id=\"${unitId}\">Deblokkeer<\/button>\n              <\/td>\n            <\/tr>\n          `;\n        }).join('');\n\n        const tableHtml = `\n          <table class=\"bt-table\">\n            <thead>\n              <tr>\n                <th class=\"bt-table-checkbox\"><\/th>\n                <th>Kenteken<\/th>\n                <th>Naam<\/th>\n                <th>Bestuurder<\/th>\n                <th>Status relay ${RELAY_ID}<\/th>\n                <th>Acties<\/th>\n              <\/tr>\n            <\/thead>\n            <tbody>${rowsHtml}<\/tbody>\n          <\/table>\n        `;\n\n        if (elVehiclesContainer) elVehiclesContainer.innerHTML = tableHtml;\n\n        bindRowEvents();\n        updateSelectionSummary();\n        updateSelectAllCheckbox();\n      }\n\n      function bindRowEvents() {\n        if (!elVehiclesContainer) return;\n\n        elVehiclesContainer.querySelectorAll('.bt-unit-checkbox').forEach((cb) => {\n          cb.addEventListener('change', (e) => {\n            const uid = Number(e.target.getAttribute('data-unit-id'));\n            if (e.target.checked) selectedUnits.add(uid); else selectedUnits.delete(uid);\n            updateSelectionSummary();\n            updateSelectAllCheckbox();\n          });\n        });\n\n        elVehiclesContainer.querySelectorAll('.bt-btn-block').forEach((btn) => {\n          btn.addEventListener('click', async (e) => {\n            e.stopPropagation();\n            const uid = Number(e.target.getAttribute('data-unit-id'));\n            await handleSingleUnit(uid, true);\n          });\n        });\n\n        elVehiclesContainer.querySelectorAll('.bt-btn-unblock').forEach((btn) => {\n          btn.addEventListener('click', async (e) => {\n            e.stopPropagation();\n            const uid = Number(e.target.getAttribute('data-unit-id'));\n            await handleSingleUnit(uid, false);\n          });\n        });\n\n        elVehiclesContainer.querySelectorAll('tbody tr').forEach((row) => {\n          row.addEventListener('click', (e) => {\n            if (e.target.closest('button') || e.target.closest('input')) return;\n            const uid = Number(row.getAttribute('data-unit-id'));\n            const checkbox = row.querySelector('.bt-unit-checkbox');\n            if (!checkbox) return;\n\n            if (selectedUnits.has(uid)) { selectedUnits.delete(uid); checkbox.checked = false; }\n            else { selectedUnits.add(uid); checkbox.checked = true; }\n\n            updateSelectionSummary();\n            updateSelectAllCheckbox();\n          });\n        });\n      }\n\n      function updateSelectionSummary() {\n        const count = selectedUnits.size;\n        if (!elSelectionSummary) return;\n        elSelectionSummary.textContent = count === 0 ? 'Geen voertuigen geselecteerd.' : (count + ' voertuig(en) geselecteerd.');\n      }\n\n      function updateSelectAllCheckbox() {\n        if (!elSelectAll) return;\n        if (!vehicles || vehicles.length === 0) { elSelectAll.checked = false; elSelectAll.indeterminate = false; return; }\n        const total = vehicles.length;\n        const selectedCount = selectedUnits.size;\n\n        if (selectedCount === 0) { elSelectAll.checked = false; elSelectAll.indeterminate = false; }\n        else if (selectedCount === total) { elSelectAll.checked = true; elSelectAll.indeterminate = false; }\n        else { elSelectAll.indeterminate = true; }\n      }\n\n      \/* ===== ACTIES ===== *\/\n      async function handleSingleUnit(unitId, block) {\n        if (!apiKey) { alert('Voer eerst een API key in.'); return; }\n\n        const unit = vehicles.find((u) => u.unit_id === unitId);\n        const kenteken = unit ? unit.number : unitId;\n\n        try {\n          setStatus((block ? 'Blokkeren ' : 'Deblokkeren ') + 'van ' + kenteken + '...');\n          await updateRelay(unitId, block);\n          setStatus('Actie uitgevoerd voor ' + kenteken + '. Vernieuwen...');\n          await refreshUnitsAndRender();\n          setStatus('Actie voltooid voor ' + kenteken + '.');\n          scheduleVerification([unitId], block);\n        } catch (e) {\n          setStatus('Fout bij actie voor ' + kenteken + ': ' + e.message);\n          logErrorLine('Fout bij directe actie voor ' + kenteken + ': ' + e.message);\n          scheduleVerification([unitId], block);\n        }\n      }\n\n      async function handleBulk(block, scope) {\n        if (!apiKey) { alert('Voer eerst een API key in.'); return; }\n        if (!vehicles || vehicles.length === 0) { alert('Geen voertuigen geladen.'); return; }\n\n        let targetIds;\n        if (scope === 'selected') {\n          if (selectedUnits.size === 0) { alert('Geen voertuigen geselecteerd.'); return; }\n          targetIds = Array.from(selectedUnits);\n        } else {\n          targetIds = vehicles.map((v) => v.unit_id);\n        }\n\n        setStatus((block ? 'Blokkeren' : 'Deblokkeren') + ' van ' + targetIds.length + ' voertuig(en)...');\n\n        let ok = 0, fail = 0;\n        for (const uid of targetIds) {\n          try { await updateRelay(uid, block); ok++; }\n          catch (e) { fail++; logErrorLine('Fout bij ' + (block ? 'blokkeren' : 'deblokkeren') + ' van unit_id ' + uid + ': ' + e.message); }\n        }\n\n        await refreshUnitsAndRender();\n        setStatus('Eerste actie gereed: ' + ok + ' succesvol, ' + fail + ' mislukt bij ' + (block ? 'blokkeren.' : 'deblokkeren.'));\n        scheduleVerification(targetIds, block);\n      }\n\n      \/* ===== PLANNER (AANGEPAST: altijd nieuwste planning, en juiste block\/unblock logica) ===== *\/\n      function readPlannerInputsToConfig(preserveEnabled) {\n        const enabled = preserveEnabled ? (!!(plannerConfig && plannerConfig.enabled)) : false;\n        return {\n          blockDay: parseInt(elPlanBlockDay.value, 10) || 5,\n          blockTime: elPlanBlockTime.value || '18:00',\n          unblockDay: parseInt(elPlanUnblockDay.value, 10) || 7,\n          unblockTime: elPlanUnblockTime.value || '22:00',\n          enabled\n        };\n      }\n\n      function updatePlannerUi() {\n        if (!elPlannerStatus || !elPlanToggle || !elPlanInfo) return;\n\n        if (!plannerConfig) {\n          elPlannerStatus.textContent = 'Planner uit';\n          elPlannerStatus.classList.remove('bt-badge-planner-on');\n          elPlannerStatus.classList.add('bt-badge-planner-off');\n          elPlanToggle.textContent = 'Planner aanzetten';\n          elPlanInfo.textContent = '';\n          return;\n        }\n\n        const enabled = !!plannerConfig.enabled;\n        elPlannerStatus.textContent = enabled ? 'Planner aan' : 'Planner uit';\n        elPlannerStatus.classList.toggle('bt-badge-planner-on', enabled);\n        elPlannerStatus.classList.toggle('bt-badge-planner-off', !enabled);\n        elPlanToggle.textContent = enabled ? 'Planner uitzetten' : 'Planner aanzetten';\n\n        const dayName = (d) => ({\n          1: 'maandag', 2: 'dinsdag', 3: 'woensdag', 4: 'donderdag', 5: 'vrijdag', 6: 'zaterdag', 7: 'zondag'\n        }[d] || d);\n\n        elPlanInfo.textContent =\n          'Blokkeren: ' + dayName(plannerConfig.blockDay) + ' ' + plannerConfig.blockTime +\n          ' \u2013 deblokkeren: ' + dayName(plannerConfig.unblockDay) + ' ' + plannerConfig.unblockTime +\n          (enabled ? ' (actief)' : ' (niet actief)');\n      }\n\n      function getCurrentMinutesOfWeek() {\n        const now = new Date();\n        let jsDay = now.getDay(); \/\/ 0=Sunday\n        let day1to7 = jsDay === 0 ? 7 : jsDay; \/\/ 1=ma t\/m 7=zo\n        return (day1to7 - 1) * 24 * 60 + now.getHours() * 60 + now.getMinutes();\n      }\n\n      function isWithinBlockWindow(nowMin, startMin, endMin) {\n        if (startMin === endMin) return false;\n        if (startMin < endMin) return nowMin >= startMin && nowMin < endMin;\n        return nowMin >= startMin || nowMin < endMin; \/\/ wrap over weekeinde\n      }\n\n      async function plannerTick() {\n        if (!plannerConfig || !plannerConfig.enabled) return;\n        if (!apiKey || !vehicles || vehicles.length === 0) return;\n\n        const nowMin = getCurrentMinutesOfWeek();\n\n        const [bh, bm] = (plannerConfig.blockTime || '18:00').split(':').map((v) => parseInt(v, 10) || 0);\n        const [uh, um] = (plannerConfig.unblockTime || '22:00').split(':').map((v) => parseInt(v, 10) || 0);\n\n        const blockMin = (plannerConfig.blockDay - 1) * 24 * 60 + bh * 60 + bm;\n        const unblockMin = (plannerConfig.unblockDay - 1) * 24 * 60 + uh * 60 + um;\n\n        const inWindow = isWithinBlockWindow(nowMin, blockMin, unblockMin);\n\n        \/\/ JUISTE LOGICA:\n        \/\/ inWindow = TRUE  => Blokkeren => startonderbreking ACTIEF => relay_state=1 => handleBulk(true)\n        \/\/ inWindow = FALSE => Deblokkeren => startonderbreking UIT => relay_state=0 => handleBulk(false)\n        if (inWindow && plannerForcedState !== 'blocked') {\n          plannerForcedState = 'blocked';\n          setStatus('Planner: automatisch blokkeren van alle voertuigen...');\n          await handleBulk(true, 'all');\n          setStatus('Planner: alle voertuigen geblokkeerd (volgens schema).');\n        } else if (!inWindow && plannerForcedState !== 'unblocked') {\n          plannerForcedState = 'unblocked';\n          setStatus('Planner: automatisch deblokkeren van alle voertuigen...');\n          await handleBulk(false, 'all');\n          setStatus('Planner: alle voertuigen gedeblokkeerd (volgens schema).');\n        }\n      }\n\n      \/\/ elke minuut checken\n      setInterval(plannerTick, 60000);\n\n      \/\/ Wanneer planner-instellingen wijzigen: meteen toepassen + oude planning effectief vervangen\n      function applyPlannerInputsImmediately() {\n        \/\/ behoud huidige enabled state (aan\/uit), maar vervang alle tijden\/dagen meteen\n        plannerConfig = readPlannerInputsToConfig(true);\n        plannerForcedState = null; \/\/ zodat de nieuwe planning meteen effect kan hebben\n        updatePlannerUi();\n\n        \/\/ Als planner aan staat: onmiddellijk evalueren met nieuwste planning\n        if (plannerConfig.enabled) {\n          plannerTick().catch((e) => {\n            setStatus('Planner fout: ' + e.message);\n            logErrorLine('Planner fout: ' + e.message);\n          });\n        }\n      }\n\n      \/* ===== EVENTS ===== *\/\n      elLoadBtn.addEventListener('click', async () => {\n        apiKey = (elApiKey.value || '').trim();\n        if (!apiKey) { alert('Voer een geldige API key in.'); return; }\n\n        if (elLoadStatus) elLoadStatus.textContent = 'Laden...';\n        if (elVehiclesContainer) elVehiclesContainer.textContent = 'Voertuigen laden...';\n\n        selectedUnits.clear();\n        if (elSelectAll) { elSelectAll.checked = false; elSelectAll.indeterminate = false; }\n\n        try {\n          await fetchUnits();\n          renderVehiclesTable();\n          if (elLoadStatus) elLoadStatus.textContent = 'Laden gereed (' + vehicles.length + ' voertuigen).';\n\n          \/\/ Als planner aan staat: meteen opnieuw evalueren (handig na reload \/ nieuwe voertuigen)\n          if (plannerConfig && plannerConfig.enabled) {\n            plannerForcedState = null;\n            plannerTick().catch(() => {});\n          }\n        } catch (e) {\n          if (elLoadStatus) elLoadStatus.textContent = 'Fout bij laden: ' + e.message;\n          if (elVehiclesContainer) elVehiclesContainer.textContent = 'Fout bij laden van voertuigen.';\n          logErrorLine('Fout bij laden van voertuigen: ' + e.message);\n        }\n      });\n\n      elSelectAll.addEventListener('change', () => {\n        if (!vehicles || vehicles.length === 0) {\n          elSelectAll.checked = false;\n          elSelectAll.indeterminate = false;\n          return;\n        }\n        selectedUnits.clear();\n        if (elSelectAll.checked) vehicles.forEach((v) => selectedUnits.add(v.unit_id));\n        renderVehiclesTable();\n      });\n\n      elBlockSelected.addEventListener('click', () => handleBulk(true, 'selected'));\n      elUnblockSelected.addEventListener('click', () => handleBulk(false, 'selected'));\n      elBlockAll.addEventListener('click', () => handleBulk(true, 'all'));\n      elUnblockAll.addEventListener('click', () => handleBulk(false, 'all'));\n\n      \/\/ Planner: opslaan (blijft bestaan), maar ook hier direct toepassen\n      elPlanSave.addEventListener('click', () => {\n        const enabled = plannerConfig ? !!plannerConfig.enabled : false;\n        plannerConfig = {\n          blockDay: parseInt(elPlanBlockDay.value, 10) || 5,\n          blockTime: elPlanBlockTime.value || '18:00',\n          unblockDay: parseInt(elPlanUnblockDay.value, 10) || 7,\n          unblockTime: elPlanUnblockTime.value || '22:00',\n          enabled\n        };\n        plannerForcedState = null;\n        updatePlannerUi();\n        elPlanInfo.textContent += ' (opgeslagen)';\n\n        \/\/ als actief: meteen evalueren met nieuwste config\n        if (plannerConfig.enabled) plannerTick().catch(() => {});\n      });\n\n      \/\/ Planner aan\/uit\n      elPlanToggle.addEventListener('click', () => {\n        if (!plannerConfig) {\n          plannerConfig = readPlannerInputsToConfig(false);\n          plannerConfig.enabled = true;\n        } else {\n          plannerConfig.enabled = !plannerConfig.enabled;\n        }\n        plannerForcedState = null;\n        updatePlannerUi();\n\n        \/\/ als net aangezet: meteen evalueren\n        if (plannerConfig.enabled) plannerTick().catch(() => {});\n      });\n\n      \/\/ Planner input changes: meteen updaten (oude planning is daarmee effectief vervangen)\n      elPlanBlockDay.addEventListener('change', applyPlannerInputsImmediately);\n      elPlanBlockTime.addEventListener('change', applyPlannerInputsImmediately);\n      elPlanUnblockDay.addEventListener('change', applyPlannerInputsImmediately);\n      elPlanUnblockTime.addEventListener('change', applyPlannerInputsImmediately);\n\n      \/\/ Retry dropdowns\n      elRetryCount.addEventListener('change', () => {\n        const v = parseInt(elRetryCount.value, 10);\n        if (!isNaN(v) && v >= 1 && v <= 10) maxVerifyAttempts = v;\n      });\n\n      elRetryInterval.addEventListener('change', () => {\n        const mins = parseInt(elRetryInterval.value, 10);\n        if (!isNaN(mins) && mins >= 1 && mins <= 30) verifyIntervalMs = mins * 60 * 1000;\n      });\n\n      \/\/ Init\n      (function init() {\n        updatePlannerUi();\n        setStatus('Klaar. Voer API key in en laad voertuigen.');\n      })();\n\n    })();\n  <\/script>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>BeTracked voertuigblokkering API key: Voertuigen laden Huidige tijd: \u2014 Herhaalinstellingen blokkering Aantal pogingen: 1 poging2 pogingen3 pogingen4 pogingen5 pogingen6 pogingen7 pogingen8 pogingen9 pogingen10 pogingen Interval tussen pogingen: 1 minuut2 minuten3 minuten4 minuten5 minuten10 minuten15 minuten20 minuten25 minuten30 minuten Deze instellingen gelden voor handmatige acties \u00e9n voor de planner. Weekplanner (alle voertuigen met relay 2) Planner<a href=\"https:\/\/www.jemtronics.be\/index.php\/test\/\">[&#8230;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"_links":{"self":[{"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/pages\/1107"}],"collection":[{"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/comments?post=1107"}],"version-history":[{"count":54,"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/pages\/1107\/revisions"}],"predecessor-version":[{"id":1227,"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/pages\/1107\/revisions\/1227"}],"wp:attachment":[{"href":"https:\/\/www.jemtronics.be\/index.php\/wp-json\/wp\/v2\/media?parent=1107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}