v0.0.1: eerste versie van de Van Emous CRM

Flask + SQLite, in de Van Emous-huisstijl (tokens uit het design system, GSAP-effecten
volgens de motion-regels, uit bij prefers-reduced-motion).

- Leads en contactpersonen: status, toewijzen, verwachte waarde, omzetten naar klant,
  tijdlijn, bestanden, taken/opvolging
- Relaties (zoals accounts in EspoCRM): klantbundels (huishouden/familie) en partners met
  type (leverancier, aannemer, ...); leveranciers van producten worden automatisch partners
- API voor formulieren (POST /api/leads) met dubbele-leaddetectie, bestanden en
  configurator-ontwerpen (house-design JSON)
- Import van CSV/JSON (leads, relaties, producten) met kolomkoppeling; configurator-formaat
- Producten met SKU als vaste sleutel, mutatiehistorie, POST /api/products, CSV-export;
  offerteregels houden hun eigen prijs
- Offertes met btw per tarief en afdrukversie (PDF via de browser)
- Inbox via IMAP/SMTP, gekoppeld aan contacten
- Gebruikers (beheerder/medewerker) en klantportaal (offertes accepteren, bestanden, berichten)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-10-14 21:56:55 +02:00
co-authored by Claude Opus 5.5
commit bffa053c05
33 changed files with 4111 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% block title %}Contacten{% endblock %}
{% block content %}
<div class="head">
<div><div class="kicker">{{ 'Verkoopkansen' if a.type == 'lead' else 'Relaties' }}</div><h1>{{ {'lead': 'Leads', 'klant': 'Klantcontacten', 'partner': 'Partnercontacten'}.get(a.type, 'Contactpersonen') }}</h1></div>
<div class="actions">
<form class="search" method="get">
{% for k in ('type', 'status', 'owner') %}{% if a[k] %}<input type="hidden" name="{{ k }}" value="{{ a[k] }}">{% endif %}{% endfor %}
<input type="search" name="q" value="{{ term }}" placeholder="Zoek op naam, e-mail, plaats…" aria-label="Zoeken">
</form>
<a class="btn ghost" href="{{ url_for('contacts_export') }}">Exporteer CSV</a>
<a class="btn" href="{{ url_for('contact_new', type=a.type or 'lead') }}">{{ 'Nieuwe klant' if a.type == 'klant' else 'Nieuwe lead' }}</a>
</div>
</div>
{% if a.type != 'lead' %}
<div class="filters">
<a href="{{ url_for('contacts', q=term or None) }}" class="{{ 'on' if not a.type }}">Alle</a>
{% for k, l in TYPES %}<a href="{{ url_for('contacts', type=k, q=term or None) }}" class="{{ 'on' if a.type == k }}">{{ l }}</a>{% endfor %}
</div>
{% endif %}
<div class="filters">
{% set base = {'type': a.type, 'q': term or None} %}
<a href="{{ url_for('contacts', **base) }}" class="{{ 'on' if not a.status and not a.owner }}">Alle</a>
{% for k, l in STATUSES %}
<a href="{{ url_for('contacts', status=k, **base) }}" class="{{ 'on' if a.status == k }}">{{ l }}</a>
{% endfor %}
<span class="muted">·</span>
<a href="{{ url_for('contacts', owner='me', **base) }}" class="{{ 'on' if a.owner == 'me' }}">Mijn {{ 'leads' if a.type == 'lead' else 'contacten' }}</a>
<a href="{{ url_for('contacts', owner='none', **base) }}" class="{{ 'on' if a.owner == 'none' }}">Niet toegewezen</a>
</div>
<div class="card">
{% if rows %}
<div class="table-wrap"><table>
<thead><tr><th>Naam</th><th>Relatie</th><th>Telefoon</th><th>Plaats</th><th>Bron</th><th>Toegewezen</th><th>Status</th><th>Aangemaakt</th></tr></thead>
<tbody>
{% for c in rows %}
<tr class="link" onclick="location='{{ url_for('contact_view', cid=c.id) }}'">
<td><a href="{{ url_for('contact_view', cid=c.id) }}">{{ c.name }}</a><div class="muted">{{ c.email }}</div></td>
<td>{% if c.relation_id %}<a href="{{ url_for('relation_view', rid=c.relation_id) }}" onclick="event.stopPropagation()">{{ c.relation_name }}</a>{% if c.relation_role %}<div class="muted">{{ c.relation_role }}</div>{% endif %}{% else %}{{ c.company }}{% endif %}</td>
<td>{{ c.phone }}</td>
<td>{{ c.city }}</td>
<td>{{ c.source }}</td>
<td>{{ c.user_name or '—' }}</td>
<td>{% if not a.type %}<span class="chip">{{ TYPE.get(c.type) }}</span> {% endif %}<span class="chip {{ c.status }}">{{ STATUS.get(c.status, c.status) }}</span></td>
<td class="muted">{{ c.created_at|dt('%d-%m-%Y') }}</td>
</tr>
{% endfor %}
</tbody>
</table></div>
{% else %}<p class="empty">Geen contacten gevonden.</p>{% endif %}
</div>
{% endblock %}