Initial commit: EasySmartInventory v1.0.0

- Python HTML generator from YAML config
- 3 themes: modern, corporate, minimal
- Field types: text, number, date, dropdown, multiselect, boolean, photo, textarea
- Auto-save to localStorage and URL hash
- CSV export with base64 photos
- Mailto with configurable subject
- Responsive design for mobile/tablet/desktop
- Company logo and color customization
main
killercow 2 weeks ago
commit 16f4cc7bc8

9
.gitignore vendored

@ -0,0 +1,9 @@
.venv/
__pycache__/
*.pyc
.secrets
exports/*.csv
*.egg-info/
dist/
build/
.DS_Store

@ -0,0 +1,111 @@
# EasySmartInventory
Genereer interactieve standalone HTML inventarisatieformulieren op basis van YAML configuratie.
## Features
- 📝 **YAML configuratie** - Definieer alle velden, validatie en opties in YAML
- 📱 **Responsive** - Werkt op iPad, iPhone, Android en desktop
- 💾 **Auto-save** - Automatisch opslaan naar localStorage
- 📤 **CSV Export** - Exporteer naar CSV met base64 foto's
- 📧 **Mailto** - Direct mailen vanuit de browser
- 🎨 **Themes** - Kies uit modern, corporate of minimal stijl
- 🏢 **Branding** - Eigen logo en kleuren
## Installatie
```bash
cd EasySmartInventory
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
## Gebruik
```bash
# Genereer HTML uit YAML configuratie
python src/generator.py examples/config.yaml
# Output: examples/Machine_Inventarisatie.html
```
## YAML Configuratie
Zie `examples/config.yaml` voor een volledig voorbeeld.
### Basis structuur
```yaml
name: "Inventarisatie Naam"
version: "1.0.0"
style:
theme: "modern" # modern, corporate, minimal
logo: "base64_string_or_url"
primary_color: "#007bff"
export:
csv:
enabled: true
mailto:
enabled: true
to: "inventory@company.com"
subject_prefix: "Inventarisatie"
subject_fields: ["Serienummer"]
sections:
- name: "Sectie Naam"
fields:
- id: "veld_id"
label: "Veld Label"
type: "text"
required: true
```
### Field Types
| Type | Beschrijving | Extra opties |
|------|--------------|--------------|
| `text` | Tekstveld | `min_length`, `placeholder` |
| `number` | Numeriek veld | `min`, `max` |
| `date` | Datumveld | `format` (dd-mm-yyyy) |
| `dropdown` | Keuzelijst | `options` (array) |
| `multiselect` | Meerdere keuzes | `options` (array) |
| `boolean` | Ja/Nee toggle | - |
| `photo` | Foto upload | `max_width`, `max_height` |
| `textarea` | Groot tekstveld | `rows` |
### Validatie opties
- `required: true` - Veld is verplicht
- `min_length: 5` - Minimaal aantal karakters
- `min: 0` / `max: 100` - Bereik voor nummers
## Voorbeelden
Drie voorbeeldstijlen zijn beschikbaar:
- `examples/inventory_modern.html` - Modern design met shadows
- `examples/inventory_corporate.html` - Zakelijk en professioneel
- `examples/inventory_minimal.html` - Minimalistisch en clean
## Export
### CSV
- Klik "Exporteer CSV" om data te downloaden
- Foto's worden opgeslagen als base64
- UTF-8 encoding met BOM voor Excel
### Email
- Klik "Verstuur per Email" om mailclient te openen
- Subject bevat configureerbare prefix + veldwaarden
- Body bevat gestructureerde data
## Versie
Huidige versie: 1.0.0
## Licentie
MIT License

@ -0,0 +1,334 @@
# Machine Inventarisatie - Voorbeeld Configuratie
# EasySmartInventory v1.0.0
name: "Machine_Inventarisatie"
version: "1.0.0"
# Styling configuratie
style:
theme: "modern" # modern, corporate, minimal
logo: "" # Base64 string of URL naar logo
primary_color: "#0066cc"
secondary_color: "#28a745"
background_color: "#f8f9fa"
text_color: "#333333"
error_color: "#dc3545"
success_color: "#28a745"
# Export configuratie
export:
csv:
enabled: true
folder: "./exports"
include_photo: true
mailto:
enabled: true
to: "inventaris@bedrijf.nl"
subject_prefix: "Inventarisatie"
subject_fields: ["serienummer", "merk"]
include_timestamp: true
# Auto-save configuratie
autosave:
enabled: true
interval_seconds: 5
use_url_hash: true
use_localstorage: true
# Secties en velden
sections:
- name: "Basisinformatie"
description: "Algemene gegevens van het apparaat"
fields:
- id: "serienummer"
label: "Serienummer"
type: "text"
required: true
min_length: 3
placeholder: "Voer serienummer in"
- id: "type_nummer"
label: "Type-/modelnummer"
type: "text"
required: false
placeholder: "Bijv. HP ProBook 450 G8"
- id: "merk"
label: "Merk / fabrikant"
type: "dropdown"
required: true
options:
- "HP"
- "Dell"
- "Lenovo"
- "Apple"
- "ASUS"
- "Acer"
- "Microsoft"
- "Samsung"
- "Anders"
- id: "soort_apparaat"
label: "Soort apparaat / categorie"
type: "dropdown"
required: true
options:
- "Laptop"
- "Desktop"
- "Server"
- "Printer"
- "Scanner"
- "Monitor"
- "Netwerkapparatuur"
- "Mobiel apparaat"
- "Anders"
- id: "productienaam"
label: "Productienaam of -omschrijving"
type: "text"
required: false
- id: "productiejaar"
label: "Productiejaar"
type: "number"
required: false
min: 1990
max: 2030
- id: "aanschafdatum"
label: "Aanschafdatum"
type: "date"
required: false
format: "dd-mm-yyyy"
- id: "aankoopprijs"
label: "Aankoopprijs of vervangingswaarde (€)"
type: "number"
required: false
min: 0
placeholder: "0.00"
- id: "asset_id"
label: "Intern identificatienummer (asset-ID)"
type: "text"
required: false
placeholder: "Bijv. AST-2024-001"
- name: "Locatie & Toewijzing"
description: "Waar bevindt het apparaat zich en wie is verantwoordelijk"
fields:
- id: "locatie_adres"
label: "Locatienummer of adres"
type: "text"
required: false
- id: "ruimte"
label: "Ruimte / afdeling / verdieping"
type: "text"
required: false
placeholder: "Bijv. Kantoor 2.15, IT-afdeling"
- id: "gebruiker"
label: "Gebruiker / verantwoordelijke persoon"
type: "text"
required: false
- id: "vestiging"
label: "Vestiging / filiaal"
type: "dropdown"
required: false
options:
- "Hoofdkantoor"
- "Vestiging Noord"
- "Vestiging Zuid"
- "Vestiging Oost"
- "Vestiging West"
- "Thuiswerker"
- "Anders"
- id: "mobiel_gebruik"
label: "Mobiel gebruik (werkplek/voertuig)"
type: "boolean"
required: false
- name: "Technische gegevens"
description: "Technische specificaties en netwerkinformatie"
fields:
- id: "specificaties"
label: "Specificaties (vermogen, capaciteit, etc.)"
type: "textarea"
required: false
rows: 3
placeholder: "Bijv. Intel i7, 16GB RAM, 512GB SSD"
- id: "component_serienummers"
label: "Serienummers van componenten"
type: "textarea"
required: false
rows: 2
- id: "software_versie"
label: "Softwareversie / firmwareversie"
type: "text"
required: false
- id: "ip_adres"
label: "IP-adres"
type: "text"
required: false
placeholder: "Bijv. 192.168.1.100"
- id: "mac_adres"
label: "MAC-adres"
type: "text"
required: false
placeholder: "Bijv. AA:BB:CC:DD:EE:FF"
- id: "energieklasse"
label: "Energieklasse"
type: "dropdown"
required: false
options:
- "A+++"
- "A++"
- "A+"
- "A"
- "B"
- "C"
- "D"
- "Onbekend"
- id: "certificeringen"
label: "Certificeringen"
type: "multiselect"
required: false
options:
- "CE"
- "ISO 9001"
- "ISO 27001"
- "Energy Star"
- "RoHS"
- "GDPR Compliant"
- name: "Status en conditie"
description: "Huidige status en onderhoudsinformatie"
fields:
- id: "status"
label: "Huidige status"
type: "dropdown"
required: true
options:
- "In gebruik"
- "In opslag"
- "Buiten gebruik"
- "Defect"
- "In reparatie"
- "Verhuurd"
- "Gereserveerd"
- id: "conditie"
label: "Conditie / onderhoudstoestand"
type: "dropdown"
required: false
options:
- "Uitstekend"
- "Goed"
- "Redelijk"
- "Matig"
- "Slecht"
- id: "laatste_onderhoud"
label: "Laatste onderhoudsdatum"
type: "date"
required: false
format: "dd-mm-yyyy"
- id: "volgend_onderhoud"
label: "Volgende geplande onderhoudsdatum"
type: "date"
required: false
format: "dd-mm-yyyy"
- id: "garantie_tot"
label: "Garantie tot"
type: "date"
required: false
format: "dd-mm-yyyy"
- id: "leverancier"
label: "Leverancier / onderhoudspartner"
type: "text"
required: false
- id: "onderhoudscontract"
label: "Onderhoudscontractnummer"
type: "text"
required: false
- name: "Documentatie & beheer"
description: "Administratieve gegevens en documentatie"
fields:
- id: "factuurnummer"
label: "Factuurnummer / aankoopreferentie"
type: "text"
required: false
- id: "leveringsdatum"
label: "Leveringsdatum"
type: "date"
required: false
format: "dd-mm-yyyy"
- id: "handleiding_link"
label: "Handleidingen / documenten (link)"
type: "text"
required: false
placeholder: "URL naar documentatie"
- id: "foto"
label: "Foto van het apparaat"
type: "photo"
required: false
max_width: 1200
max_height: 1200
- id: "opmerkingen"
label: "Opmerkingen / bijzonderheden"
type: "textarea"
required: false
rows: 4
placeholder: "Modificaties, risico's, speciale aandachtspunten..."
- name: "Afvoer / einde levensduur"
description: "Informatie over afvoer en recycling"
fields:
- id: "datum_buiten_gebruik"
label: "Datum buiten gebruikstelling"
type: "date"
required: false
format: "dd-mm-yyyy"
- id: "reden_afvoer"
label: "Reden van afvoer"
type: "dropdown"
required: false
options:
- "Einde levensduur"
- "Defect (niet repareerbaar)"
- "Verouderd"
- "Vervangen"
- "Verkocht"
- "Gestolen"
- "Verloren"
- "Anders"
- id: "afvoermethode"
label: "Verwijderlocatie of afvoermethode"
type: "text"
required: false
- id: "hergebruik_info"
label: "Hergebruik / donatie / recycling informatie"
type: "textarea"
required: false
rows: 2

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,78 @@
# EasySmartInventory - Findings & Decisions
## Requirements Analysis
### Must-Have Features
1. **YAML configuratie** - Alle velden en opties via YAML
2. **Standalone HTML** - Enkel bestand, geen externe dependencies
3. **Field types**:
- Text (met min_length validatie)
- Number (met min/max)
- Date (configureerbaar formaat)
- Dropdown (single select)
- Multiselect (checkboxes)
- Boolean (ja/nee toggle)
- Photo (camera/upload, base64)
4. **Verplichte velden** - Markering en validatie
5. **Auto-save** - LocalStorage met URL state
6. **CSV Export** - Met base64 foto
7. **Mailto** - Configureerbare prefix en velden in subject
8. **Responsive** - iPad, iPhone, Android, Desktop
9. **Versienummer** - In HTML zichtbaar
10. **Bedrijfslogo** - Configureerbaar
### Technical Decisions
#### 1. Standalone HTML Approach
**Beslissing**: Alle CSS en JS inline in HTML
**Reden**: Makkelijk te delen, geen dependencies
**Trade-off**: Groter bestand, maar acceptabel
#### 2. Auto-save Strategy
**Beslissing**: LocalStorage + URL parameters
**Implementatie**:
- LocalStorage key = document naam + versie
- URL hash voor sharing (#field1=value1&field2=value2)
- Save elke 5 seconden bij wijzigingen
#### 3. Mailto Limitatie
**Probleem**: Mailto kan geen echte bijlagen
**Oplossing**:
- Foto base64 in body (kan lang zijn)
- Of: aparte instructie om foto apart te mailen
- Body bevat gestructureerde data voor parsing
#### 4. CSV Export
**Beslissing**: Client-side generatie met download
**Implementatie**:
- Blob + download link
- UTF-8 met BOM voor Excel compatibiliteit
- Foto als base64 string in cel
#### 5. Photo Handling
**Beslissing**: HTML5 FileReader + Canvas
**Features**:
- Camera capture op mobiel
- File upload op desktop
- Resize voor performance
- Base64 encoding
## Style Guidelines
### Themes
1. **Modern** - Rounded corners, gradients, shadows
2. **Corporate** - Clean lines, neutral colors, professional
3. **Minimal** - Maximum whitespace, simple borders
### Color Scheme (configureerbaar)
- Primary: Hoofdkleur (buttons, headers)
- Secondary: Accentkleur
- Background: Pagina achtergrond
- Text: Tekstkleur
- Error: Validatie errors
- Success: Bevestigingen
## Open Questions
- [ ] Maximale foto resolutie voor base64 in CSV?
- [ ] Offline support nodig?
- [ ] Meerdere talen ondersteuning?

@ -0,0 +1,157 @@
# EasySmartInventory - Project Plan
## Overview
- **Goal**: Python tool dat interactieve HTML inventarisatieformulieren genereert op basis van YAML configuratie
- **Deadline**: Flexibel
- **Priority**: High
## Deliverables
1. **Python Generator** (`src/generator.py`) - Leest YAML, genereert standalone HTML
2. **YAML Schema** (`examples/config.yaml`) - Configuratiebestand met alle opties
3. **3 Voorbeeld HTML bestanden** - Verschillende stijlen (modern, corporate, minimal)
## Architectuur
```
EasySmartInventory/
├── src/
│ ├── __init__.py
│ ├── generator.py # Main HTML generator
│ ├── yaml_parser.py # YAML config parser
│ └── templates.py # HTML/CSS/JS templates
├── templates/
│ └── base.html # Jinja2 base template
├── styles/
│ ├── modern.css
│ ├── corporate.css
│ └── minimal.css
├── examples/
│ ├── config.yaml # Voorbeeld configuratie
│ ├── inventory_modern.html
│ ├── inventory_corporate.html
│ └── inventory_minimal.html
├── exports/ # CSV export folder
├── docs/
│ └── README.md
├── requirements.txt
└── README.md
```
## Phases
### Phase 1: Core Structure (M size)
- [x] Git repo setup
- [ ] Project structure
- [ ] YAML schema design
- [ ] Base Python generator
### Phase 2: HTML Features (L size)
- [ ] Field types implementatie
- [ ] Validation
- [ ] Auto-save (localStorage)
- [ ] CSV export
- [ ] Mailto functionaliteit
- [ ] Foto capture (base64)
### Phase 3: Styling (M size)
- [ ] Responsive CSS framework
- [ ] 3 verschillende themes
- [ ] Logo support
### Phase 4: Testing & Docs (S size)
- [ ] Generate voorbeelden
- [ ] Test responsive
- [ ] Documentatie
- [ ] Push to Gitea
## YAML Schema Design
```yaml
# Configuratie structuur
name: "Inventarisatie Naam"
version: "1.0.0"
# Styling
style:
theme: "modern" # modern, corporate, minimal
logo: "base64_or_url"
primary_color: "#007bff"
# Export opties
export:
csv:
enabled: true
folder: "./exports"
mailto:
enabled: true
to: "email@example.com"
subject_prefix: "Inventarisatie"
subject_fields: ["Serienummer"]
# Velden definitie
sections:
- name: "Basisinformatie"
fields:
- id: "serienummer"
label: "Serienummer"
type: "text"
required: true
min_length: 5
- id: "type_nummer"
label: "Type-/modelnummer"
type: "text"
- id: "merk"
label: "Merk / fabrikant"
type: "dropdown"
options: ["HP", "Dell", "Lenovo", "Apple", "Anders"]
- id: "soort_apparaat"
label: "Soort apparaat"
type: "dropdown"
options: ["Laptop", "Desktop", "Server", "Printer", "Anders"]
- id: "productiejaar"
label: "Productiejaar"
type: "number"
min: 1990
max: 2030
- id: "aanschafdatum"
label: "Aanschafdatum"
type: "date"
format: "dd-mm-yyyy"
- id: "in_gebruik"
label: "In gebruik"
type: "boolean"
- id: "certificeringen"
label: "Certificeringen"
type: "multiselect"
options: ["CE", "ISO9001", "ISO27001", "GDPR"]
- id: "foto"
label: "Foto apparaat"
type: "photo"
```
## Risks
| Risk | Impact | Mitigation |
|------|--------|------------|
| Mailto bijlage limitatie | Medium | Base64 in body of apart veld |
| LocalStorage limiet | Low | Compressie, waarschuwing |
| Cross-browser support | Medium | Testen, polyfills |
## Agent Assignments
| Task | Agent | Status |
|------|-------|--------|
| Architecture | Senior Developer | In progress |
| YAML Schema | Programming | Pending |
| HTML Generator | Programming | Pending |
| CSS Themes | GUI Designer | Pending |
| UX Flow | UX Designer | Pending |
| Testing | Junior Developer | Pending |

@ -0,0 +1,40 @@
# EasySmartInventory - Progress Log
## Session 1 - 2026-01-12
### Completed
- [x] Requirements analysis van EasySmartInventorie.txt
- [x] Git repository geïnitialiseerd
- [x] Project structure opgezet
- [x] 3-file planning pattern aangemaakt
- [x] YAML schema ontworpen
- [x] Architectuur beslissingen gedocumenteerd
- [x] Python YAML parser (yaml_parser.py)
- [x] HTML generator (generator.py)
- [x] CSS themes (modern, corporate, minimal)
- [x] JavaScript auto-save, validation, CSV export, mailto
- [x] Alle field types geïmplementeerd
- [x] 3 voorbeeld HTML bestanden gegenereerd
### 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

@ -0,0 +1,2 @@
pyyaml>=6.0
jinja2>=3.1.0

@ -0,0 +1 @@
# EasySmartInventory

@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""
EasySmartInventory Generator
Genereert standalone HTML inventarisatieformulieren op basis van YAML configuratie
Gebruik:
python generator.py <config.yaml> [--output <output.html>] [--theme <modern|corporate|minimal>]
"""
import argparse
import json
import sys
from pathlib import Path
from html import escape
from yaml_parser import parse_yaml, validate_config, InventoryConfig, FieldConfig
from templates import get_theme_css, JAVASCRIPT
def generate_field_html(field: FieldConfig) -> str:
"""Genereer HTML voor een veld"""
required_attr = 'data-required="true"' if field.required else ''
required_mark = '<span class="required">*</span>' if field.required else ''
min_length_attr = f'data-min-length="{field.min_length}"' if field.min_length else ''
placeholder_attr = f'placeholder="{escape(field.placeholder)}"' if field.placeholder else ''
html = f'<div class="field-group">\n'
html += f' <label for="{field.id}">{escape(field.label)}{required_mark}</label>\n'
if field.type == "text":
html += f' <input type="text" id="{field.id}" {required_attr} {min_length_attr} {placeholder_attr}>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "number":
min_attr = f'min="{field.min}"' if field.min is not None else ''
max_attr = f'max="{field.max}"' if field.max is not None else ''
html += f' <input type="number" id="{field.id}" {required_attr} {min_attr} {max_attr} {placeholder_attr}>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "date":
html += f' <input type="date" id="{field.id}" {required_attr}>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "textarea":
html += f' <textarea id="{field.id}" rows="{field.rows}" {required_attr} {placeholder_attr}></textarea>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "dropdown":
html += f' <select id="{field.id}" {required_attr}>\n'
html += f' <option value="">-- Selecteer --</option>\n'
for option in field.options:
html += f' <option value="{escape(option)}">{escape(option)}</option>\n'
html += f' </select>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "multiselect":
html += f' <div class="multiselect-group" data-id="{field.id}">\n'
for option in field.options:
safe_value = escape(option)
html += f' <label class="multiselect-item">\n'
html += f' <input type="checkbox" name="{field.id}" value="{safe_value}">\n'
html += f' <span>{safe_value}</span>\n'
html += f' </label>\n'
html += f' </div>\n'
elif field.type == "boolean":
html += f' <div class="toggle-container">\n'
html += f' <label class="toggle">\n'
html += f' <input type="checkbox" id="{field.id}" class="boolean-field">\n'
html += f' <span class="toggle-slider"></span>\n'
html += f' </label>\n'
html += f' <span>Ja</span>\n'
html += f' </div>\n'
elif field.type == "photo":
html += f' <div class="photo-container" data-max-width="{field.max_width}" data-max-height="{field.max_height}">\n'
html += f' <input type="file" accept="image/*" capture="environment" style="display:none">\n'
html += f' <img id="{field.id}" class="photo-preview" src="" alt="Foto preview" style="display:none">\n'
html += f' <div class="photo-placeholder">📷 Klik om foto te maken of uploaden</div>\n'
html += f' <div class="photo-buttons" style="display:none">\n'
html += f' <button type="button" class="btn btn-secondary" onclick="removePhoto(\'{field.id}\')">Verwijderen</button>\n'
html += f' </div>\n'
html += f' </div>\n'
html += '</div>\n'
return html
def generate_html(config: InventoryConfig) -> str:
"""Genereer complete standalone HTML"""
# Config voor JavaScript
js_config = {
"name": config.name,
"version": config.version,
"autosave": {
"enabled": config.autosave.enabled,
"interval_seconds": config.autosave.interval_seconds,
"use_url_hash": config.autosave.use_url_hash,
"use_localstorage": config.autosave.use_localstorage,
},
"export": {
"csv": {
"enabled": config.export.csv.enabled,
"include_photo": config.export.csv.include_photo,
},
"mailto": {
"enabled": config.export.mailto.enabled,
"to": config.export.mailto.to,
"subject_prefix": config.export.mailto.subject_prefix,
"subject_fields": config.export.mailto.subject_fields,
"include_timestamp": config.export.mailto.include_timestamp,
},
},
}
# CSS genereren
css = get_theme_css(config.style.theme, config.style)
# Extra CSS voor photo container
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; }
"""
# JavaScript met config
js = JAVASCRIPT.replace("{CONFIG_JSON}", json.dumps(js_config, ensure_ascii=False))
# Secties genereren
sections_html = ""
for section in config.sections:
sections_html += f'<section class="section">\n'
sections_html += f' <h2>{escape(section.name)}</h2>\n'
if section.description:
sections_html += f' <p class="section-desc">{escape(section.description)}</p>\n'
for field in section.fields:
sections_html += generate_field_html(field)
sections_html += '</section>\n'
# Logo HTML
logo_html = ""
if config.style.logo:
if config.style.logo.startswith("data:") or config.style.logo.startswith("http"):
logo_html = f'<img src="{config.style.logo}" alt="Logo" class="logo">'
else:
logo_html = f'<img src="{config.style.logo}" alt="Logo" class="logo">'
# Actions HTML
actions_html = '<div class="actions">\n'
if config.export.csv.enabled:
actions_html += ' <button type="button" class="btn btn-primary" onclick="exportCSV()">📥 Exporteer CSV</button>\n'
if config.export.mailto.enabled:
actions_html += ' <button type="button" class="btn btn-success" onclick="sendEmail()">📧 Verstuur per Email</button>\n'
actions_html += ' <button type="button" class="btn btn-danger" onclick="clearForm()">🗑️ Formulier Wissen</button>\n'
actions_html += '</div>\n'
# Complete HTML
html = f'''<!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 v{config.version}">
<title>{escape(config.name)}</title>
<style>
{css}
</style>
</head>
<body>
<div class="container">
<header>
{logo_html}
<h1>{escape(config.name)}</h1>
<div class="version">Versie {config.version}</div>
</header>
<form id="inventory-form" onsubmit="return false;">
{sections_html}
{actions_html}
</form>
<div class="status-bar">
<span id="save-indicator" class="save-indicator">Gereed</span>
<span>EasySmartInventory v{config.version}</span>
</div>
</div>
<div id="toast" class="toast"></div>
<script>
{js}
</script>
</body>
</html>'''
return html
def main():
parser = argparse.ArgumentParser(
description="Genereer standalone HTML inventarisatieformulieren"
)
parser.add_argument("config", help="Pad naar YAML configuratiebestand")
parser.add_argument("-o", "--output", help="Output HTML bestand (default: <name>.html)")
parser.add_argument("-t", "--theme", choices=["modern", "corporate", "minimal"],
help="Override theme van YAML config")
args = parser.parse_args()
# Parse YAML
config_path = Path(args.config)
if not config_path.exists():
print(f"Error: Configuratiebestand niet gevonden: {config_path}")
sys.exit(1)
print(f"📖 Lezen configuratie: {config_path}")
config = parse_yaml(str(config_path))
# Valideer
errors = validate_config(config)
if errors:
print("❌ Validatie errors:")
for error in errors:
print(f" - {error}")
sys.exit(1)
# Override theme indien opgegeven
if args.theme:
config.style.theme = args.theme
# Output pad bepalen
if args.output:
output_path = Path(args.output)
else:
output_path = config_path.parent / f"{config.name}.html"
# Genereer HTML
print(f"⚙️ Genereren HTML met theme '{config.style.theme}'...")
html = generate_html(config)
# Schrijf bestand
output_path.write_text(html, encoding="utf-8")
print(f"✅ HTML gegenereerd: {output_path}")
print(f" Grootte: {len(html):,} bytes")
print(f" Secties: {len(config.sections)}")
total_fields = sum(len(s.fields) for s in config.sections)
print(f" Velden: {total_fields}")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

@ -0,0 +1,225 @@
"""
YAML Parser voor EasySmartInventory
Parseert en valideert YAML configuratiebestanden
"""
import yaml
from dataclasses import dataclass, field
from typing import Any
@dataclass
class FieldConfig:
"""Configuratie voor een enkel veld"""
id: str
label: str
type: str
required: bool = False
placeholder: str = ""
min_length: int = 0
min: float = None
max: float = None
format: str = ""
options: list = field(default_factory=list)
rows: int = 3
max_width: int = 1200
max_height: int = 1200
@dataclass
class SectionConfig:
"""Configuratie voor een sectie"""
name: str
description: str = ""
fields: list[FieldConfig] = field(default_factory=list)
@dataclass
class StyleConfig:
"""Styling configuratie"""
theme: str = "modern"
logo: str = ""
primary_color: str = "#0066cc"
secondary_color: str = "#28a745"
background_color: str = "#f8f9fa"
text_color: str = "#333333"
error_color: str = "#dc3545"
success_color: str = "#28a745"
@dataclass
class CsvExportConfig:
"""CSV export configuratie"""
enabled: bool = True
folder: str = "./exports"
include_photo: bool = True
@dataclass
class MailtoConfig:
"""Mailto configuratie"""
enabled: bool = True
to: str = ""
subject_prefix: str = "Inventarisatie"
subject_fields: list = field(default_factory=list)
include_timestamp: bool = True
@dataclass
class ExportConfig:
"""Export configuratie"""
csv: CsvExportConfig = field(default_factory=CsvExportConfig)
mailto: MailtoConfig = field(default_factory=MailtoConfig)
@dataclass
class AutosaveConfig:
"""Autosave configuratie"""
enabled: bool = True
interval_seconds: int = 5
use_url_hash: bool = True
use_localstorage: bool = True
@dataclass
class InventoryConfig:
"""Hoofdconfiguratie voor inventarisatie"""
name: str
version: str = "1.0.0"
style: StyleConfig = field(default_factory=StyleConfig)
export: ExportConfig = field(default_factory=ExportConfig)
autosave: AutosaveConfig = field(default_factory=AutosaveConfig)
sections: list[SectionConfig] = field(default_factory=list)
def parse_field(field_data: dict) -> FieldConfig:
"""Parse een veld configuratie"""
return FieldConfig(
id=field_data.get("id", ""),
label=field_data.get("label", ""),
type=field_data.get("type", "text"),
required=field_data.get("required", False),
placeholder=field_data.get("placeholder", ""),
min_length=field_data.get("min_length", 0),
min=field_data.get("min"),
max=field_data.get("max"),
format=field_data.get("format", ""),
options=field_data.get("options", []),
rows=field_data.get("rows", 3),
max_width=field_data.get("max_width", 1200),
max_height=field_data.get("max_height", 1200),
)
def parse_section(section_data: dict) -> SectionConfig:
"""Parse een sectie configuratie"""
fields = [parse_field(f) for f in section_data.get("fields", [])]
return SectionConfig(
name=section_data.get("name", ""),
description=section_data.get("description", ""),
fields=fields,
)
def parse_style(style_data: dict) -> StyleConfig:
"""Parse style configuratie"""
if not style_data:
return StyleConfig()
return StyleConfig(
theme=style_data.get("theme", "modern"),
logo=style_data.get("logo", ""),
primary_color=style_data.get("primary_color", "#0066cc"),
secondary_color=style_data.get("secondary_color", "#28a745"),
background_color=style_data.get("background_color", "#f8f9fa"),
text_color=style_data.get("text_color", "#333333"),
error_color=style_data.get("error_color", "#dc3545"),
success_color=style_data.get("success_color", "#28a745"),
)
def parse_export(export_data: dict) -> ExportConfig:
"""Parse export configuratie"""
if not export_data:
return ExportConfig()
csv_data = export_data.get("csv", {})
mailto_data = export_data.get("mailto", {})
return ExportConfig(
csv=CsvExportConfig(
enabled=csv_data.get("enabled", True),
folder=csv_data.get("folder", "./exports"),
include_photo=csv_data.get("include_photo", True),
),
mailto=MailtoConfig(
enabled=mailto_data.get("enabled", True),
to=mailto_data.get("to", ""),
subject_prefix=mailto_data.get("subject_prefix", "Inventarisatie"),
subject_fields=mailto_data.get("subject_fields", []),
include_timestamp=mailto_data.get("include_timestamp", True),
),
)
def parse_autosave(autosave_data: dict) -> AutosaveConfig:
"""Parse autosave configuratie"""
if not autosave_data:
return AutosaveConfig()
return AutosaveConfig(
enabled=autosave_data.get("enabled", True),
interval_seconds=autosave_data.get("interval_seconds", 5),
use_url_hash=autosave_data.get("use_url_hash", True),
use_localstorage=autosave_data.get("use_localstorage", True),
)
def parse_yaml(yaml_path: str) -> InventoryConfig:
"""Parse YAML bestand naar InventoryConfig"""
with open(yaml_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
sections = [parse_section(s) for s in data.get("sections", [])]
return InventoryConfig(
name=data.get("name", "Inventarisatie"),
version=data.get("version", "1.0.0"),
style=parse_style(data.get("style", {})),
export=parse_export(data.get("export", {})),
autosave=parse_autosave(data.get("autosave", {})),
sections=sections,
)
def validate_config(config: InventoryConfig) -> list[str]:
"""Valideer de configuratie en return lijst met errors"""
errors = []
if not config.name:
errors.append("Naam is verplicht")
if not config.sections:
errors.append("Minimaal één sectie is verplicht")
field_ids = set()
for section in config.sections:
if not section.name:
errors.append("Sectie naam is verplicht")
for field in section.fields:
if not field.id:
errors.append(f"Veld ID is verplicht in sectie '{section.name}'")
elif field.id in field_ids:
errors.append(f"Dubbel veld ID: '{field.id}'")
else:
field_ids.add(field.id)
if not field.label:
errors.append(f"Veld label is verplicht voor '{field.id}'")
if field.type == "dropdown" and not field.options:
errors.append(f"Dropdown '{field.id}' heeft geen opties")
if field.type == "multiselect" and not field.options:
errors.append(f"Multiselect '{field.id}' heeft geen opties")
return errors
Loading…
Cancel
Save

Powered by TurnKey Linux.