- 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 customizationmain
commit
16f4cc7bc8
@ -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…
Reference in new issue