Add multi-inspection-type routing for Wissel, Overweg, and Spoor

Route inspection forms by objectsoort/EQART from CSV or SAP XML.
Each type gets its own header fields, assessment table layout,
overview photo grid, and XML export format. CSS toggles type-specific
sections via body class. SW cache bumped to v3.
This commit is contained in:
Randy Fischer 2026-04-16 08:09:02 +02:00
parent b005193dbc
commit f2f9b65916
9 changed files with 422 additions and 124 deletions

View File

@ -78,6 +78,39 @@
.header-grid .label { background: var(--header-bg); font-weight: 600; color: #333; }
.header-grid .value { background: #fff; color: #000; }
/* Type-based visibility: hide type-specific sections by default */
.header-grid.wissel-only,
.header-grid.overweg-only,
.header-grid.spoor-only { display: none; }
body.type-wissel .header-grid.wissel-only { display: grid; }
body.type-overweg .header-grid.overweg-only { display: grid; }
body.type-spoor .header-grid.spoor-only { display: grid; }
.wissel-overview { display: none; }
body.type-wissel .wissel-overview { display: grid; }
.overweg-overview { display: none; }
body.type-overweg .overweg-overview { display: block; }
.spoor-overview { display: none; }
body.type-spoor .spoor-overview { display: block; }
.instruction-box.wissel-only,
.instruction-box.overweg-only,
.instruction-box.spoor-only { display: none; }
body.type-wissel .instruction-box.wissel-only { display: block; }
body.type-overweg .instruction-box.overweg-only { display: block; }
body.type-spoor .instruction-box.spoor-only { display: block; }
.svg-link-row { display: none; }
body.type-wissel .svg-link-row { display: block; }
.ov-photo-grid {
display: grid; grid-template-columns: repeat(3, 1fr);
gap: 12px; max-width: 500px; margin: 0 auto; padding: 16px 0;
}
.ov-photo-grid .capture-label { padding: 12px 8px; }
.task-section {
display: grid; grid-template-columns: 180px 1fr;
border-bottom: 1px solid var(--border);

View File

@ -125,32 +125,42 @@
<div class="tab-panel active" id="panel-inspectie">
<div class="form-container" id="formContainer">
<div class="form-title">Duimstokformulier Gewoon of Symmetrisch wissel</div>
<div class="form-title" id="formTitle">Duimstokformulier</div>
<div class="header-grid">
<div class="cell label">Geocode</div><div class="cell value" id="hdr_geo"></div>
<div class="cell label">Generatie</div><div class="cell value" id="hdr_generatie"></div>
<div class="cell label">Geocode omschrijving</div><div class="cell value" id="hdr_geotxt"></div>
<div class="cell label">Profiel</div><div class="cell value" id="hdr_profiel"></div>
<div class="cell label">Equipmentnummer</div><div class="cell value" id="hdr_equnr"></div>
<div class="cell label">Wisselligger soort</div><div class="cell value" id="hdr_dwarsligger"></div>
<div class="cell label">Wisselnummer</div><div class="cell value" id="hdr_wisselnr"></div>
<div class="cell label">Achterkant afwijking</div><div class="cell value" id="hdr_afwijking"></div>
<div class="cell label">Objectsoort</div><div class="cell value" id="hdr_eqart"></div>
<div class="cell label">Hergebruikt object?</div><div class="cell value" id="hdr_hergebruikt"></div>
<div class="cell label">Wisselsoort</div><div class="cell value" id="hdr_soort"></div>
<div class="cell label">Aangesloten wisselverwarming</div><div class="cell value" id="hdr_wisselverw"></div>
<div class="cell label">Startpunt</div><div class="cell value" id="hdr_startpoint"></div>
<div class="cell label">Hoofdspoor/Zijspoor</div><div class="cell value" id="hdr_spoor"></div>
<div class="cell label">Eindpunt</div><div class="cell value" id="hdr_endpoint"></div>
<div class="cell label">Omschrijving</div><div class="cell value" id="hdr_omschrijving"></div>
<div class="cell label">Order-operatie</div><div class="cell value" id="hdr_orderoperatie"></div>
<div class="cell label">Afschrijvingsgroep</div><div class="cell value" id="hdr_afschr"></div>
<div class="cell label">Plaatsingsdatum</div><div class="cell value" id="hdr_plaatsingsdatum"></div>
<div class="cell label">Hoofdspoor/Zijspoor</div><div class="cell value" id="hdr_spoor"></div>
</div>
<div class="header-grid wissel-only">
<div class="cell label">Wisselnummer</div><div class="cell value" id="hdr_wisselnr"></div>
<div class="cell label">Wisselsoort</div><div class="cell value" id="hdr_soort"></div>
<div class="cell label">Hoekverhouding</div><div class="cell value" id="hdr_hoekverhouding"></div>
<div class="cell label">Profiel</div><div class="cell value" id="hdr_profiel"></div>
<div class="cell label">Wisselligger soort</div><div class="cell value" id="hdr_dwarsligger"></div>
<div class="cell label">Achterkant afwijking</div><div class="cell value" id="hdr_afwijking"></div>
<div class="cell label">Hergebruikt object?</div><div class="cell value" id="hdr_hergebruikt"></div>
<div class="cell label">Aangesloten wisselverwarming</div><div class="cell value" id="hdr_wisselverw"></div>
<div class="cell label">Generatie</div><div class="cell value" id="hdr_generatie"></div>
<div class="cell label">Mee-/tegengebogen wissel</div><div class="cell value" id="hdr_meetegengebogen"></div>
<div class="cell label">Wissel classificatie</div><div class="cell value" id="hdr_classificatie"></div>
<div class="cell label">Voegloos</div><div class="cell value" id="hdr_voegloos"></div>
<div class="cell label">Plaatsingsdatum</div><div class="cell value" id="hdr_plaatsingsdatum"></div>
<div class="cell label">Hoekverhouding</div><div class="cell value" id="hdr_hoekverhouding"></div>
<div class="cell label">Aanschafdatum</div><div class="cell value" id="hdr_aanschafdatum"></div>
<div class="cell label">Omschrijving</div><div class="cell value" id="hdr_omschrijving"></div>
<div class="cell label">Order-operatie</div><div class="cell value" id="hdr_orderoperatie"></div>
</div>
<div class="header-grid overweg-only">
<div class="cell label">Type bevloering</div><div class="cell value" id="hdr_overwegtype"></div>
<div class="cell label">Bovenliggende overweg</div><div class="cell value" id="hdr_overwegbasis"></div>
</div>
<div class="header-grid spoor-only">
<div class="cell label">Type spoor</div><div class="cell value" id="hdr_spoortype"></div>
<div class="cell label">BVS Code</div><div class="cell value" id="hdr_bvscode"></div>
</div>
<div class="task-section">
<div class="label">Taak omschrijving</div><div class="value" id="hdr_taakomschrijving"></div>
@ -176,7 +186,7 @@
<div class="cell label">Fotonummers</div><div class="cell fotonummers-cell" id="totaal_fotonummers"></div>
</div>
<table class="assessment-table" id="assessmentTable">
<thead><tr>
<thead id="assessmentHead"><tr>
<th class="nr-col">Nr</th><th class="loc-col">Locatie in wissel</th>
<th class="score-col">DWL Score</th><th class="score-col">BAL Score</th>
<th class="foto-col">Foto's</th><th class="opm-col">Opmerkingen / bijzonderheden</th>
@ -198,12 +208,24 @@
<div class="tab-panel" id="panel-overzicht">
<div class="overview-container">
<div class="overview-title" id="overzichtTitle">Gewoon of Symmetrisch wissel</div>
<div class="instruction-box">
<div class="instruction-box wissel-only">
<strong>Instructie:</strong> Eerste foto bevat het bordje met het wisselnummer.
Daarna dienen de foto's buitenom of rechtsom in volgorde te worden gemaakt.
Foto's met bijzonderheden dienen in het opmerkingen-veld begeleid te worden
in combinatie met het fotonummer.
</div>
<div class="instruction-box overweg-only">
<strong>Instructie:</strong> Maak overzichtsfoto's vanuit beide richtingen.
Maak per beoordelingslocatie minimaal een foto van de situatie.
Foto's met bijzonderheden dienen in het opmerkingen-veld begeleid te worden
in combinatie met het fotonummer.
</div>
<div class="instruction-box spoor-only">
<strong>Instructie:</strong> Maak overzichtsfoto's vanuit beide richtingen van het spoorvak.
Maak per beoordeeld segment minimaal een foto via de beoordelingstabel.
Foto's met bijzonderheden dienen in het opmerkingen-veld begeleid te worden
in combinatie met het fotonummer.
</div>
<div class="wissel-overview">
<div class="ovz-row">
<label class="capture-label" data-pos="ovz_tl">
@ -253,6 +275,54 @@
</label>
</div>
</div>
<div class="overweg-overview">
<div class="ov-photo-grid">
<label class="capture-label" data-pos="ovz_ow_1">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_ovz_ow_1"></span></div>
<span class="cap-text">Overzichtsfoto 1</span>
<input type="file" accept="image/*" capture="environment" data-pos="ovz_ow_1">
</label>
<label class="capture-label" data-pos="ovz_ow_2">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_ovz_ow_2"></span></div>
<span class="cap-text">Overzichtsfoto 2</span>
<input type="file" accept="image/*" capture="environment" data-pos="ovz_ow_2">
</label>
<label class="capture-label" data-pos="foto_ligging_spoor">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_foto_ligging_spoor"></span></div>
<span class="cap-text">Ligging spoor</span>
<input type="file" accept="image/*" capture="environment" data-pos="foto_ligging_spoor">
</label>
<label class="capture-label" data-pos="foto_ligging_bevl">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_foto_ligging_bevl"></span></div>
<span class="cap-text">Ligging bevloering</span>
<input type="file" accept="image/*" capture="environment" data-pos="foto_ligging_bevl">
</label>
<label class="capture-label" data-pos="foto_constr_kwal">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_foto_constr_kwal"></span></div>
<span class="cap-text">Constructieve kwaliteit</span>
<input type="file" accept="image/*" capture="environment" data-pos="foto_constr_kwal">
</label>
<label class="capture-label" data-pos="foto_wegverharding">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_foto_wegverharding"></span></div>
<span class="cap-text">Wegverharding</span>
<input type="file" accept="image/*" capture="environment" data-pos="foto_wegverharding">
</label>
</div>
</div>
<div class="spoor-overview">
<div class="ov-photo-grid">
<label class="capture-label" data-pos="ovz_sp_1">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_ovz_sp_1"></span></div>
<span class="cap-text">Overzichtsfoto begin</span>
<input type="file" accept="image/*" capture="environment" data-pos="ovz_sp_1">
</label>
<label class="capture-label" data-pos="ovz_sp_2">
<div class="cap-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span class="cap-badge" id="cbadge_ovz_sp_2"></span></div>
<span class="cap-text">Overzichtsfoto einde</span>
<input type="file" accept="image/*" capture="environment" data-pos="ovz_sp_2">
</label>
</div>
</div>
<div class="overview-thumbs" id="overviewThumbs">
<h4>Vastgelegde foto's</h4>
<div class="thumb-grid" id="overviewThumbGrid">
@ -299,6 +369,7 @@
<script src="js/namespace.js"></script>
<script src="../src/Domain/sectionMap.js"></script>
<script src="../src/Domain/scoring.js"></script>
<script src="../src/Domain/inspectionTypes.js"></script>
<script src="../src/Domain/orderParser.js"></script>
<script src="../src/Infrastructure/utils.js"></script>
<script src="../src/Infrastructure/geolocation.js"></script>

View File

@ -1,4 +1,4 @@
const CACHE_VERSION = 'duimstok-v2';
const CACHE_VERSION = 'duimstok-v3';
const APP_SHELL = [
'./',
@ -15,6 +15,7 @@ const APP_SHELL = [
'./js/main.js',
'../src/Domain/sectionMap.js',
'../src/Domain/scoring.js',
'../src/Domain/inspectionTypes.js',
'../src/Domain/orderParser.js',
'../src/Infrastructure/utils.js',
'../src/Infrastructure/geolocation.js',

View File

@ -1,40 +1,56 @@
(function (A, I) {
(function (A, D, I) {
function exportScores(fd, x) {
var type = fd.type;
x.push('<BEOORDELINGEN>');
for (var [nr, data] of Object.entries(fd.scores)) {
var fNrs = (fd.photos[nr] || []).map(function (_, i) { return fd.orderNr + '_loc' + nr + '_foto' + (i + 1); }).join('; ');
if (type === D.TYPE_WISSEL) {
x.push('<WISSEL_SCORE><NR>' + nr + '</NR><DWL_SCORE>' + I.esc(data.dwl || '') + '</DWL_SCORE><BAL_SCORE>' + I.esc(data.bal || '') + '</BAL_SCORE><FOTO_NR>' + I.esc(fNrs) + '</FOTO_NR><OPMERKING>' + I.esc(data.opm || '') + '</OPMERKING></WISSEL_SCORE>');
} else if (type === D.TYPE_OVERWEG) {
x.push('<OVERWEG_SCORE><LOCATIE>' + I.esc(data.locatie || '') + '</LOCATIE><SCORE>' + I.esc(data.score || '') + '</SCORE></OVERWEG_SCORE>');
} else if (type === D.TYPE_SPOOR) {
x.push('<SPOOR_STUK><NR>' + nr + '</NR><KM_VAN>' + I.esc(data.kmVan || '') + '</KM_VAN><KM_TOT>' + I.esc(data.kmTot || '') + '</KM_TOT><LENGTE>' + I.esc(data.lengte || '') + '</LENGTE><TECH_JAAR>' + I.esc(data.techJaar || '') + '</TECH_JAAR><INSP_JAAR>' + I.esc(data.inspJaar || '') + '</INSP_JAAR><SCORE>' + I.esc(data.score || '') + '</SCORE><FOTO_NR>' + I.esc(fNrs) + '</FOTO_NR></SPOOR_STUK>');
}
}
x.push('</BEOORDELINGEN>');
}
A.exportFormData = async function () {
await A.saveCurrentForm();
const fd = A.state.formData;
const x = ['<?xml version="1.0" encoding="utf-8"?>', '<FORM>'];
x.push(`<AUFNR_VORNR>${I.esc(fd.orderNr)}</AUFNR_VORNR>`);
x.push(`<INSPECTEUR>${I.esc(document.getElementById('inp_inspecteur').value)}</INSPECTEUR>`);
x.push(`<INSP_DATUM>${I.esc(document.getElementById('inp_inspectiedatum').value)}</INSP_DATUM>`);
x.push(`<TECH_JAAR>${I.esc(document.getElementById('inp_techjaar').value)}</TECH_JAAR>`);
x.push(`<INSP_JAAR>${I.esc(document.getElementById('inp_inspjaar').value)}</INSP_JAAR>`);
x.push(`<OPMERKING>${I.esc(document.getElementById('inp_opmerkingen').value)}</OPMERKING>`);
x.push('<OVERZICHTFOTOS>');
for (const [pos, photos] of Object.entries(fd.overviewPhotos)) {
photos.forEach((_, i) => x.push(`<FOTO><POSITIE>${I.esc(pos)}</POSITIE><NAAM>${I.esc(fd.orderNr.replace('/', '-'))}_${pos}_foto${i + 1}</NAAM></FOTO>`));
var fd = A.state.formData;
var x = ['<?xml version="1.0" encoding="utf-8"?>', '<FORM>'];
x.push('<AUFNR_VORNR>' + I.esc(fd.orderNr) + '</AUFNR_VORNR>');
x.push('<INSPECTEUR>' + I.esc(document.getElementById('inp_inspecteur').value) + '</INSPECTEUR>');
x.push('<INSP_DATUM>' + I.esc(document.getElementById('inp_inspectiedatum').value) + '</INSP_DATUM>');
x.push('<TECH_JAAR>' + I.esc(document.getElementById('inp_techjaar').value) + '</TECH_JAAR>');
x.push('<INSP_JAAR>' + I.esc(document.getElementById('inp_inspjaar').value) + '</INSP_JAAR>');
x.push('<OPMERKING>' + I.esc(document.getElementById('inp_opmerkingen').value) + '</OPMERKING>');
if (fd.type === D.TYPE_WISSEL) {
x.push('<OVERZICHTFOTOS>');
for (var [pos, photos] of Object.entries(fd.overviewPhotos)) {
photos.forEach(function (_, i) {
x.push('<FOTO><POSITIE>' + I.esc(pos) + '</POSITIE><NAAM>' + I.esc(fd.orderNr.replace('/', '-')) + '_' + pos + '_foto' + (i + 1) + '</NAAM></FOTO>');
});
}
x.push('</OVERZICHTFOTOS>');
}
x.push('</OVERZICHTFOTOS>');
x.push('<BEOORDELINGEN>');
for (const [nr, data] of Object.entries(fd.scores)) {
const fNrs = (fd.photos[nr] || []).map((_, i) => `${fd.orderNr}_loc${nr}_foto${i + 1}`).join('; ');
x.push(`<WISSEL_SCORE><NR>${nr}</NR><DWL_SCORE>${I.esc(data.dwl || '')}</DWL_SCORE><BAL_SCORE>${I.esc(data.bal || '')}</BAL_SCORE><FOTO_NR>${I.esc(fNrs)}</FOTO_NR><OPMERKING>${I.esc(data.opm || '')}</OPMERKING></WISSEL_SCORE>`);
}
x.push('</BEOORDELINGEN>', '</FORM>');
exportScores(fd, x);
x.push('</FORM>');
I.downloadBlob(new Blob([x.join('\n')], { type: 'application/xml' }),
`${fd.orderNr.replace('/', '-')}_inspectie_${new Date().toISOString().slice(0, 10)}.xml`);
let pc = 0;
for (const [nr, photos] of Object.entries(fd.photos)) {
photos.forEach((p, i) => {
fd.orderNr.replace('/', '-') + '_inspectie_' + new Date().toISOString().slice(0, 10) + '.xml');
var pc = 0;
for (var [nr, phs] of Object.entries(fd.photos)) {
phs.forEach(function (p, i) {
pc++;
I.downloadBlob(p.blob, `${fd.orderNr.replace('/', '-')}_loc${nr}_foto${i + 1}.jpg`);
I.downloadBlob(p.blob, fd.orderNr.replace('/', '-') + '_loc' + nr + '_foto' + (i + 1) + '.jpg');
});
}
for (const [pos, photos] of Object.entries(fd.overviewPhotos)) {
photos.forEach((p, i) => {
for (var [pos2, phs2] of Object.entries(fd.overviewPhotos)) {
phs2.forEach(function (p, i) {
pc++;
I.downloadBlob(p.blob, `${fd.orderNr.replace('/', '-')}_${pos}_foto${i + 1}.jpg`);
I.downloadBlob(p.blob, fd.orderNr.replace('/', '-') + '_' + pos2 + '_foto' + (i + 1) + '.jpg');
});
}
alert(`Opgeslagen!\n- 1 XML-bestand\n- ${pc} foto('s)`);
alert('Opgeslagen!\n- 1 XML-bestand\n- ' + pc + ' foto(\'s)');
};
})(window.App.Application, window.App.Infrastructure);
})(window.App.Application, window.App.Domain, window.App.Infrastructure);

View File

@ -1,4 +1,6 @@
(function (A, D, I) {
var CAMERA_SVG = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>';
A.switchTab = function (tabId) {
document.querySelectorAll('.tab-btn').forEach(btn =>
btn.classList.toggle('active', btn.dataset.tab === tabId));
@ -6,8 +8,28 @@
panel.classList.toggle('active', panel.id === 'panel-' + tabId));
};
A.applyFormType = function (type) {
A.state.formData.type = type;
var body = document.body;
body.classList.remove('type-wissel', 'type-overweg', 'type-spoor');
body.classList.add('type-' + type);
var thead = document.getElementById('assessmentHead');
if (type === D.TYPE_WISSEL) {
thead.innerHTML = '<tr><th class="nr-col">Nr</th><th class="loc-col">Locatie in wissel</th><th class="score-col">DWL Score</th><th class="score-col">BAL Score</th><th class="foto-col">Foto\'s</th><th class="opm-col">Opmerkingen</th></tr>';
document.getElementById('formTitle').textContent = 'Duimstokformulier Gewoon of Symmetrisch wissel';
} else if (type === D.TYPE_OVERWEG) {
thead.innerHTML = '<tr><th class="loc-col">Locatie</th><th class="score-col">Score</th><th class="foto-col">Foto\'s</th><th class="opm-col">Opmerkingen</th></tr>';
document.getElementById('formTitle').textContent = 'Duimstokformulier Overwegbevloering';
} else if (type === D.TYPE_SPOOR) {
thead.innerHTML = '<tr><th class="nr-col">Nr</th><th>KM van</th><th>KM tot</th><th>Lengte</th><th class="score-col">Score</th><th class="foto-col">Foto\'s</th><th class="opm-col">Opmerkingen</th></tr>';
document.getElementById('formTitle').textContent = 'Duimstokformulier Spoor';
}
};
A.resetForm = function () {
A.state.formData = A.emptyFormData();
document.body.classList.remove('type-wissel', 'type-overweg', 'type-spoor');
document.querySelectorAll('.header-grid .value').forEach(el => el.textContent = '');
document.querySelectorAll('#hdr_taakomschrijving, #hdr_aanleiding').forEach(el => el.textContent = '');
document.getElementById('inp_inspecteur').value = '';
@ -16,7 +38,7 @@
document.getElementById('inp_inspjaar').value = '';
document.getElementById('inp_opmerkingen').value = '';
document.getElementById('assessmentBody').innerHTML = '';
const totaal = document.getElementById('totaalscore');
var totaal = document.getElementById('totaalscore');
totaal.textContent = '-';
totaal.className = 'cell score-cell';
document.getElementById('totaal_fotonummers').textContent = '';
@ -29,6 +51,8 @@
};
A.prefillFromOrder = function (order) {
var type = D.typeFromObjectsoort(order.objectsoort);
A.applyFormType(type);
A.state.formData.orderNr = order.orderKey;
document.getElementById('hdr_omschrijving').textContent = order.omschrijving;
document.getElementById('hdr_eqart').textContent = order.objectsoort;
@ -45,77 +69,167 @@
D.SCORE_VALUES.map(v => `<option value="${v}" ${selected === v ? 'selected' : ''}>${v}</option>`).join('');
}
A.buildAssessmentTable = function (scoreElements) {
const tbody = document.getElementById('assessmentBody');
function photoButton(nr, locatie) {
return `<button class="foto-btn" data-action="open-photo" data-nr="${nr}" data-loc="${locatie.replace(/"/g, '&quot;')}">${CAMERA_SVG}Foto's<span class="badge" id="badge_${nr}"></span></button>`;
}
function wireAssessmentEvents() {
var tbody = document.getElementById('assessmentBody');
tbody.querySelectorAll('select[data-nr]').forEach(sel => sel.addEventListener('change', () => onScoreChange(sel)));
tbody.querySelectorAll('input[data-field="opm"]').forEach(inp => inp.addEventListener('input', () => onOpmChange(inp)));
tbody.querySelectorAll('input[data-field="techJaar"], input[data-field="inspJaar"]').forEach(inp =>
inp.addEventListener('input', () => onSpoorFieldChange(inp)));
tbody.querySelectorAll('button[data-action="open-photo"]').forEach(btn =>
btn.addEventListener('click', () => A.openPhotoModal(+btn.dataset.nr, btn.dataset.loc)));
}
A.buildWisselAssessment = function (scoreElements) {
var tbody = document.getElementById('assessmentBody');
tbody.innerHTML = '';
let currentSection = '';
var currentSection = '';
scoreElements.forEach(el => {
const nr = parseInt(el.querySelector('NR').textContent.trim());
const locatie = el.querySelector('LOCATIE').textContent.trim();
const dwlScore = el.querySelector('DWL_SCORE').textContent.trim();
const balScore = el.querySelector('BAL_SCORE').textContent.trim();
const section = D.getSectionForNr(nr);
var nr = parseInt(el.querySelector('NR').textContent.trim());
var locatie = el.querySelector('LOCATIE').textContent.trim();
var dwlScore = el.querySelector('DWL_SCORE').textContent.trim();
var balScore = el.querySelector('BAL_SCORE').textContent.trim();
var section = D.getSectionForNr(nr);
if (section && section !== currentSection) {
currentSection = section;
const sr = document.createElement('tr');
var sr = document.createElement('tr');
sr.className = 'section-row';
sr.innerHTML = `<td colspan="6">${section}</td>`;
sr.innerHTML = '<td colspan="6">' + section + '</td>';
tbody.appendChild(sr);
}
const tr = document.createElement('tr');
var tr = document.createElement('tr');
tr.dataset.nr = nr;
tr.innerHTML = `
<td class="nr-col">${nr}</td>
<td class="loc-col">${locatie}</td>
<td class="score-col editable-cell"><select data-nr="${nr}" data-type="dwl">${buildScoreOptions(dwlScore)}</select></td>
<td class="score-col editable-cell"><select data-nr="${nr}" data-type="bal">${buildScoreOptions(balScore)}</select></td>
<td class="foto-col"><button class="foto-btn" data-action="open-photo" data-nr="${nr}" data-loc="${locatie.replace(/"/g, '&quot;')}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>Foto's<span class="badge" id="badge_${nr}"></span></button></td>
<td class="opm-col editable-cell"><input type="text" data-nr="${nr}" data-field="opm" placeholder=""></td>`;
tr.innerHTML =
'<td class="nr-col">' + nr + '</td>' +
'<td class="loc-col">' + locatie + '</td>' +
'<td class="score-col editable-cell"><select data-nr="' + nr + '" data-type="dwl">' + buildScoreOptions(dwlScore) + '</select></td>' +
'<td class="score-col editable-cell"><select data-nr="' + nr + '" data-type="bal">' + buildScoreOptions(balScore) + '</select></td>' +
'<td class="foto-col">' + photoButton(nr, locatie) + '</td>' +
'<td class="opm-col editable-cell"><input type="text" data-nr="' + nr + '" data-field="opm" placeholder=""></td>';
tbody.appendChild(tr);
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = { dwl: dwlScore, bal: balScore, opm: '' };
if (!A.state.formData.photos[nr]) A.state.formData.photos[nr] = [];
const dwlSel = tr.querySelector('select[data-type="dwl"]');
var dwlSel = tr.querySelector('select[data-type="dwl"]');
if (dwlScore) dwlSel.className = D.scoreClass(dwlScore);
const balSel = tr.querySelector('select[data-type="bal"]');
var balSel = tr.querySelector('select[data-type="bal"]');
if (balScore) balSel.className = D.scoreClass(balScore);
});
wireAssessmentEvents();
};
tbody.querySelectorAll('select[data-nr]').forEach(sel => sel.addEventListener('change', () => onScoreChange(sel)));
tbody.querySelectorAll('input[data-field="opm"]').forEach(inp => inp.addEventListener('input', () => onOpmChange(inp)));
tbody.querySelectorAll('button[data-action="open-photo"]').forEach(btn =>
btn.addEventListener('click', () => A.openPhotoModal(+btn.dataset.nr, btn.dataset.loc)));
A.buildOverwegAssessment = function (scoreElements) {
var tbody = document.getElementById('assessmentBody');
tbody.innerHTML = '';
scoreElements.forEach((el, idx) => {
var nr = idx + 1;
var locatie = el.querySelector('LOCATIE').textContent.trim();
var score = el.querySelector('SCORE').textContent.trim();
var tr = document.createElement('tr');
tr.dataset.nr = nr;
tr.innerHTML =
'<td class="loc-col">' + locatie + '</td>' +
'<td class="score-col editable-cell"><select data-nr="' + nr + '" data-type="score">' + buildScoreOptions(score) + '</select></td>' +
'<td class="foto-col">' + photoButton(nr, locatie) + '</td>' +
'<td class="opm-col editable-cell"><input type="text" data-nr="' + nr + '" data-field="opm" placeholder=""></td>';
tbody.appendChild(tr);
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = { score: score, opm: '' };
if (!A.state.formData.photos[nr]) A.state.formData.photos[nr] = [];
var sel = tr.querySelector('select[data-type="score"]');
if (score) sel.className = D.scoreClass(score);
});
wireAssessmentEvents();
};
A.buildSpoorAssessment = function (scoreElements) {
var tbody = document.getElementById('assessmentBody');
tbody.innerHTML = '';
scoreElements.forEach(el => {
var nr = parseInt(el.querySelector('NR').textContent.trim());
var kmVan = el.querySelector('KM_VAN').textContent.trim();
var kmTot = el.querySelector('KM_TOT').textContent.trim();
var lengte = el.querySelector('LENGTE').textContent.trim();
var score = el.querySelector('SCORE').textContent.trim();
var techJaar = el.querySelector('TECH_JAAR') ? el.querySelector('TECH_JAAR').textContent.trim() : '';
var inspJaar = el.querySelector('INSP_JAAR') ? el.querySelector('INSP_JAAR').textContent.trim() : '';
var tr = document.createElement('tr');
tr.dataset.nr = nr;
tr.innerHTML =
'<td class="nr-col">' + nr + '</td>' +
'<td class="editable-cell">' + kmVan + '</td>' +
'<td class="editable-cell">' + kmTot + '</td>' +
'<td class="editable-cell">' + lengte + ' m</td>' +
'<td class="score-col editable-cell"><select data-nr="' + nr + '" data-type="score">' + buildScoreOptions(score) + '</select></td>' +
'<td class="foto-col">' + photoButton(nr, 'Segment ' + nr) + '</td>' +
'<td class="opm-col editable-cell"><input type="text" data-nr="' + nr + '" data-field="opm" placeholder=""></td>';
tbody.appendChild(tr);
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = { score: score, kmVan: kmVan, kmTot: kmTot, lengte: lengte, techJaar: techJaar, inspJaar: inspJaar, opm: '' };
if (!A.state.formData.photos[nr]) A.state.formData.photos[nr] = [];
var sel = tr.querySelector('select[data-type="score"]');
if (score) sel.className = D.scoreClass(score);
});
wireAssessmentEvents();
};
A.buildAssessmentForType = function (type, scoreElements) {
if (type === D.TYPE_WISSEL) A.buildWisselAssessment(scoreElements);
else if (type === D.TYPE_OVERWEG) A.buildOverwegAssessment(scoreElements);
else if (type === D.TYPE_SPOOR) A.buildSpoorAssessment(scoreElements);
};
function onScoreChange(sel) {
const nr = parseInt(sel.dataset.nr);
const type = sel.dataset.type;
const val = sel.value;
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = { dwl: '', bal: '', opm: '' };
A.state.formData.scores[nr][type] = val;
var nr = parseInt(sel.dataset.nr);
var field = sel.dataset.type;
var val = sel.value;
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = {};
A.state.formData.scores[nr][field] = val;
sel.className = D.scoreClass(val);
A.updateTotaalscore();
A.autoSave();
}
function onOpmChange(inp) {
const nr = parseInt(inp.dataset.nr);
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = { dwl: '', bal: '', opm: '' };
var nr = parseInt(inp.dataset.nr);
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = {};
A.state.formData.scores[nr].opm = inp.value;
A.autoSave();
}
function onSpoorFieldChange(inp) {
var nr = parseInt(inp.dataset.nr);
var field = inp.dataset.field;
if (!A.state.formData.scores[nr]) A.state.formData.scores[nr] = {};
A.state.formData.scores[nr][field] = inp.value;
A.autoSave();
}
A.updateTotaalscore = function () {
const worst = D.computeWorstScore(A.state.formData.scores);
const el = document.getElementById('totaalscore');
var type = A.state.formData.type;
var worst;
if (type === D.TYPE_WISSEL) {
worst = D.computeWorstScore(A.state.formData.scores);
} else {
var order = ['', ...D.SCORE_VALUES];
var wIdx = 0; var wScore = '';
for (var data of Object.values(A.state.formData.scores)) {
var s = data.score || '';
var m = order.indexOf(s);
if (m > wIdx) { wIdx = m; wScore = order[m]; }
}
worst = wScore;
}
var el = document.getElementById('totaalscore');
el.textContent = worst || '-';
el.className = 'cell score-cell';
if (worst) el.classList.add(D.scoreClass(worst));
};
A.loadSavedData = async function (orderNr) {
const saved = await I.loadInspection(orderNr);
var saved = await I.loadInspection(orderNr);
if (!saved) return;
if (saved.type) A.applyFormType(saved.type);
if (saved.inspecteur) document.getElementById('inp_inspecteur').value = saved.inspecteur;
if (saved.inspectiedatum) document.getElementById('inp_inspectiedatum').value = saved.inspectiedatum;
if (saved.techJaar) document.getElementById('inp_techjaar').value = saved.techJaar;
@ -123,12 +237,14 @@
if (saved.opmerkingen) document.getElementById('inp_opmerkingen').value = saved.opmerkingen;
if (saved.scores) {
A.state.formData.scores = saved.scores;
for (const [nr, data] of Object.entries(saved.scores)) {
const dS = document.querySelector(`select[data-nr="${nr}"][data-type="dwl"]`);
const bS = document.querySelector(`select[data-nr="${nr}"][data-type="bal"]`);
const oI = document.querySelector(`input[data-nr="${nr}"][data-field="opm"]`);
for (var [nr, data] of Object.entries(saved.scores)) {
var dS = document.querySelector('select[data-nr="' + nr + '"][data-type="dwl"]');
var bS = document.querySelector('select[data-nr="' + nr + '"][data-type="bal"]');
var sS = document.querySelector('select[data-nr="' + nr + '"][data-type="score"]');
var oI = document.querySelector('input[data-nr="' + nr + '"][data-field="opm"]');
if (dS && data.dwl) { dS.value = data.dwl; dS.className = D.scoreClass(data.dwl); }
if (bS && data.bal) { bS.value = data.bal; bS.className = D.scoreClass(data.bal); }
if (sS && data.score) { sS.value = data.score; sS.className = D.scoreClass(data.score); }
if (oI && data.opm) oI.value = data.opm;
}
A.updateTotaalscore();

View File

@ -13,7 +13,7 @@
function emptyFormData() {
return {
orderNr: '', inspecteur: '', inspectiedatum: '', techJaar: '', inspJaar: '',
orderNr: '', type: 'wissel', inspecteur: '', inspectiedatum: '', techJaar: '', inspJaar: '',
opmerkingen: '', scores: {}, photos: {}, overviewPhotos: {}
};
}

View File

@ -1,42 +1,64 @@
(function (A, I) {
A.parseAndLoadXML = function (xmlText) {
const parser = new DOMParser();
const xml = parser.parseFromString(xmlText, 'text/xml');
const getText = (tag) => { const el = xml.querySelector(tag); return el ? el.textContent.trim() : ''; };
(function (A, D, I) {
function setIfExists(id, value) {
var el = document.getElementById(id);
if (el && value) el.textContent = value;
}
const orderNr = getText('AUFNR_VORNR');
A.parseAndLoadXML = function (xmlText) {
var parser = new DOMParser();
var xml = parser.parseFromString(xmlText, 'text/xml');
var getText = function (tag) { var el = xml.querySelector(tag); return el ? el.textContent.trim() : ''; };
var eqart = getText('EQART');
var type = D.typeFromEqart(eqart);
A.applyFormType(type);
var orderNr = getText('AUFNR_VORNR');
A.state.formData.orderNr = orderNr;
A.state.currentOrderKey = orderNr;
document.getElementById('hdr_geo').textContent = getText('GEO');
document.getElementById('hdr_geotxt').textContent = getText('GEOTXT');
document.getElementById('hdr_equnr').textContent = getText('EQUNR');
document.getElementById('hdr_wisselnr').textContent = getText('WISSELNR') || getText('EQFNR_EQUI');
document.getElementById('hdr_eqart').textContent = getText('EQART');
document.getElementById('hdr_soort').textContent = getText('SOORT');
document.getElementById('hdr_startpoint').textContent = getText('START_POINT');
document.getElementById('hdr_endpoint').textContent = getText('END_POINT');
document.getElementById('hdr_hoekverhouding').textContent = getText('HOEKVERHOUDING');
document.getElementById('hdr_omschrijving').textContent = getText('EQKTX');
document.getElementById('hdr_orderoperatie').textContent = getText('AUFNR_VORNR');
document.getElementById('hdr_profiel').textContent = getText('PROFIEL');
document.getElementById('hdr_dwarsligger').textContent = getText('DWARSLIGGER') || '-';
document.getElementById('hdr_afwijking').textContent = getText('AFWIJKING') || '-';
document.getElementById('hdr_hergebruikt').textContent = '-';
document.getElementById('hdr_wisselverw').textContent = '-';
document.getElementById('hdr_spoor').textContent = getText('SPOOR');
document.getElementById('hdr_afschr').textContent = getText('ZAFSCHR_GRP_CODE');
document.getElementById('hdr_meetegengebogen').textContent = '-';
document.getElementById('hdr_classificatie').textContent = '-';
document.getElementById('hdr_voegloos').textContent = '-';
document.getElementById('hdr_plaatsingsdatum').textContent = I.formatDate(getText('DATUM_START'));
document.getElementById('hdr_aanschafdatum').textContent = '-';
document.getElementById('hdr_generatie').textContent = '-';
document.getElementById('hdr_taakomschrijving').textContent = getText('LTXA1');
document.getElementById('hdr_aanleiding').textContent = getText('KURZTEXT');
setIfExists('hdr_geo', getText('GEO'));
setIfExists('hdr_geotxt', getText('GEOTXT'));
setIfExists('hdr_equnr', getText('EQUNR'));
setIfExists('hdr_eqart', eqart);
setIfExists('hdr_startpoint', getText('START_POINT'));
setIfExists('hdr_endpoint', getText('END_POINT'));
setIfExists('hdr_omschrijving', getText('EQKTX'));
setIfExists('hdr_orderoperatie', getText('AUFNR_VORNR'));
setIfExists('hdr_spoor', getText('SPOOR'));
setIfExists('hdr_afschr', getText('ZAFSCHR_GRP_CODE'));
setIfExists('hdr_plaatsingsdatum', I.formatDate(getText('DATUM_START')));
setIfExists('hdr_taakomschrijving', getText('LTXA1'));
setIfExists('hdr_aanleiding', getText('KURZTEXT'));
const eqktx = getText('EQKTX');
if (eqktx) document.getElementById('overzichtTitle').textContent = eqktx;
if (type === D.TYPE_WISSEL) {
setIfExists('hdr_wisselnr', getText('WISSELNR') || getText('EQFNR_EQUI'));
setIfExists('hdr_soort', getText('SOORT'));
setIfExists('hdr_hoekverhouding', getText('HOEKVERHOUDING'));
setIfExists('hdr_profiel', getText('PROFIEL'));
setIfExists('hdr_dwarsligger', getText('DWARSLIGGER') || '-');
setIfExists('hdr_afwijking', getText('AFWIJKING') || '-');
setIfExists('hdr_hergebruikt', '-');
setIfExists('hdr_wisselverw', '-');
setIfExists('hdr_meetegengebogen', '-');
setIfExists('hdr_classificatie', '-');
setIfExists('hdr_voegloos', '-');
setIfExists('hdr_aanschafdatum', '-');
setIfExists('hdr_generatie', '-');
}
if (type === D.TYPE_OVERWEG) {
setIfExists('hdr_overwegtype', getText('TYPE'));
setIfExists('hdr_overwegbasis', getText('EQUKTX_BASIS'));
}
if (type === D.TYPE_SPOOR) {
setIfExists('hdr_spoortype', getText('TYPE'));
setIfExists('hdr_bvscode', getText('BVS_CODE'));
}
var eqktx = getText('EQKTX');
if (eqktx) setIfExists('overzichtTitle', eqktx);
document.getElementById('formToolbarTitle').textContent = eqktx || orderNr;
if (getText('INSPECTEUR')) document.getElementById('inp_inspecteur').value = getText('INSPECTEUR');
@ -45,8 +67,11 @@
if (getText('INSP_JAAR')) document.getElementById('inp_inspjaar').value = getText('INSP_JAAR');
if (getText('OPMERKING')) document.getElementById('inp_opmerkingen').value = getText('OPMERKING');
A.buildAssessmentTable(xml.querySelectorAll('WISSEL_SCORE'));
if (type === D.TYPE_WISSEL) A.buildWisselAssessment(xml.querySelectorAll('WISSEL_SCORE'));
else if (type === D.TYPE_OVERWEG) A.buildOverwegAssessment(xml.querySelectorAll('OVERWEG_SCORE'));
else if (type === D.TYPE_SPOOR) A.buildSpoorAssessment(xml.querySelectorAll('SPOOR_STUK'));
document.getElementById('statusOrder').textContent = 'Order: ' + orderNr + ' | ' + eqktx;
A.loadSavedData(orderNr);
};
})(window.App.Application, window.App.Infrastructure);
})(window.App.Application, window.App.Domain, window.App.Infrastructure);

View File

@ -0,0 +1,32 @@
(function (D) {
D.TYPE_WISSEL = 'wissel';
D.TYPE_OVERWEG = 'overweg';
D.TYPE_SPOOR = 'spoor';
D.EQART_TO_TYPE = {
'WISSEL': D.TYPE_WISSEL,
'OVERWBEVL': D.TYPE_OVERWEG,
'SPOORDWL': D.TYPE_SPOOR
};
D.OBJECTSOORT_TO_TYPE = {
'Wissel': D.TYPE_WISSEL,
'Overwegbevloering': D.TYPE_OVERWEG,
'Spoor': D.TYPE_SPOOR
};
D.typeFromEqart = function (eqart) {
return D.EQART_TO_TYPE[eqart] || D.TYPE_WISSEL;
};
D.typeFromObjectsoort = function (objectsoort) {
return D.OBJECTSOORT_TO_TYPE[objectsoort] || D.TYPE_WISSEL;
};
D.OVERWEG_LOCATIONS = [
'Ligging spoor',
'Ligging bevloering',
'Constructieve kwaliteit',
'Wegverharding'
];
})(window.App.Domain);

View File

@ -17,6 +17,10 @@
ovz_bl: 'Overzichtsfoto LO', ovz_br: 'Overzichtsfoto RO',
foto_s3_l: 'Foto Puntstuk L', foto_s3_r: 'Foto Puntstuk R',
foto_s2_l: 'Foto Midden L', foto_s2_r: 'Foto Midden R',
foto_s1_l: 'Foto Tong L', foto_s1_r: 'Foto Tong R'
foto_s1_l: 'Foto Tong L', foto_s1_r: 'Foto Tong R',
ovz_ow_1: 'Overzichtsfoto 1', ovz_ow_2: 'Overzichtsfoto 2',
foto_ligging_spoor: 'Ligging spoor', foto_ligging_bevl: 'Ligging bevloering',
foto_constr_kwal: 'Constructieve kwaliteit', foto_wegverharding: 'Wegverharding',
ovz_sp_1: 'Overzichtsfoto begin', ovz_sp_2: 'Overzichtsfoto einde'
};
})(window.App.Domain);