PageSourceSearch

https://mdb-nrg.netlify.app/assets/wizard.js?v=1.19.1

js mdb-nrg.netlify.app collected 2026-10-03 11:03:35 UTC 16,412 bytes, 328 lines download raw bytes

1/* =====================================================================
2   Energieloket — quickscan-wizard (één doorlopende vragenlijst)
3   Stappen: voornaam, achternaam, e-mail, telefoon, bedrijf, projectnaam,
4            netaansluiting (bekend? + grootte), opdrachtgever-eisen,
5            locatie, looptijd -> afronden (bijlagen + versturen).
6   Bij een aansluiting > 3×80A wordt de aanvrager direct gealarmeerd.
7   Vereist: app.js (window.sb, window.Loket), Leaflet.
8   ===================================================================== */
9(function () {
10  const root = document.getElementById("qwiz");
11  if (!root) return;
12  const $ = (id) => document.getElementById(id);
13  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14
15  const wz = {
16    key: "first", t0: Date.now(),
17    s: {
18      first: "", last: "", email: "", phone: "", company: "", project: "",
19      netKnown: "", netSize: "", netOver: false,
20      laadKnown: "", laadAantal: 1, eisen: [],
21      addr: "", lat: null, lng: null, start: "", end: "",
22    },
23    map: null, marker: null, mapReady: false,
24  };
25  const wzFiles = [];
26
27  // ---- stapvolgorde (één pad, alle vragen) ----
28  const SEQ = ["first", "last", "email", "phone", "company", "project", "net", "laad", "eisen", "loc", "term"];
29  function idx() { return SEQ.indexOf(wz.key); }
30  function total() { return SEQ.length; }
31  function curScreen() { return root.querySelector('.qscreen[data-screen="' + wz.key + '"]'); }
32
33  function markChoice(list, active) { list.forEach((el) => el && el.classList.toggle("on", el === active)); }
34
35  function show(focus) {
36    const k = wz.key;
37    root.querySelectorAll(".qscreen").forEach((el) => (el.hidden = el.dataset.screen !== k));
38    const i = idx(), n = total();
39    $("qwizBar").style.width = Math.round(((i + 1) / n) * 100) + "%";
40    $("qwizLabel").textContent = "Stap " + (i + 1) + " van " + n;
41    $("wzBack").style.visibility = i === 0 ? "hidden" : "visible";
42    $("wzNext").textContent = i === n - 1 ? "Naar afronden" : "Volgende";
43    $("wzMsg").style.display = "none";
44    if (k === "loc") ensureMap();
45    if (focus) {
46      const sc = curScreen();
47      const inp = sc && sc.querySelector("input:not([type=checkbox])");
48      if (inp && inp.type !== "date") setTimeout(() => inp.focus(), 30);
49    }
50  }
51  function err(msg) { const m = $("wzMsg"); m.textContent = msg; m.className = "authmsg show err"; m.style.display = "block"; }
52
53  // ---- detectie aansluiting > 3×80A ----
54  function exceeds3x80(raw) {
55    if (!raw) return false;
56    const s = String(raw).toLowerCase().replace(/[×*]/g, "x").replace(/,/g, ".");
57    let m = s.match(/([\d.]+)\s*kva/);
58    if (m) return parseFloat(m[1]) > 55.2;                 // 3×80A ≈ 55,2 kVA
59    m = s.match(/(\d+)\s*x\s*(\d+)\s*a?/);
60    if (m) return (+m[1]) * (+m[2]) > 240;                 // 3×80A = 240
61    m = s.match(/(\d+)\s*a/);
62    if (m) return parseFloat(m[1]) > 80;                   // enkel ampère
63    return false;
64  }
65  function updateNetAlarm() {
66    const inp = $("wzNetSize"), box = $("netAlarm");
67    if (!inp || !box) return;
68    const over = wz.s.netKnown === "ja" && exceeds3x80(inp.value);
69    wz.s.netOver = over;
70    box.classList.toggle("hideblock", !over);
71  }
72
73  function read() {
74    const k = wz.key, s = wz.s;
75    if (k === "first") s.first = $("wzFirst").value.trim();
76    if (k === "last") s.last = $("wzLast").value.trim();
77    if (k === "email") s.email = $("wzEmail").value.trim();
78    if (k === "phone") s.phone = $("wzPhone").value.trim();
79    if (k === "company") s.company = $("wzCompany").value.trim();
80    if (k === "project") s.project = $("wzProject").value.trim();
81    if (k === "net" && s.netKnown === "ja") { s.netSize = $("wzNetSize").value.trim(); updateNetAlarm(); }
82    if (k === "laad" && s.laadKnown === "ja") {
83      const n = parseInt($("wzLaadAantal").value) || 1;
84      s.laadAantal = Math.max(1, n); $("wzLaadAantal").value = s.laadAantal;
85    }
86    if (k === "eisen") s.eisen = [...document.querySelectorAll('#wzEisen input:checked')].map((c) => c.value);
87    if (k === "loc") s.addr = $("wzAddr").value.trim();
88    if (k === "term") { s.start = $("wzStart").value; s.end = $("wzEnd").value; }
89  }
90
91  async function valid() {
92    const k = wz.key, s = wz.s;
93    if (k === "first" && !s.first) return err("Vul je voornaam in."), false;
94    if (k === "last" && !s.last) return err("Vul je achternaam in."), false;
95    if (k === "email" && !EMAIL_RE.test(s.email)) return err("Vul een geldig e-mailadres in."), false;
96    if (k === "phone" && (s.phone || "").replace(/[^\d]/g, "").length < 8) return err("Vul een geldig telefoonnummer in."), false;
97    if (k === "company" && !s.company) return err("Vul je bedrijfsnaam in."), false;
98    if (k === "project" && !s.project) return err("Vul de projectnaam in."), false;
99    if (k === "net") {
100      if (!s.netKnown) return err("Geef aan of het type netaansluiting al bekend is."), false;
101      if (s.netKnown === "ja" && !s.netSize) return err("Vul de grootte van de aansluiting in."), false;
102    }
103    if (k === "laad" && !s.laadKnown) return err("Geef aan of er laadpalen nodig zijn."), false;
104    if (k === "loc") {
105      if (s.lat == null && s.addr) { await geocode(); }
106      if (s.lat == null) return err("Zoek het adres of zet de speld op de kaart."), false;
107    }
108    if (k === "term") {
109      if (!s.start) return err("Kies een startdatum."), false;
110      if (s.end && s.end < s.start) return err("De einddatum ligt vóór de startdatum."), false;
111    }
112    return true;
113  }
114
115  async function next() {
116    read();
117    if (!(await valid())) return;
118    const i = idx();
119    if (i === SEQ.length - 1) return finish();
120    wz.key = SEQ[i + 1]; show(true);
121  }
122  function back() {
123    read();
124    const i = idx();
125    if (i > 0) { wz.key = SEQ[i - 1]; show(true); }
126  }
127
128  function finish() {
129    root.style.display = "none";
130    const rest = $("qrest"); rest.style.display = "block";
131    const q2 = $("q2"), cw = $("calcwrap"), fs = $("finalSubmit");
132    if (q2) q2.classList.add("hideblock");     // geen laadinfra-vraag
133    if (cw) cw.classList.remove("show");        // geen ERE-calculator
134    if (fs) fs.classList.remove("hideblock");   // direct het verzendblok
135    wireFinal();
136    rest.scrollIntoView({ behavior: "smooth", block: "start" });
137  }
138
139  // ---- kaart (Leaflet + PDOK) ----
140  function pinIcon() {
141    return L.divIcon({
142      className: "qwiz-pinicon",
143      html: '<svg width="30" height="40" viewBox="0 0 24 32" xmlns="http://www.w3.org/2000/svg"><path d="M12 0C5.4 0 0 5.4 0 12c0 9 12 20 12 20s12-11 12-20C24 5.4 18.6 0 12 0z" fill="#A816D9"/><circle cx="12" cy="12" r="5" fill="#fff"/></svg>',
144      iconSize: [30, 40], iconAnchor: [15, 40],
145    });
146  }
147  function ensureMap() {
148    if (wz.mapReady) { setTimeout(() => wz.map.invalidateSize(), 60); return; }
149    if (typeof L === "undefined") return;
150    wz.map = L.map("wzMap", { scrollWheelZoom: false }).setView([52.13, 5.29], 7);
151    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
152      maxZoom: 19, attribution: "&copy; OpenStreetMap",
153    }).addTo(wz.map);
154    wz.map.on("click", (e) => { setPin(e.latlng.lat, e.latlng.lng); reverse(e.latlng.lat, e.latlng.lng); });
155    wz.mapReady = true;
156    setTimeout(() => wz.map.invalidateSize(), 60);
157  }
158  function setPin(lat, lng, zoom) {
159    wz.s.lat = +lat.toFixed(6); wz.s.lng = +lng.toFixed(6);
160    if (!wz.marker) {
161      wz.marker = L.marker([lat, lng], { draggable: true, icon: pinIcon() }).addTo(wz.map);
162      wz.marker.on("dragend", () => { const p = wz.marker.getLatLng(); setPin(p.lat, p.lng); reverse(p.lat, p.lng); });
163    } else wz.marker.setLatLng([lat, lng]);
164    wz.map.setView([lat, lng], zoom || Math.max(wz.map.getZoom(), 15));
165    $("wzCoord").textContent = "Locatie vastgelegd op de kaart.";
166  }
167  async function geocode() {
168    const q = $("wzAddr").value.trim(); if (!q) return;
169    try {
170      const u = "https://api.pdok.nl/bzk/locatieserver/search/v3_1/free?q=" + encodeURIComponent(q) + "&rows=1&fl=weergavenaam,centroide_ll";
171      const r = await fetch(u); const j = await r.json();
172      const d = j.response && j.response.docs && j.response.docs[0];
173      if (!d) { err("Adres niet gevonden — zet de speld handmatig op de kaart."); return; }
174      const m = /POINT\(([-\d.]+) ([-\d.]+)\)/.exec(d.centroide_ll);
175      if (!m) return;
176      ensureMap();
177      $("wzAddr").value = d.weergavenaam || q; wz.s.addr = $("wzAddr").value;
178      setPin(parseFloat(m[2]), parseFloat(m[1]), 16);
179    } catch (e) { err("Adres zoeken lukte niet — zet de speld handmatig op de kaart."); }
180  }
181  async function reverse(lat, lng) {
182    try {
183      const u = "https://api.pdok.nl/bzk/locatieserver/search/v3_1/reverse?lat=" + lat + "&lon=" + lng + "&rows=1&fl=weergavenaam";
184      const r = await fetch(u); const j = await r.json();
185      const d = j.response && j.response.docs && j.response.docs[0];
186      if (d && d.weergavenaam) { $("wzAddr").value = d.weergavenaam; wz.s.addr = d.weergavenaam; }
187    } catch (e) { /* stil */ }
188  }
189  window.qwizSearch = geocode;
190
191  // ---- definitieve verzending ----
192  function wireFinal() {
193    const drop = $("drop"), inp = $("files"), list = $("filelist"), bt
193n = $("btnQuick");
194    if (drop && !drop.dataset.wired) {
195      drop.dataset.wired = "1";
196      const render = () => { list.innerHTML = ""; wzFiles.forEach((f, i) => { const li = document.createElement("li"); li.innerHTML = '<span><svg class="icn" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11l-8 8a5 5 0 0 1-7-7l9-9a3.5 3.5 0 0 1 5 5l-9 9a2 2 0 0 1-3-3l8-8"/></svg> ' + Loket.escape(f.name) + '</span><span class="x" title="verwijder">&times;</span>'; li.querySelector(".x").onclick = () => { wzFiles.splice(i, 1); render(); }; list.appendChild(li); }); };
197      drop.onclick = () => inp.click();
198      inp.onchange = (e) => { for (const f of e.target.files) wzFiles.push(f); render(); inp.value = ""; };
199      ["dragover", "dragenter"].forEach((ev) => drop.addEventListener(ev, (e) => { e.preventDefault(); drop.classList.add("over"); }));
200      ["dragleave", "drop"].forEach((ev) => drop.addEventListener(ev, (e) => { e.preventDefault(); drop.classList.remove("over"); }));
201      drop.addEventListener("drop", (e) => { for (const f of e.dataTransfer.files) wzFiles.push(f); render(); });
202    }
203    if (btn && !btn.dataset.wired) { btn.dataset.wired = "1"; btn.onclick = submit; }
204  }
205
206  function botBlock() {
207    try {
208      $("drop").parentElement.style.display = "none";
209      $("btnQuick").parentElement.style.display = "none";
210    } catch (e) {}
211    $("recap").innerHTML = "<div>Je aanvraag is ontvangen.</div>";
212    $("success").classList.add("show");
213  }
214
215  function buildIntake() {
216    const s = wz.s;
217    const intake = { net_known: s.netKnown || null, laad_known: s.laadKnown || null, eisen: s.eisen || [] };
218    if (s.laadKnown === "ja") intake.laad_aantal = s.laadAantal;
219    if (s.netKnown === "ja") { intake.net_size = s.netSize || null; intake.net_over_3x80 = !!s.netOver; }
220    return intake;
221  }
222
223  async function submit() {
224    const hp = document.getElementById("wzHp");
225    if ((hp && hp.value.trim()) || Date.now() - wz.t0 < 3000) { botBlock(); return; }
226    if (!window.sb) { Loket.toast("Supabase nog niet ingesteld.", "err"); return; }
227    const btn = $("btnQuick"); btn.disabled = true; const lbl = btn.textContent;
228    btn.innerHTML = '<span class="spinner"></span> Versturen…';
229    try {
230      const folder = (crypto.randomUUID && crypto.randomUUID()) || ("a" + Date.now() + Math.random().toString(36).slice(2));
231      const attachments = [];
232      for (const f of wzFiles) {
233        const safe = f.name.replace(/[^\w.\-]+/g, "_"); const path = folder + "/" + safe;
234        const { error } = await window.sb.storage.from("aanvragen").upload(path, f, { upsert: false });
235        if (error) throw error;
236        attachments.push({ name: f.name, path, size: f.size });
237      }
238      const s = wz.s;
239      const intake = buildIntake();
240      const { data, error } = await window.sb.rpc("submit_quickscan", {
241        p_first: s.first, p_last: s.last, p_email: s.email, p_phone: s.phone, p_company: s.company, p_project: s.project,
242        p_address: s.addr, p_lat: s.lat, p_lng: s.lng, p_start: s.start || null, p_end: s.end || null,
243        p_ere: null, p_attachments: attachments, p_intake: intake,
244      });
245      if (error) throw error;
246      const nr = (data && data.project_no) || "—";
247      // e-mails versturen via de Edge Function (bevestiging naar aanvrager + interne kopie)
248      try {
249        if (data && data.id) await window.sb.functions.invoke("send-aanvraag", { body: { id: data.id } });
250      } catch (mailErr) {
251        console.error("Mailfunctie niet bereikbaar:", mailErr); // aanvraag is al opgeslagen; niet blokkeren
252      }
253      $("drop").parentElement.style.display = "none";
254      btn.parentElement.style.display = "none";
255      const esc = Loket.escape;
256      const intakeLines =
257        (s.netKnown
258          ? '<div><b>Netaansluiting bekend:</b> ' + (s.netKnown === "ja" ? "Ja — " + esc(s.netSize || "") : "Nee — verdiepende scan gewenst") + "</div>"
259          : "") +
260        (s.netOver ? '<div style="color:var(--red)"><b>Let op:</b> aansluiting > 3×80A — energiescan noodzakelijk.</div>' : "") +
261        (s.laadKnown
262          ? '<div><b>Laadpalen nodig:</b> ' + (s.laadKnown === "ja" ? s.laadAantal + " laadplek" + (s.laadAantal === 1 ? "" : "ken") : "Nog niet bekend") + "</div>"
263          : "") +
264        (s.eisen && s.eisen.length ? '<div><b>Eisen / kansen:</b> ' + esc(s.eisen.join(", ")) + "</div>" : "");
265      const rec = $("recap");
266      rec.innerHTML =
267        '<div><b>Aanvraagnummer:</b> ' + esc(nr) + "</div>" +
268        '<div><b>Naam:</b> ' + esc(s.first + " " + s.last) + "</div>" +
269        '<div><b>Telefoon:</b> ' + esc(s.phone) + "</div>" +
270        '<div><b>Bedrijf:</b> ' + esc(s.company) + "</div>" +
271        '<div><b>Project:</b> ' + esc(s.project) + "</div>" +
272        '<div><b>Locatie:</b> ' + esc(s.addr) + "</div>" +
273        (s.start ? '<div><b>Looptijd:</b> ' + esc(s.start) + (s.end ? " t/m " + esc(s.end) : "") + "</div>" : "") +
274        intakeLines +
275        (attachments.length ? '<div><b>Bijlagen:</b> ' + attachments.length + "</div>" : "") +
276        '<p class="hint" style="margin-top:10px">Bewaar je aanvraagnummer. Je ontvangt een bevestiging per e-mail en een expert neemt binnen 2 werkdagen contact met je op.</p>';
277      $("success").classList.add("show");
278      $("success").scrollIntoView({ behavior: "smooth", block: "center" });
279    } catch (e) {
280      Loket.toast("Versturen mislukt: " + (e.message || e), "err");
281      btn.disabled = false; btn.textContent = lbl;
282    }
283  }
284
285  // ---- init ----
286  $("wzNext").onclick = next;
287  $("wzBack").onclick = back;
288  $("wzAddrBtn").onclick = geocode;
289
290  // netaansluiting bekend? ja/nee
291  [["netJaBtn", "ja"], ["netNeeBtn", "nee"]].forEach(([id, val]) => {
292    const b = $(id);
293    if (b) b.onclick = () => {
294      wz.s.netKnown = val; markChoice([$("netJaBtn"), $("netNeeBtn")], b);
295      $("netJa").classList.toggle("hideblock", val !== "ja");
296      $("netNee").classList.toggle("hideblock", val !== "nee");
297      $("wzMsg").style.display = "none";
298      updateNetAlarm();
299      if (val === "ja") setTimeout(() => $("wzNetSize").focus(), 30);
300    };
301  });
302  // laadpalen nodig? ja/nee
303  [["laadJaBtn", "ja"], ["laadNeeBtn", "nee"]].forEach(([id, val]) => {
304    const b = $(id);
305    if (b) b.onclick = () => {
306      wz.s.laadKnown = val; markChoice([$("laadJaBtn"), $("laadNeeBtn")], b);
307      $("laadJa").classList.toggle("hideblock", val !== "ja");
308      $("laadNee").classList.toggle("hideblock", val !== "nee");
309      $("wzMsg").style.display = "none";
310      if (val === "ja") setTimeout(() => $("wzLaadAantal").focus(), 30);
311    };
312  });
313  window.wzLaadBump = function (n) {
314    const el = $("wzLaadAantal"); if (!el) return;
315    const v = Math.max(1, (parseInt(el.value) || 1) + n);
316    el.value = v; wz.s.laadAantal = v;
317  };
318
319  if ($("wzNetSize")) $("wzNetSize").addEventListener("input", updateNetAlarm);
320  if ($("wzNetAsk")) $("wzNetAsk").onclick = () => next();
321
322  root.querySelectorAll("input").forEach((i) => {
323    if (i.type !== "date" && i.type !== "checkbox") {
324      i.addEventListener("keydown", (e) => { if (e.key === "Enter" && i.id !== "wzAddr") { e.preventDefault(); next(); } });
325    }
326  });
327  show(false);
328})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.