diff --git a/examples/config.yaml b/examples/config.yaml
index 747df15..2b1e418 100644
--- a/examples/config.yaml
+++ b/examples/config.yaml
@@ -1,8 +1,8 @@
# Machine Inventarisatie - Voorbeeld Configuratie
-# EasySmartInventory v1.0.0
+# EasySmartInventory v1.0.1
name: "Machine_Inventarisatie"
-version: "1.0.0"
+version: "1.0.1"
# Styling configuratie
style:
@@ -35,6 +35,13 @@ autosave:
use_url_hash: true
use_localstorage: true
+# Unieke identificatie voor formulieren
+# Deze velden bepalen samen het unieke ID voor opgeslagen formulieren
+# Hiermee kunnen meerdere formulieren op hetzelfde apparaat worden opgeslagen
+unique_id_fields:
+ - "serienummer"
+ - "asset_id"
+
# Secties en velden
sections:
- name: "Basisinformatie"
diff --git a/examples/inventory_corporate.html b/examples/inventory_corporate.html
index 2f7cf0c..ad6b8cb 100644
--- a/examples/inventory_corporate.html
+++ b/examples/inventory_corporate.html
@@ -3,7 +3,7 @@
-
+
Machine_Inventarisatie
@@ -294,9 +331,17 @@ input.invalid, select.invalid, textarea.invalid {
Machine_Inventarisatie
- Versie 1.0.0
+ Versie 1.0.1
+
+
+
+
+
+
Gereed
- EasySmartInventory v1.0.0
+ EasySmartInventory v1.0.1
@@ -631,13 +676,15 @@ input.invalid, select.invalid, textarea.invalid {
(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;
+ const CONFIG = {"name": "Machine_Inventarisatie", "version": "1.0.1", "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}}, "unique_id_fields": ["serienummer", "asset_id"]};
+ const FORMS_INDEX_KEY = 'inventory_' + CONFIG.name + '_forms_index';
+ let currentFormId = null;
let saveTimeout = null;
let hasChanges = false;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
+ setupFormSelector();
loadSavedData();
setupAutoSave();
setupValidation();
@@ -645,6 +692,173 @@ input.invalid, select.invalid, textarea.invalid {
updateSaveIndicator('loaded');
});
+ // Get unique form ID based on configured fields
+ function getFormUniqueId() {
+ if (!CONFIG.unique_id_fields || CONFIG.unique_id_fields.length === 0) {
+ return 'default';
+ }
+ const parts = [];
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field && field.value && field.value.trim()) {
+ parts.push(field.value.trim());
+ }
+ });
+ return parts.length > 0 ? parts.join('_') : null;
+ }
+
+ // Get storage key for a specific form
+ function getStorageKey(formId) {
+ return 'inventory_' + CONFIG.name + '_' + CONFIG.version + '_form_' + formId;
+ }
+
+ // Get all saved forms from index
+ function getSavedForms() {
+ try {
+ const index = localStorage.getItem(FORMS_INDEX_KEY);
+ return index ? JSON.parse(index) : [];
+ } catch (e) {
+ return [];
+ }
+ }
+
+ // Save form to index
+ function saveFormToIndex(formId, label) {
+ const forms = getSavedForms();
+ const existing = forms.findIndex(f => f.id === formId);
+ const formInfo = {
+ id: formId,
+ label: label || formId,
+ lastModified: new Date().toISOString()
+ };
+ if (existing >= 0) {
+ forms[existing] = formInfo;
+ } else {
+ forms.push(formInfo);
+ }
+ localStorage.setItem(FORMS_INDEX_KEY, JSON.stringify(forms));
+ updateFormSelector();
+ }
+
+ // Remove form from index
+ function removeFormFromIndex(formId) {
+ const forms = getSavedForms().filter(f => f.id !== formId);
+ localStorage.setItem(FORMS_INDEX_KEY, JSON.stringify(forms));
+ localStorage.removeItem(getStorageKey(formId));
+ updateFormSelector();
+ }
+
+ // Setup form selector dropdown
+ function setupFormSelector() {
+ const selector = document.getElementById('form-selector');
+ if (!selector) return;
+
+ updateFormSelector();
+
+ selector.addEventListener('change', function() {
+ const selectedId = this.value;
+ if (selectedId === '__new__') {
+ clearFormData();
+ currentFormId = null;
+ showToast('Nieuw formulier gestart', 'success');
+ } else if (selectedId) {
+ loadFormById(selectedId);
+ }
+ });
+
+ // Watch unique ID fields for changes
+ if (CONFIG.unique_id_fields) {
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field) {
+ field.addEventListener('blur', function() {
+ const newId = getFormUniqueId();
+ if (newId && newId !== currentFormId) {
+ // Check if this form already exists
+ const existing = getSavedForms().find(f => f.id === newId);
+ if (existing) {
+ if (confirm('Er bestaat al een formulier met deze ID. Wilt u dit formulier laden?')) {
+ loadFormById(newId);
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+
+ // Update form selector options
+ function updateFormSelector() {
+ const selector = document.getElementById('form-selector');
+ if (!selector) return;
+
+ const forms = getSavedForms();
+ const currentValue = selector.value;
+
+ // Clear and rebuild
+ selector.innerHTML = '';
+ selector.innerHTML += '';
+
+ if (forms.length > 0) {
+ const optgroup = document.createElement('optgroup');
+ optgroup.label = 'Opgeslagen formulieren (' + forms.length + ')';
+
+ forms.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+ forms.forEach(form => {
+ const option = document.createElement('option');
+ option.value = form.id;
+ const date = new Date(form.lastModified).toLocaleString('nl-NL');
+ option.textContent = form.label + ' (' + date + ')';
+ if (form.id === currentFormId) {
+ option.selected = true;
+ }
+ optgroup.appendChild(option);
+ });
+ selector.appendChild(optgroup);
+ }
+ }
+
+ // Load a specific form by ID
+ function loadFormById(formId) {
+ try {
+ const saved = localStorage.getItem(getStorageKey(formId));
+ if (saved) {
+ clearFormData();
+ const data = JSON.parse(saved);
+ Object.entries(data).forEach(([key, value]) => {
+ setFieldValue(key, value);
+ });
+ currentFormId = formId;
+ updateFormSelector();
+ showToast('Formulier geladen: ' + formId, 'success');
+ }
+ } catch (e) {
+ console.warn('Could not load form:', e);
+ showToast('Kon formulier niet laden', 'error');
+ }
+ }
+
+ // Clear form data (without removing from storage)
+ function clearFormData() {
+ 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.style.display = 'none';
+ img.parentElement.classList.remove('has-photo');
+ });
+
+ history.replaceState(null, '', window.location.pathname);
+ }
+
// Load saved data from localStorage or URL
function loadSavedData() {
// Try URL hash first
@@ -655,24 +869,20 @@ input.invalid, select.invalid, textarea.invalid {
params.forEach((value, key) => {
setFieldValue(key, value);
});
+ currentFormId = getFormUniqueId();
return;
} catch (e) {
console.warn('Could not parse URL hash:', e);
}
}
- // Try localStorage
+ // Try to load most recent form from 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);
+ const forms = getSavedForms();
+ if (forms.length > 0) {
+ // Load most recent
+ forms.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+ loadFormById(forms[0].id);
}
}
}
@@ -741,11 +951,33 @@ input.invalid, select.invalid, textarea.invalid {
// Save data
function saveData() {
const data = getFormData();
+ const formId = getFormUniqueId();
+
+ // Only save if we have a valid form ID
+ if (!formId) {
+ updateSaveIndicator('waiting');
+ return;
+ }
- // Save to localStorage
+ // Save to localStorage with form-specific key
if (CONFIG.autosave.use_localstorage) {
try {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+ localStorage.setItem(getStorageKey(formId), JSON.stringify(data));
+
+ // Build label from unique ID fields
+ const labelParts = [];
+ if (CONFIG.unique_id_fields) {
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field && field.value) {
+ labelParts.push(field.value);
+ }
+ });
+ }
+ const label = labelParts.join(' - ') || formId;
+
+ saveFormToIndex(formId, label);
+ currentFormId = formId;
} catch (e) {
console.warn('Could not save to localStorage:', e);
}
@@ -802,7 +1034,8 @@ input.invalid, select.invalid, textarea.invalid {
const statusText = {
'loaded': 'Gegevens geladen',
'saving': 'Opslaan...',
- 'saved': 'Opgeslagen β'
+ 'saved': 'Opgeslagen β',
+ 'waiting': 'Vul ID-velden in om op te slaan'
};
indicator.textContent = statusText[status] || status;
@@ -1101,12 +1334,32 @@ input.invalid, select.invalid, textarea.invalid {
img.parentElement.classList.remove('has-photo');
});
- localStorage.removeItem(STORAGE_KEY);
history.replaceState(null, '', window.location.pathname);
+ currentFormId = null;
+ updateFormSelector();
showToast('Formulier gewist', 'success');
};
+ // Delete saved form
+ window.deleteForm = function() {
+ if (!currentFormId) {
+ showToast('Geen formulier geselecteerd om te verwijderen', 'error');
+ return;
+ }
+
+ if (!confirm('Weet u zeker dat u dit opgeslagen formulier wilt verwijderen?\n\nFormulier: ' + currentFormId)) {
+ return;
+ }
+
+ removeFormFromIndex(currentFormId);
+ clearFormData();
+ currentFormId = null;
+ updateFormSelector();
+
+ showToast('Formulier verwijderd', 'success');
+ };
+
// Show toast notification
function showToast(message, type) {
const toast = document.getElementById('toast');
diff --git a/examples/inventory_minimal.html b/examples/inventory_minimal.html
index 647c42e..17a7644 100644
--- a/examples/inventory_minimal.html
+++ b/examples/inventory_minimal.html
@@ -3,7 +3,7 @@
-
+
Machine_Inventarisatie
@@ -325,9 +362,17 @@ select {
Machine_Inventarisatie
- Versie 1.0.0
+ Versie 1.0.1
+
+
+
+
+
+
Gereed
- EasySmartInventory v1.0.0
+ EasySmartInventory v1.0.1
@@ -662,13 +707,15 @@ select {
(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;
+ const CONFIG = {"name": "Machine_Inventarisatie", "version": "1.0.1", "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}}, "unique_id_fields": ["serienummer", "asset_id"]};
+ const FORMS_INDEX_KEY = 'inventory_' + CONFIG.name + '_forms_index';
+ let currentFormId = null;
let saveTimeout = null;
let hasChanges = false;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
+ setupFormSelector();
loadSavedData();
setupAutoSave();
setupValidation();
@@ -676,6 +723,173 @@ select {
updateSaveIndicator('loaded');
});
+ // Get unique form ID based on configured fields
+ function getFormUniqueId() {
+ if (!CONFIG.unique_id_fields || CONFIG.unique_id_fields.length === 0) {
+ return 'default';
+ }
+ const parts = [];
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field && field.value && field.value.trim()) {
+ parts.push(field.value.trim());
+ }
+ });
+ return parts.length > 0 ? parts.join('_') : null;
+ }
+
+ // Get storage key for a specific form
+ function getStorageKey(formId) {
+ return 'inventory_' + CONFIG.name + '_' + CONFIG.version + '_form_' + formId;
+ }
+
+ // Get all saved forms from index
+ function getSavedForms() {
+ try {
+ const index = localStorage.getItem(FORMS_INDEX_KEY);
+ return index ? JSON.parse(index) : [];
+ } catch (e) {
+ return [];
+ }
+ }
+
+ // Save form to index
+ function saveFormToIndex(formId, label) {
+ const forms = getSavedForms();
+ const existing = forms.findIndex(f => f.id === formId);
+ const formInfo = {
+ id: formId,
+ label: label || formId,
+ lastModified: new Date().toISOString()
+ };
+ if (existing >= 0) {
+ forms[existing] = formInfo;
+ } else {
+ forms.push(formInfo);
+ }
+ localStorage.setItem(FORMS_INDEX_KEY, JSON.stringify(forms));
+ updateFormSelector();
+ }
+
+ // Remove form from index
+ function removeFormFromIndex(formId) {
+ const forms = getSavedForms().filter(f => f.id !== formId);
+ localStorage.setItem(FORMS_INDEX_KEY, JSON.stringify(forms));
+ localStorage.removeItem(getStorageKey(formId));
+ updateFormSelector();
+ }
+
+ // Setup form selector dropdown
+ function setupFormSelector() {
+ const selector = document.getElementById('form-selector');
+ if (!selector) return;
+
+ updateFormSelector();
+
+ selector.addEventListener('change', function() {
+ const selectedId = this.value;
+ if (selectedId === '__new__') {
+ clearFormData();
+ currentFormId = null;
+ showToast('Nieuw formulier gestart', 'success');
+ } else if (selectedId) {
+ loadFormById(selectedId);
+ }
+ });
+
+ // Watch unique ID fields for changes
+ if (CONFIG.unique_id_fields) {
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field) {
+ field.addEventListener('blur', function() {
+ const newId = getFormUniqueId();
+ if (newId && newId !== currentFormId) {
+ // Check if this form already exists
+ const existing = getSavedForms().find(f => f.id === newId);
+ if (existing) {
+ if (confirm('Er bestaat al een formulier met deze ID. Wilt u dit formulier laden?')) {
+ loadFormById(newId);
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+
+ // Update form selector options
+ function updateFormSelector() {
+ const selector = document.getElementById('form-selector');
+ if (!selector) return;
+
+ const forms = getSavedForms();
+ const currentValue = selector.value;
+
+ // Clear and rebuild
+ selector.innerHTML = '';
+ selector.innerHTML += '';
+
+ if (forms.length > 0) {
+ const optgroup = document.createElement('optgroup');
+ optgroup.label = 'Opgeslagen formulieren (' + forms.length + ')';
+
+ forms.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+ forms.forEach(form => {
+ const option = document.createElement('option');
+ option.value = form.id;
+ const date = new Date(form.lastModified).toLocaleString('nl-NL');
+ option.textContent = form.label + ' (' + date + ')';
+ if (form.id === currentFormId) {
+ option.selected = true;
+ }
+ optgroup.appendChild(option);
+ });
+ selector.appendChild(optgroup);
+ }
+ }
+
+ // Load a specific form by ID
+ function loadFormById(formId) {
+ try {
+ const saved = localStorage.getItem(getStorageKey(formId));
+ if (saved) {
+ clearFormData();
+ const data = JSON.parse(saved);
+ Object.entries(data).forEach(([key, value]) => {
+ setFieldValue(key, value);
+ });
+ currentFormId = formId;
+ updateFormSelector();
+ showToast('Formulier geladen: ' + formId, 'success');
+ }
+ } catch (e) {
+ console.warn('Could not load form:', e);
+ showToast('Kon formulier niet laden', 'error');
+ }
+ }
+
+ // Clear form data (without removing from storage)
+ function clearFormData() {
+ 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.style.display = 'none';
+ img.parentElement.classList.remove('has-photo');
+ });
+
+ history.replaceState(null, '', window.location.pathname);
+ }
+
// Load saved data from localStorage or URL
function loadSavedData() {
// Try URL hash first
@@ -686,24 +900,20 @@ select {
params.forEach((value, key) => {
setFieldValue(key, value);
});
+ currentFormId = getFormUniqueId();
return;
} catch (e) {
console.warn('Could not parse URL hash:', e);
}
}
- // Try localStorage
+ // Try to load most recent form from 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);
+ const forms = getSavedForms();
+ if (forms.length > 0) {
+ // Load most recent
+ forms.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+ loadFormById(forms[0].id);
}
}
}
@@ -772,11 +982,33 @@ select {
// Save data
function saveData() {
const data = getFormData();
+ const formId = getFormUniqueId();
+
+ // Only save if we have a valid form ID
+ if (!formId) {
+ updateSaveIndicator('waiting');
+ return;
+ }
- // Save to localStorage
+ // Save to localStorage with form-specific key
if (CONFIG.autosave.use_localstorage) {
try {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+ localStorage.setItem(getStorageKey(formId), JSON.stringify(data));
+
+ // Build label from unique ID fields
+ const labelParts = [];
+ if (CONFIG.unique_id_fields) {
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field && field.value) {
+ labelParts.push(field.value);
+ }
+ });
+ }
+ const label = labelParts.join(' - ') || formId;
+
+ saveFormToIndex(formId, label);
+ currentFormId = formId;
} catch (e) {
console.warn('Could not save to localStorage:', e);
}
@@ -833,7 +1065,8 @@ select {
const statusText = {
'loaded': 'Gegevens geladen',
'saving': 'Opslaan...',
- 'saved': 'Opgeslagen β'
+ 'saved': 'Opgeslagen β',
+ 'waiting': 'Vul ID-velden in om op te slaan'
};
indicator.textContent = statusText[status] || status;
@@ -1132,12 +1365,32 @@ select {
img.parentElement.classList.remove('has-photo');
});
- localStorage.removeItem(STORAGE_KEY);
history.replaceState(null, '', window.location.pathname);
+ currentFormId = null;
+ updateFormSelector();
showToast('Formulier gewist', 'success');
};
+ // Delete saved form
+ window.deleteForm = function() {
+ if (!currentFormId) {
+ showToast('Geen formulier geselecteerd om te verwijderen', 'error');
+ return;
+ }
+
+ if (!confirm('Weet u zeker dat u dit opgeslagen formulier wilt verwijderen?\n\nFormulier: ' + currentFormId)) {
+ return;
+ }
+
+ removeFormFromIndex(currentFormId);
+ clearFormData();
+ currentFormId = null;
+ updateFormSelector();
+
+ showToast('Formulier verwijderd', 'success');
+ };
+
// Show toast notification
function showToast(message, type) {
const toast = document.getElementById('toast');
diff --git a/examples/inventory_modern.html b/examples/inventory_modern.html
index 5648de8..1cc968f 100644
--- a/examples/inventory_modern.html
+++ b/examples/inventory_modern.html
@@ -3,7 +3,7 @@
-
+
Machine_Inventarisatie
@@ -356,9 +393,17 @@ input.invalid, select.invalid, textarea.invalid {
Machine_Inventarisatie
- Versie 1.0.0
+ Versie 1.0.1
+
+
+
+
+
+
Gereed
- EasySmartInventory v1.0.0
+ EasySmartInventory v1.0.1
@@ -693,13 +738,15 @@ input.invalid, select.invalid, textarea.invalid {
(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;
+ const CONFIG = {"name": "Machine_Inventarisatie", "version": "1.0.1", "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}}, "unique_id_fields": ["serienummer", "asset_id"]};
+ const FORMS_INDEX_KEY = 'inventory_' + CONFIG.name + '_forms_index';
+ let currentFormId = null;
let saveTimeout = null;
let hasChanges = false;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
+ setupFormSelector();
loadSavedData();
setupAutoSave();
setupValidation();
@@ -707,6 +754,173 @@ input.invalid, select.invalid, textarea.invalid {
updateSaveIndicator('loaded');
});
+ // Get unique form ID based on configured fields
+ function getFormUniqueId() {
+ if (!CONFIG.unique_id_fields || CONFIG.unique_id_fields.length === 0) {
+ return 'default';
+ }
+ const parts = [];
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field && field.value && field.value.trim()) {
+ parts.push(field.value.trim());
+ }
+ });
+ return parts.length > 0 ? parts.join('_') : null;
+ }
+
+ // Get storage key for a specific form
+ function getStorageKey(formId) {
+ return 'inventory_' + CONFIG.name + '_' + CONFIG.version + '_form_' + formId;
+ }
+
+ // Get all saved forms from index
+ function getSavedForms() {
+ try {
+ const index = localStorage.getItem(FORMS_INDEX_KEY);
+ return index ? JSON.parse(index) : [];
+ } catch (e) {
+ return [];
+ }
+ }
+
+ // Save form to index
+ function saveFormToIndex(formId, label) {
+ const forms = getSavedForms();
+ const existing = forms.findIndex(f => f.id === formId);
+ const formInfo = {
+ id: formId,
+ label: label || formId,
+ lastModified: new Date().toISOString()
+ };
+ if (existing >= 0) {
+ forms[existing] = formInfo;
+ } else {
+ forms.push(formInfo);
+ }
+ localStorage.setItem(FORMS_INDEX_KEY, JSON.stringify(forms));
+ updateFormSelector();
+ }
+
+ // Remove form from index
+ function removeFormFromIndex(formId) {
+ const forms = getSavedForms().filter(f => f.id !== formId);
+ localStorage.setItem(FORMS_INDEX_KEY, JSON.stringify(forms));
+ localStorage.removeItem(getStorageKey(formId));
+ updateFormSelector();
+ }
+
+ // Setup form selector dropdown
+ function setupFormSelector() {
+ const selector = document.getElementById('form-selector');
+ if (!selector) return;
+
+ updateFormSelector();
+
+ selector.addEventListener('change', function() {
+ const selectedId = this.value;
+ if (selectedId === '__new__') {
+ clearFormData();
+ currentFormId = null;
+ showToast('Nieuw formulier gestart', 'success');
+ } else if (selectedId) {
+ loadFormById(selectedId);
+ }
+ });
+
+ // Watch unique ID fields for changes
+ if (CONFIG.unique_id_fields) {
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field) {
+ field.addEventListener('blur', function() {
+ const newId = getFormUniqueId();
+ if (newId && newId !== currentFormId) {
+ // Check if this form already exists
+ const existing = getSavedForms().find(f => f.id === newId);
+ if (existing) {
+ if (confirm('Er bestaat al een formulier met deze ID. Wilt u dit formulier laden?')) {
+ loadFormById(newId);
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+
+ // Update form selector options
+ function updateFormSelector() {
+ const selector = document.getElementById('form-selector');
+ if (!selector) return;
+
+ const forms = getSavedForms();
+ const currentValue = selector.value;
+
+ // Clear and rebuild
+ selector.innerHTML = '';
+ selector.innerHTML += '';
+
+ if (forms.length > 0) {
+ const optgroup = document.createElement('optgroup');
+ optgroup.label = 'Opgeslagen formulieren (' + forms.length + ')';
+
+ forms.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+ forms.forEach(form => {
+ const option = document.createElement('option');
+ option.value = form.id;
+ const date = new Date(form.lastModified).toLocaleString('nl-NL');
+ option.textContent = form.label + ' (' + date + ')';
+ if (form.id === currentFormId) {
+ option.selected = true;
+ }
+ optgroup.appendChild(option);
+ });
+ selector.appendChild(optgroup);
+ }
+ }
+
+ // Load a specific form by ID
+ function loadFormById(formId) {
+ try {
+ const saved = localStorage.getItem(getStorageKey(formId));
+ if (saved) {
+ clearFormData();
+ const data = JSON.parse(saved);
+ Object.entries(data).forEach(([key, value]) => {
+ setFieldValue(key, value);
+ });
+ currentFormId = formId;
+ updateFormSelector();
+ showToast('Formulier geladen: ' + formId, 'success');
+ }
+ } catch (e) {
+ console.warn('Could not load form:', e);
+ showToast('Kon formulier niet laden', 'error');
+ }
+ }
+
+ // Clear form data (without removing from storage)
+ function clearFormData() {
+ 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.style.display = 'none';
+ img.parentElement.classList.remove('has-photo');
+ });
+
+ history.replaceState(null, '', window.location.pathname);
+ }
+
// Load saved data from localStorage or URL
function loadSavedData() {
// Try URL hash first
@@ -717,24 +931,20 @@ input.invalid, select.invalid, textarea.invalid {
params.forEach((value, key) => {
setFieldValue(key, value);
});
+ currentFormId = getFormUniqueId();
return;
} catch (e) {
console.warn('Could not parse URL hash:', e);
}
}
- // Try localStorage
+ // Try to load most recent form from 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);
+ const forms = getSavedForms();
+ if (forms.length > 0) {
+ // Load most recent
+ forms.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+ loadFormById(forms[0].id);
}
}
}
@@ -803,11 +1013,33 @@ input.invalid, select.invalid, textarea.invalid {
// Save data
function saveData() {
const data = getFormData();
+ const formId = getFormUniqueId();
+
+ // Only save if we have a valid form ID
+ if (!formId) {
+ updateSaveIndicator('waiting');
+ return;
+ }
- // Save to localStorage
+ // Save to localStorage with form-specific key
if (CONFIG.autosave.use_localstorage) {
try {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+ localStorage.setItem(getStorageKey(formId), JSON.stringify(data));
+
+ // Build label from unique ID fields
+ const labelParts = [];
+ if (CONFIG.unique_id_fields) {
+ CONFIG.unique_id_fields.forEach(fieldId => {
+ const field = document.getElementById(fieldId);
+ if (field && field.value) {
+ labelParts.push(field.value);
+ }
+ });
+ }
+ const label = labelParts.join(' - ') || formId;
+
+ saveFormToIndex(formId, label);
+ currentFormId = formId;
} catch (e) {
console.warn('Could not save to localStorage:', e);
}
@@ -864,7 +1096,8 @@ input.invalid, select.invalid, textarea.invalid {
const statusText = {
'loaded': 'Gegevens geladen',
'saving': 'Opslaan...',
- 'saved': 'Opgeslagen β'
+ 'saved': 'Opgeslagen β',
+ 'waiting': 'Vul ID-velden in om op te slaan'
};
indicator.textContent = statusText[status] || status;
@@ -1163,12 +1396,32 @@ input.invalid, select.invalid, textarea.invalid {
img.parentElement.classList.remove('has-photo');
});
- localStorage.removeItem(STORAGE_KEY);
history.replaceState(null, '', window.location.pathname);
+ currentFormId = null;
+ updateFormSelector();
showToast('Formulier gewist', 'success');
};
+ // Delete saved form
+ window.deleteForm = function() {
+ if (!currentFormId) {
+ showToast('Geen formulier geselecteerd om te verwijderen', 'error');
+ return;
+ }
+
+ if (!confirm('Weet u zeker dat u dit opgeslagen formulier wilt verwijderen?\n\nFormulier: ' + currentFormId)) {
+ return;
+ }
+
+ removeFormFromIndex(currentFormId);
+ clearFormData();
+ currentFormId = null;
+ updateFormSelector();
+
+ showToast('Formulier verwijderd', 'success');
+ };
+
// Show toast notification
function showToast(message, type) {
const toast = document.getElementById('toast');
diff --git a/project_progress.md b/project_progress.md
index a734e84..a2328c9 100644
--- a/project_progress.md
+++ b/project_progress.md
@@ -1,6 +1,27 @@
# EasySmartInventory - Progress Log
-## Session 1 - 2026-01-12
+## Session 2 - 2026-01-12 (v1.0.1)
+
+### New Feature: Form History
+- [x] Added `unique_id_fields` to YAML schema
+- [x] Updated yaml_parser.py for new config
+- [x] JavaScript multi-form storage with index
+- [x] Form selector dropdown in UI
+- [x] "Nieuw formulier" button
+- [x] "Verwijder" button for saved forms
+- [x] Auto-detection of existing forms by unique ID
+- [x] Version bump to 1.0.1
+
+### How it works:
+1. Configure `unique_id_fields` in YAML (e.g., serienummer, asset_id)
+2. When user fills in these fields, form is saved with unique ID
+3. Dropdown shows all saved forms with last modified date
+4. User can switch between forms or start new one
+5. Forms persist in localStorage per device
+
+---
+
+## Session 1 - 2026-01-12 (v1.0.0)
### Completed
- [x] Requirements analysis van EasySmartInventorie.txt
@@ -15,26 +36,9 @@
- [x] JavaScript auto-save, validation, CSV export, mailto
- [x] Alle field types geΓ―mplementeerd
- [x] 3 voorbeeld HTML bestanden gegenereerd
+- [x] Base64 foto's in email body
### Generated Files
-- `examples/inventory_modern.html` (35,456 bytes)
-- `examples/inventory_corporate.html` (33,894 bytes)
-- `examples/inventory_minimal.html` (34,481 bytes)
-
-### Features Implemented
-- β
YAML configuratie parsing
-- β
8 field types: text, number, date, textarea, dropdown, multiselect, boolean, photo
-- β
Validatie (required, min_length, min/max)
-- β
Auto-save naar localStorage
-- β
URL hash voor state sharing
-- β
CSV export met base64 foto's
-- β
Mailto met configureerbare prefix
-- β
3 responsive themes
-- β
Company logo support
-- β
Versienummer tracking
-
-### Notes
-- Mailto kan geen echte bijlagen - foto's worden apart vermeld in email body
-- LocalStorage + URL hash voor state management
-- Alle CSS/JS inline voor standalone HTML
-- UTF-8 BOM toegevoegd aan CSV voor Excel compatibiliteit
+- `examples/inventory_modern.html`
+- `examples/inventory_corporate.html`
+- `examples/inventory_minimal.html`
diff --git a/src/generator.py b/src/generator.py
index 964bfc7..d97b814 100644
--- a/src/generator.py
+++ b/src/generator.py
@@ -112,21 +112,71 @@ def generate_html(config: InventoryConfig) -> str:
"include_timestamp": config.export.mailto.include_timestamp,
},
},
+ "unique_id_fields": config.unique_id_fields,
}
# CSS genereren
css = get_theme_css(config.style.theme, config.style)
- # Extra CSS voor photo container
+ # Extra CSS voor photo container en form selector
css += """
.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; }
+
+.form-selector-container {
+ padding: 15px 30px;
+ background: linear-gradient(to right, #f8f9fa, #e9ecef);
+ border-bottom: 1px solid #ddd;
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ flex-wrap: wrap;
+}
+
+.form-selector-container label {
+ font-weight: 600;
+ margin: 0;
+ white-space: nowrap;
+}
+
+.form-selector-container select {
+ flex: 1;
+ min-width: 200px;
+ max-width: 400px;
+}
+
+.form-selector-container .btn {
+ padding: 8px 16px;
+ font-size: 0.9em;
+}
+
+@media (max-width: 768px) {
+ .form-selector-container {
+ flex-direction: column;
+ align-items: stretch;
+ }
+ .form-selector-container select {
+ max-width: 100%;
+ }
+}
"""
# JavaScript met config
js = JAVASCRIPT.replace("{CONFIG_JSON}", json.dumps(js_config, ensure_ascii=False))
+ # Form selector HTML (alleen als unique_id_fields geconfigureerd zijn)
+ form_selector_html = ""
+ if config.unique_id_fields:
+ form_selector_html = '''
+
+
+
+
+'''
+
# Secties genereren
sections_html = ""
for section in config.sections:
@@ -154,7 +204,7 @@ def generate_html(config: InventoryConfig) -> str:
actions_html += ' \n'
if config.export.mailto.enabled:
actions_html += ' \n'
- actions_html += ' \n'
+ actions_html += ' \n'
actions_html += '\n'
# Complete HTML
@@ -177,6 +227,7 @@ def generate_html(config: InventoryConfig) -> str:
Versie {config.version}
+ {form_selector_html}