You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1126 lines
34 KiB

<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="EasySmartInventory v1.0.0">
<title>Machine_Inventarisatie</title>
<style>
:root {
--primary: #0066cc;
--secondary: #28a745;
--background: #f8f9fa;
--text: #333333;
--error: #dc3545;
--success: #28a745;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
background: white;
color: var(--text);
line-height: 1.7;
min-height: 100vh;
padding: 40px 20px;
}
.container {
max-width: 700px;
margin: 0 auto;
}
header {
margin-bottom: 50px;
padding-bottom: 30px;
border-bottom: 1px solid #eee;
}
header h1 {
font-size: 2em;
font-weight: 300;
color: var(--text);
}
header .version {
color: #999;
font-size: 0.85em;
margin-top: 5px;
}
.logo { max-height: 40px; margin-bottom: 20px; }
.section {
margin-bottom: 50px;
}
.section h2 {
font-size: 0.9em;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 2px;
color: #999;
margin-bottom: 25px;
}
.section-desc {
color: #666;
font-size: 0.9em;
margin-top: -20px;
margin-bottom: 25px;
}
.field-group {
margin-bottom: 25px;
}
label {
display: block;
font-size: 0.9em;
margin-bottom: 8px;
color: #666;
}
label .required {
color: var(--error);
}
input[type="text"],
input[type="number"],
input[type="date"],
select,
textarea {
width: 100%;
padding: 12px 0;
border: none;
border-bottom: 1px solid #ddd;
font-size: 1em;
background: transparent;
transition: border-color 0.2s;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-bottom-color: var(--primary);
}
input.invalid, select.invalid, textarea.invalid {
border-bottom-color: var(--error);
}
.error-message {
color: var(--error);
font-size: 0.8em;
margin-top: 5px;
display: none;
}
.invalid + .error-message { display: block; }
select {
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0 center;
padding-right: 20px;
}
.checkbox-group, .multiselect-group {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.checkbox-item, .multiselect-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.95em;
}
.checkbox-item input, .multiselect-item input {
width: 18px;
height: 18px;
accent-color: var(--primary);
}
.toggle-container {
display: flex;
align-items: center;
gap: 12px;
}
.toggle {
position: relative;
width: 40px;
height: 20px;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
position: absolute;
cursor: pointer;
inset: 0;
background: #ddd;
border-radius: 20px;
transition: 0.3s;
}
.toggle-slider::before {
content: '';
position: absolute;
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
background: white;
border-radius: 50%;
transition: 0.3s;
}
.toggle input:checked + .toggle-slider {
background: var(--primary);
}
.toggle input:checked + .toggle-slider::before {
transform: translateX(20px);
}
.photo-container {
border: 1px solid #eee;
padding: 40px;
text-align: center;
cursor: pointer;
transition: border-color 0.2s;
}
.photo-container:hover {
border-color: var(--primary);
}
.photo-container.has-photo {
padding: 15px;
}
.photo-preview {
max-width: 100%;
max-height: 250px;
}
.photo-buttons {
margin-top: 15px;
display: flex;
gap: 10px;
justify-content: center;
}
.btn {
padding: 12px 30px;
border: 1px solid;
font-size: 0.9em;
cursor: pointer;
background: transparent;
transition: all 0.2s;
}
.btn-primary {
border-color: var(--primary);
color: var(--primary);
}
.btn-primary:hover {
background: var(--primary);
color: white;
}
.btn-secondary {
border-color: #999;
color: #999;
}
.btn-secondary:hover {
background: #999;
color: white;
}
.btn-success {
border-color: var(--success);
color: var(--success);
}
.btn-success:hover {
background: var(--success);
color: white;
}
.btn-danger {
border-color: var(--error);
color: var(--error);
}
.btn-danger:hover {
background: var(--error);
color: white;
}
.actions {
margin-top: 50px;
padding-top: 30px;
border-top: 1px solid #eee;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.status-bar {
margin-top: 30px;
font-size: 0.8em;
color: #999;
display: flex;
justify-content: space-between;
}
.save-indicator {
display: flex;
align-items: center;
gap: 6px;
}
.toast {
position: fixed;
bottom: 30px;
right: 30px;
padding: 15px 25px;
background: var(--text);
color: white;
transform: translateY(100px);
opacity: 0;
transition: all 0.3s;
z-index: 1000;
}
.toast.show { transform: translateY(0); opacity: 1; }
.toast.success { background: var(--success); }
.toast.error { background: var(--error); }
@media (max-width: 768px) {
body { padding: 20px 15px; }
header h1 { font-size: 1.5em; }
.section { margin-bottom: 35px; }
.actions { flex-direction: column; }
.btn { width: 100%; text-align: center; }
}
.photo-container.has-photo .photo-placeholder { display: none; }
.photo-container.has-photo .photo-preview { display: block !important; }
.photo-container.has-photo .photo-buttons { display: flex !important; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>Machine_Inventarisatie</h1>
<div class="version">Versie 1.0.0</div>
</header>
<form id="inventory-form" onsubmit="return false;">
<section class="section">
<h2>Basisinformatie</h2>
<p class="section-desc">Algemene gegevens van het apparaat</p>
<div class="field-group">
<label for="serienummer">Serienummer<span class="required">*</span></label>
<input type="text" id="serienummer" data-required="true" data-min-length="3" placeholder="Voer serienummer in">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="type_nummer">Type-/modelnummer</label>
<input type="text" id="type_nummer" placeholder="Bijv. HP ProBook 450 G8">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="merk">Merk / fabrikant<span class="required">*</span></label>
<select id="merk" data-required="true">
<option value="">-- Selecteer --</option>
<option value="HP">HP</option>
<option value="Dell">Dell</option>
<option value="Lenovo">Lenovo</option>
<option value="Apple">Apple</option>
<option value="ASUS">ASUS</option>
<option value="Acer">Acer</option>
<option value="Microsoft">Microsoft</option>
<option value="Samsung">Samsung</option>
<option value="Anders">Anders</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="soort_apparaat">Soort apparaat / categorie<span class="required">*</span></label>
<select id="soort_apparaat" data-required="true">
<option value="">-- Selecteer --</option>
<option value="Laptop">Laptop</option>
<option value="Desktop">Desktop</option>
<option value="Server">Server</option>
<option value="Printer">Printer</option>
<option value="Scanner">Scanner</option>
<option value="Monitor">Monitor</option>
<option value="Netwerkapparatuur">Netwerkapparatuur</option>
<option value="Mobiel apparaat">Mobiel apparaat</option>
<option value="Anders">Anders</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="productienaam">Productienaam of -omschrijving</label>
<input type="text" id="productienaam" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="productiejaar">Productiejaar</label>
<input type="number" id="productiejaar" min="1990" max="2030" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="aanschafdatum">Aanschafdatum</label>
<input type="date" id="aanschafdatum" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="aankoopprijs">Aankoopprijs of vervangingswaarde (€)</label>
<input type="number" id="aankoopprijs" min="0" placeholder="0.00">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="asset_id">Intern identificatienummer (asset-ID)</label>
<input type="text" id="asset_id" placeholder="Bijv. AST-2024-001">
<div class="error-message"></div>
</div>
</section>
<section class="section">
<h2>Locatie &amp; Toewijzing</h2>
<p class="section-desc">Waar bevindt het apparaat zich en wie is verantwoordelijk</p>
<div class="field-group">
<label for="locatie_adres">Locatienummer of adres</label>
<input type="text" id="locatie_adres" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="ruimte">Ruimte / afdeling / verdieping</label>
<input type="text" id="ruimte" placeholder="Bijv. Kantoor 2.15, IT-afdeling">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="gebruiker">Gebruiker / verantwoordelijke persoon</label>
<input type="text" id="gebruiker" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="vestiging">Vestiging / filiaal</label>
<select id="vestiging" >
<option value="">-- Selecteer --</option>
<option value="Hoofdkantoor">Hoofdkantoor</option>
<option value="Vestiging Noord">Vestiging Noord</option>
<option value="Vestiging Zuid">Vestiging Zuid</option>
<option value="Vestiging Oost">Vestiging Oost</option>
<option value="Vestiging West">Vestiging West</option>
<option value="Thuiswerker">Thuiswerker</option>
<option value="Anders">Anders</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="mobiel_gebruik">Mobiel gebruik (werkplek/voertuig)</label>
<div class="toggle-container">
<label class="toggle">
<input type="checkbox" id="mobiel_gebruik" class="boolean-field">
<span class="toggle-slider"></span>
</label>
<span>Ja</span>
</div>
</div>
</section>
<section class="section">
<h2>Technische gegevens</h2>
<p class="section-desc">Technische specificaties en netwerkinformatie</p>
<div class="field-group">
<label for="specificaties">Specificaties (vermogen, capaciteit, etc.)</label>
<textarea id="specificaties" rows="3" placeholder="Bijv. Intel i7, 16GB RAM, 512GB SSD"></textarea>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="component_serienummers">Serienummers van componenten</label>
<textarea id="component_serienummers" rows="2" ></textarea>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="software_versie">Softwareversie / firmwareversie</label>
<input type="text" id="software_versie" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="ip_adres">IP-adres</label>
<input type="text" id="ip_adres" placeholder="Bijv. 192.168.1.100">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="mac_adres">MAC-adres</label>
<input type="text" id="mac_adres" placeholder="Bijv. AA:BB:CC:DD:EE:FF">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="energieklasse">Energieklasse</label>
<select id="energieklasse" >
<option value="">-- Selecteer --</option>
<option value="A+++">A+++</option>
<option value="A++">A++</option>
<option value="A+">A+</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
<option value="Onbekend">Onbekend</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="certificeringen">Certificeringen</label>
<div class="multiselect-group" data-id="certificeringen">
<label class="multiselect-item">
<input type="checkbox" name="certificeringen" value="CE">
<span>CE</span>
</label>
<label class="multiselect-item">
<input type="checkbox" name="certificeringen" value="ISO 9001">
<span>ISO 9001</span>
</label>
<label class="multiselect-item">
<input type="checkbox" name="certificeringen" value="ISO 27001">
<span>ISO 27001</span>
</label>
<label class="multiselect-item">
<input type="checkbox" name="certificeringen" value="Energy Star">
<span>Energy Star</span>
</label>
<label class="multiselect-item">
<input type="checkbox" name="certificeringen" value="RoHS">
<span>RoHS</span>
</label>
<label class="multiselect-item">
<input type="checkbox" name="certificeringen" value="GDPR Compliant">
<span>GDPR Compliant</span>
</label>
</div>
</div>
</section>
<section class="section">
<h2>Status en conditie</h2>
<p class="section-desc">Huidige status en onderhoudsinformatie</p>
<div class="field-group">
<label for="status">Huidige status<span class="required">*</span></label>
<select id="status" data-required="true">
<option value="">-- Selecteer --</option>
<option value="In gebruik">In gebruik</option>
<option value="In opslag">In opslag</option>
<option value="Buiten gebruik">Buiten gebruik</option>
<option value="Defect">Defect</option>
<option value="In reparatie">In reparatie</option>
<option value="Verhuurd">Verhuurd</option>
<option value="Gereserveerd">Gereserveerd</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="conditie">Conditie / onderhoudstoestand</label>
<select id="conditie" >
<option value="">-- Selecteer --</option>
<option value="Uitstekend">Uitstekend</option>
<option value="Goed">Goed</option>
<option value="Redelijk">Redelijk</option>
<option value="Matig">Matig</option>
<option value="Slecht">Slecht</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="laatste_onderhoud">Laatste onderhoudsdatum</label>
<input type="date" id="laatste_onderhoud" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="volgend_onderhoud">Volgende geplande onderhoudsdatum</label>
<input type="date" id="volgend_onderhoud" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="garantie_tot">Garantie tot</label>
<input type="date" id="garantie_tot" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="leverancier">Leverancier / onderhoudspartner</label>
<input type="text" id="leverancier" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="onderhoudscontract">Onderhoudscontractnummer</label>
<input type="text" id="onderhoudscontract" >
<div class="error-message"></div>
</div>
</section>
<section class="section">
<h2>Documentatie &amp; beheer</h2>
<p class="section-desc">Administratieve gegevens en documentatie</p>
<div class="field-group">
<label for="factuurnummer">Factuurnummer / aankoopreferentie</label>
<input type="text" id="factuurnummer" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="leveringsdatum">Leveringsdatum</label>
<input type="date" id="leveringsdatum" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="handleiding_link">Handleidingen / documenten (link)</label>
<input type="text" id="handleiding_link" placeholder="URL naar documentatie">
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="foto">Foto van het apparaat</label>
<div class="photo-container" data-max-width="1200" data-max-height="1200">
<input type="file" accept="image/*" capture="environment" style="display:none">
<img id="foto" class="photo-preview" src="" alt="Foto preview" style="display:none">
<div class="photo-placeholder">📷 Klik om foto te maken of uploaden</div>
<div class="photo-buttons" style="display:none">
<button type="button" class="btn btn-secondary" onclick="removePhoto('foto')">Verwijderen</button>
</div>
</div>
</div>
<div class="field-group">
<label for="opmerkingen">Opmerkingen / bijzonderheden</label>
<textarea id="opmerkingen" rows="4" placeholder="Modificaties, risico&#x27;s, speciale aandachtspunten..."></textarea>
<div class="error-message"></div>
</div>
</section>
<section class="section">
<h2>Afvoer / einde levensduur</h2>
<p class="section-desc">Informatie over afvoer en recycling</p>
<div class="field-group">
<label for="datum_buiten_gebruik">Datum buiten gebruikstelling</label>
<input type="date" id="datum_buiten_gebruik" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="reden_afvoer">Reden van afvoer</label>
<select id="reden_afvoer" >
<option value="">-- Selecteer --</option>
<option value="Einde levensduur">Einde levensduur</option>
<option value="Defect (niet repareerbaar)">Defect (niet repareerbaar)</option>
<option value="Verouderd">Verouderd</option>
<option value="Vervangen">Vervangen</option>
<option value="Verkocht">Verkocht</option>
<option value="Gestolen">Gestolen</option>
<option value="Verloren">Verloren</option>
<option value="Anders">Anders</option>
</select>
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="afvoermethode">Verwijderlocatie of afvoermethode</label>
<input type="text" id="afvoermethode" >
<div class="error-message"></div>
</div>
<div class="field-group">
<label for="hergebruik_info">Hergebruik / donatie / recycling informatie</label>
<textarea id="hergebruik_info" rows="2" ></textarea>
<div class="error-message"></div>
</div>
</section>
<div class="actions">
<button type="button" class="btn btn-primary" onclick="exportCSV()">📥 Exporteer CSV</button>
<button type="button" class="btn btn-success" onclick="sendEmail(this)">📧 Verstuur per Email</button>
<button type="button" class="btn btn-danger" onclick="clearForm()">🗑️ Formulier Wissen</button>
</div>
</form>
<div class="status-bar">
<span id="save-indicator" class="save-indicator">Gereed</span>
<span>EasySmartInventory v1.0.0</span>
</div>
</div>
<div id="toast" class="toast"></div>
<script>
(function() {
'use strict';
const CONFIG = {"name": "Machine_Inventarisatie", "version": "1.0.0", "autosave": {"enabled": true, "interval_seconds": 5, "use_url_hash": true, "use_localstorage": true}, "export": {"csv": {"enabled": true, "include_photo": true}, "mailto": {"enabled": true, "to": "inventaris@bedrijf.nl", "subject_prefix": "Inventarisatie", "subject_fields": ["serienummer", "merk"], "include_timestamp": true}}};
const STORAGE_KEY = 'inventory_' + CONFIG.name + '_' + CONFIG.version;
let saveTimeout = null;
let hasChanges = false;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadSavedData();
setupAutoSave();
setupValidation();
setupPhotoHandlers();
updateSaveIndicator('loaded');
});
// Load saved data from localStorage or URL
function loadSavedData() {
// Try URL hash first
if (CONFIG.autosave.use_url_hash && window.location.hash) {
try {
const hashData = decodeURIComponent(window.location.hash.substring(1));
const params = new URLSearchParams(hashData);
params.forEach((value, key) => {
setFieldValue(key, value);
});
return;
} catch (e) {
console.warn('Could not parse URL hash:', e);
}
}
// Try localStorage
if (CONFIG.autosave.use_localstorage) {
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const data = JSON.parse(saved);
Object.entries(data).forEach(([key, value]) => {
setFieldValue(key, value);
});
}
} catch (e) {
console.warn('Could not load from localStorage:', e);
}
}
}
// Set field value by ID
function setFieldValue(id, value) {
const field = document.getElementById(id);
if (!field) return;
if (field.type === 'checkbox') {
field.checked = value === 'true' || value === true;
} else if (field.classList.contains('multiselect-field')) {
// Multiselect checkboxes
const values = Array.isArray(value) ? value : value.split(',');
values.forEach(v => {
const cb = document.querySelector(`input[name="${id}"][value="${v}"]`);
if (cb) cb.checked = true;
});
} else if (field.tagName === 'IMG') {
// Photo field
if (value) {
field.src = value;
field.parentElement.classList.add('has-photo');
}
} else {
field.value = value;
}
}
// Get all form data
function getFormData() {
const data = {};
// Text, number, date, select, textarea
document.querySelectorAll('input[type="text"], input[type="number"], input[type="date"], select, textarea').forEach(field => {
if (field.id) {
data[field.id] = field.value;
}
});
// Checkboxes (boolean)
document.querySelectorAll('input[type="checkbox"].boolean-field').forEach(field => {
if (field.id) {
data[field.id] = field.checked;
}
});
// Multiselect
document.querySelectorAll('.multiselect-group').forEach(group => {
const id = group.dataset.id;
const checked = Array.from(group.querySelectorAll('input:checked')).map(cb => cb.value);
data[id] = checked;
});
// Photo
document.querySelectorAll('.photo-preview').forEach(img => {
const id = img.id;
if (img.src && !img.src.includes('data:image/svg')) {
data[id] = img.src;
}
});
return data;
}
// Save data
function saveData() {
const data = getFormData();
// Save to localStorage
if (CONFIG.autosave.use_localstorage) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {
console.warn('Could not save to localStorage:', e);
}
}
// Update URL hash
if (CONFIG.autosave.use_url_hash) {
const params = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => {
if (value && value !== '' && !(Array.isArray(value) && value.length === 0)) {
params.set(key, Array.isArray(value) ? value.join(',') : value);
}
});
const hash = params.toString();
if (hash) {
history.replaceState(null, '', '#' + hash);
}
}
hasChanges = false;
updateSaveIndicator('saved');
}
// Setup auto-save
function setupAutoSave() {
if (!CONFIG.autosave.enabled) return;
const interval = CONFIG.autosave.interval_seconds * 1000;
document.addEventListener('input', function() {
hasChanges = true;
updateSaveIndicator('saving');
clearTimeout(saveTimeout);
saveTimeout = setTimeout(saveData, interval);
});
document.addEventListener('change', function() {
hasChanges = true;
updateSaveIndicator('saving');
clearTimeout(saveTimeout);
saveTimeout = setTimeout(saveData, interval);
});
}
// Update save indicator
function updateSaveIndicator(status) {
const indicator = document.getElementById('save-indicator');
if (!indicator) return;
indicator.className = 'save-indicator ' + status;
const statusText = {
'loaded': 'Gegevens geladen',
'saving': 'Opslaan...',
'saved': 'Opgeslagen ✓'
};
indicator.textContent = statusText[status] || status;
}
// Setup validation
function setupValidation() {
document.querySelectorAll('[data-required="true"]').forEach(field => {
field.addEventListener('blur', function() {
validateField(this);
});
});
document.querySelectorAll('[data-min-length]').forEach(field => {
field.addEventListener('blur', function() {
validateField(this);
});
});
}
// Validate single field
function validateField(field) {
let valid = true;
let message = '';
const value = field.value.trim();
if (field.dataset.required === 'true' && !value) {
valid = false;
message = 'Dit veld is verplicht';
}
if (valid && field.dataset.minLength && value.length < parseInt(field.dataset.minLength)) {
valid = false;
message = `Minimaal ${field.dataset.minLength} karakters vereist`;
}
if (valid && field.type === 'number') {
const num = parseFloat(value);
if (field.min && num < parseFloat(field.min)) {
valid = false;
message = `Minimale waarde is ${field.min}`;
}
if (field.max && num > parseFloat(field.max)) {
valid = false;
message = `Maximale waarde is ${field.max}`;
}
}
field.classList.toggle('invalid', !valid);
const errorEl = field.nextElementSibling;
if (errorEl && errorEl.classList.contains('error-message')) {
errorEl.textContent = message;
}
return valid;
}
// Validate all fields
function validateAll() {
let valid = true;
document.querySelectorAll('[data-required="true"], [data-min-length]').forEach(field => {
if (!validateField(field)) {
valid = false;
}
});
return valid;
}
// Setup photo handlers
function setupPhotoHandlers() {
document.querySelectorAll('.photo-container').forEach(container => {
const input = container.querySelector('input[type="file"]');
const preview = container.querySelector('.photo-preview');
const fieldId = preview.id;
const maxWidth = parseInt(container.dataset.maxWidth) || 1200;
const maxHeight = parseInt(container.dataset.maxHeight) || 1200;
container.addEventListener('click', function(e) {
if (e.target.tagName !== 'BUTTON') {
input.click();
}
});
input.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
processImage(file, maxWidth, maxHeight, function(dataUrl) {
preview.src = dataUrl;
container.classList.add('has-photo');
hasChanges = true;
saveData();
});
});
});
}
// Process and resize image
function processImage(file, maxWidth, maxHeight, callback) {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = height * (maxWidth / width);
width = maxWidth;
}
if (height > maxHeight) {
width = width * (maxHeight / height);
height = maxHeight;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
callback(canvas.toDataURL('image/jpeg', 0.8));
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
// Remove photo
window.removePhoto = function(fieldId) {
const preview = document.getElementById(fieldId);
const container = preview.parentElement;
preview.src = '';
container.classList.remove('has-photo');
hasChanges = true;
saveData();
};
// Export to CSV
window.exportCSV = function() {
if (!validateAll()) {
showToast('Vul eerst alle verplichte velden in', 'error');
return;
}
const data = getFormData();
const headers = Object.keys(data);
const values = Object.values(data).map(v => {
if (Array.isArray(v)) return v.join('; ');
if (typeof v === 'string' && (v.includes(',') || v.includes('"') || v.includes('\n'))) {
return '"' + v.replace(/"/g, '""') + '"';
}
return v;
});
// UTF-8 BOM for Excel
const BOM = '\uFEFF';
const csv = BOM + headers.join(',') + '\n' + values.join(',');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = CONFIG.name + '_' + formatDate(new Date()) + '.csv';
a.click();
URL.revokeObjectURL(url);
showToast('CSV geëxporteerd!', 'success');
};
// Send email
window.sendEmail = function(btn) {
if (!validateAll()) {
showToast('Vul eerst alle verplichte velden in', 'error');
return;
}
// Show loading state immediately
const button = btn || document.querySelector('[onclick*="sendEmail"]');
const originalText = button ? button.innerHTML : '';
if (button) {
button.innerHTML = '⏳ Email voorbereiden...';
button.disabled = true;
}
// Use setTimeout to allow UI to update before heavy processing
setTimeout(function() {
const data = getFormData();
// Build subject
let subject = CONFIG.export.mailto.subject_prefix;
if (CONFIG.export.mailto.include_timestamp) {
subject += ' - ' + formatDateTime(new Date());
}
CONFIG.export.mailto.subject_fields.forEach(fieldId => {
if (data[fieldId]) {
subject += ' - ' + data[fieldId];
}
});
// Build body
let body = 'INVENTARISATIE GEGEVENS\n';
body += '========================\n\n';
body += 'Formulier: ' + CONFIG.name + '\n';
body += 'Versie: ' + CONFIG.version + '\n';
body += 'Datum: ' + formatDateTime(new Date()) + '\n\n';
Object.entries(data).forEach(([key, value]) => {
if (value && value !== '' && !key.includes('foto') && !key.includes('photo')) {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
const displayValue = Array.isArray(value) ? value.join(', ') : value;
body += labelText + ': ' + displayValue + '\n';
}
});
body += '\n========================\n';
body += 'Let op: Foto\'s kunnen niet via mailto worden verzonden.\n';
body += 'Gebruik CSV export voor complete data inclusief foto\'s.';
const mailto = 'mailto:' + encodeURIComponent(CONFIG.export.mailto.to) +
'?subject=' + encodeURIComponent(subject) +
'&body=' + encodeURIComponent(body);
// Restore button state
if (button) {
button.innerHTML = originalText;
button.disabled = false;
}
window.location.href = mailto;
}, 50);
};
// Clear form
window.clearForm = function() {
if (!confirm('Weet u zeker dat u alle gegevens wilt wissen?')) return;
document.querySelectorAll('input[type="text"], input[type="number"], input[type="date"], select, textarea').forEach(field => {
field.value = '';
field.classList.remove('invalid');
});
document.querySelectorAll('input[type="checkbox"]').forEach(cb => {
cb.checked = false;
});
document.querySelectorAll('.photo-preview').forEach(img => {
img.src = '';
img.parentElement.classList.remove('has-photo');
});
localStorage.removeItem(STORAGE_KEY);
history.replaceState(null, '', window.location.pathname);
showToast('Formulier gewist', 'success');
};
// Show toast notification
function showToast(message, type) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = 'toast ' + type + ' show';
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Format date
function formatDate(date) {
return date.toISOString().split('T')[0];
}
// Format datetime
function formatDateTime(date) {
return date.toLocaleDateString('nl-NL') + ' ' +
date.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
})();
</script>
</body>
</html>

Powered by TurnKey Linux.