280 lines
17 KiB
HTML
280 lines
17 KiB
HTML
{% extends 'base.html' %}
|
|
{% from '_compose.html' import compose %}
|
|
{% from '_documents.html' import documents_list with context %}
|
|
{% from '_kaart.html' import kaart_block %}
|
|
{% block title %}{{ c.name }}{% endblock %}
|
|
{% block content %}
|
|
<div class="head">
|
|
<div>
|
|
<a class="label" href="{{ url_for('contacts') }}">← Contacten</a>
|
|
<h1>{{ c.name }}</h1>
|
|
<div class="muted"><span class="chip">{{ TYPE.get(c.type) }}</span>
|
|
{% if c.relation_id %}<a href="{{ url_for('relation_view', rid=c.relation_id) }}">{{ c.relation_name }}</a>{% if c.relation_role %} ({{ c.relation_role }}){% endif %} · {% endif %}{{ [c.company, c.city]|select|join(' · ') }}</div>
|
|
</div>
|
|
<div class="actions">
|
|
<form method="post" action="{{ url_for('contact_status', cid=c.id) }}" class="inline">
|
|
<select class="form-select w-auto" name="status" onchange="this.form.submit()" aria-label="Status">
|
|
{% for k, l in STATUSES %}<option value="{{ k }}" {{ 'selected' if c.status == k }}>{{ l }}</option>{% endfor %}
|
|
</select>
|
|
</form>
|
|
{% if c.type == 'lead' %}
|
|
<button class="btn btn-outline-primary" type="button" onclick="document.getElementById('convert').showModal()">Omzetten naar klant</button>
|
|
{% endif %}
|
|
<form method="post" action="{{ url_for('quote_new') }}" class="inline">
|
|
<input type="hidden" name="contact_id" value="{{ c.id }}">
|
|
<button class="btn btn-outline-primary">Nieuwe offerte</button>
|
|
</form>
|
|
<a class="btn btn-primary" href="{{ url_for('contact_edit', cid=c.id) }}">Bewerken</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row gx-4 stack">
|
|
<div class="col-lg-7 col-xl-8">
|
|
{% if woningen %}
|
|
<div class="card"><div class="card-body">
|
|
<div class="card-top">
|
|
<h2>Woning</h2>
|
|
<div class="actions">
|
|
{% if woningen|length > 1 or not woning %}
|
|
<form method="post" action="{{ url_for('contact_design', cid=c.id) }}" class="inline">
|
|
<select class="form-select form-select-sm w-auto" name="document_id" onchange="this.form.submit()" aria-label="Andere woning kiezen">
|
|
{% if not woning %}<option value="">Kies een woning…</option>{% endif %}
|
|
{% for d in woningen %}<option value="{{ d.id }}" {{ 'selected' if woning and d.id == woning.id }}>{{ d.title }} · {{ d.updated_at|dt('%d-%m-%Y') }}</option>{% endfor %}
|
|
</select>
|
|
</form>
|
|
{% endif %}
|
|
{% if woning %}
|
|
{% if woning.versions|length > 1 %}
|
|
<form method="get" action="{{ url_for('contact_view', cid=c.id) }}" class="inline">
|
|
<select class="form-select form-select-sm w-auto" name="versie" onchange="this.form.submit()" aria-label="Versie">
|
|
{% for v in woning.versions %}<option value="{{ v.id }}" {{ 'selected' if v.id == shown.id }}>Versie {{ v.version }}{{ ' (nieuwste)' if loop.first }} · {{ v.created_at|dt('%d-%m-%Y') }}</option>{% endfor %}
|
|
</select>
|
|
</form>
|
|
{% endif %}
|
|
<form method="post" action="{{ url_for('quote_new') }}" class="inline">
|
|
<input type="hidden" name="contact_id" value="{{ c.id }}"><input type="hidden" name="bestek" value="1">
|
|
<button class="btn btn-primary btn-sm">Offerte met bestek</button>
|
|
</form>
|
|
{% if tectum_url %}<a class="btn btn-outline-primary btn-sm" href="{{ tectum_url }}" target="_blank" title="Openen in Tectum; Opslaan in CRM maakt een nieuwe versie">Bewerken in Tectum</a>{% endif %}
|
|
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('design_viewer', fid=shown.id) }}" target="_blank">Openen</a>
|
|
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('upload_file', name=shown.stored) }}" download="{{ shown.original }}">JSON</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if woning %}
|
|
<div class="viewer-frame"><iframe src="{{ url_for('design_viewer', fid=shown.id) }}" title="3D-weergave {{ woning.title }}" loading="lazy" allowfullscreen></iframe></div>
|
|
<p class="muted" style="margin:.75rem 0 0"><strong style="color:var(--ink)">{{ woning.title }}</strong>
|
|
· versie {{ shown.version }}{% if shown.id != woning.latest.id %} (nieuwste: {{ woning.latest.version }}){% endif %}{% if shown.note %} · {{ shown.note }}{% endif %}
|
|
· {{ design_meta.units }} unit{{ 's' if design_meta.units != 1 }}{% if design_meta.address %} · {{ design_meta.address }}{% endif %}
|
|
· {{ shown.created_at|dt }}{% if design_meta.version %} · configurator {{ design_meta.version }}{% endif %}</p>
|
|
{% if design_meta.note %}<p style="white-space:pre-line;margin:.5rem 0 0">{{ design_meta.note }}</p>{% endif %}
|
|
{% if shown.id == woning.latest.id %}
|
|
<p class="pdf-links" style="margin:.5rem 0 0">Tekeningen van deze versie:
|
|
{% for k, label in PDF_KINDS.items() %}{% set p = pdfs.get(k) if pdfs.get(k) and pdfs.get(k).current else None %}
|
|
<a data-pdf="{{ k }}" href="{{ p.url if p else '#' }}" target="_blank" {{ 'hidden' if not p }}>{{ (label[:1]|upper) ~ label[1:] }}</a><span class="muted" data-pdf-wait="{{ k }}" {{ 'hidden' if p }}>{{ label }} wordt gemaakt…</span>{% if k == 'werk' %} <span class="muted">(intern)</span> ·{% endif %}
|
|
{% endfor %}
|
|
<span class="muted">· opnieuw maken via PDF in de weergave</span></p>
|
|
<script>
|
|
addEventListener('message', (e) => {
|
|
if (e.origin !== location.origin || !e.data || e.data.type !== 'tectum-pdf') return;
|
|
const a = document.querySelector(`[data-pdf="${e.data.kind}"]`), w = document.querySelector(`[data-pdf-wait="${e.data.kind}"]`);
|
|
if (a) { a.href = e.data.url; a.hidden = false; }
|
|
if (w) w.hidden = true;
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
<details style="margin-top:1rem">
|
|
<summary style="font-size:1rem">Nieuwe versie uploaden</summary>
|
|
<form method="post" action="{{ url_for('document_version', did=woning.id) }}" enctype="multipart/form-data" class="row g-2">
|
|
<div class="col-md-6"><input class="form-control" type="file" name="file" accept=".json,application/json" required aria-label="Woningontwerp (JSON)"></div>
|
|
<div class="col-md"><input class="form-control" name="note" placeholder="Wat is er veranderd?" aria-label="Opmerking"></div>
|
|
<div class="col-md-auto"><button class="btn btn-outline-primary">Opslaan als versie {{ woning.latest.version + 1 }}</button></div>
|
|
</form>
|
|
</details>
|
|
{% else %}
|
|
<p class="muted" style="margin:0">Er is geen woning gekozen.</p>
|
|
{% endif %}
|
|
</div></div>
|
|
{% endif %}
|
|
|
|
{% if kaart %}{{ kaart_block(kaart) }}{% endif %}
|
|
|
|
<div class="card o-2"><div class="card-body">
|
|
<h2>Taken</h2>
|
|
{% for t in tasks %}
|
|
<form method="post" action="{{ url_for('task_done', tid=t.id) }}" class="task {{ 'done' if t.done }}">
|
|
<button class="check" aria-label="{{ 'Heropenen' if t.done else 'Afronden' }}"></button>
|
|
<div><div class="title">{{ t.title }}</div>
|
|
<div class="muted">{{ t.user_name or '' }}{% if t.due_date %} · <span class="{{ 'overdue' if not t.done and t.due_date < today }}">{{ t.due_date|dt('%d-%m-%Y') }}</span>{% endif %}</div></div>
|
|
</form>
|
|
{% endfor %}
|
|
<form method="post" action="{{ url_for('contact_task', cid=c.id) }}" class="row g-2 mt-2">
|
|
<div class="col-12 col-md"><input class="form-control" name="title" placeholder="Bijv. terugbellen, offerte nabellen" required aria-label="Taak"></div>
|
|
<div class="col-6 col-md-auto"><input class="form-control" type="date" name="due_date" aria-label="Datum"></div>
|
|
<div class="col-6 col-md-auto">
|
|
<select class="form-select" name="user_id" aria-label="Voor">
|
|
{% for u in USERS %}<option value="{{ u.id }}" {{ 'selected' if u.id == g.user.id }}>{{ u.name }}</option>{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-12 col-md-auto"><button class="btn btn-outline-primary w-100">Taak toevoegen</button></div>
|
|
</form>
|
|
</div></div>
|
|
|
|
<div class="card"><div class="card-body">
|
|
<div class="card-top"><h2>Afspraken</h2>
|
|
<div class="actions"><a class="btn btn-outline-primary btn-sm" href="{{ url_for('calendar_edit', contact_id=c.id, next=request.path) }}">Afspraak plannen</a></div></div>
|
|
{% for i in appointments %}
|
|
<div class="agenda-row"><div class="when">{{ i.when }}</div>
|
|
<div><a href="{{ url_for('calendar_edit', eid=i.id, next=request.path) }}">{{ i.title }}</a>{% if i.repeats %} ↻{% endif %}
|
|
<div class="muted">{{ i.user }}{% if i.location %} · {{ i.location }}{% endif %}</div></div></div>
|
|
{% else %}
|
|
<p class="empty">Geen afspraken gepland.</p>
|
|
{% endfor %}
|
|
</div></div>
|
|
|
|
<div class="card o-3"><div class="card-body">
|
|
<h2>Notitie toevoegen</h2>
|
|
<form method="post" action="{{ url_for('contact_note', cid=c.id) }}">
|
|
<textarea class="form-control" name="body" required placeholder="Gebeld, afspraak gemaakt, wensen…" aria-label="Notitie"></textarea>
|
|
<div class="actions"><button class="btn btn-primary">Opslaan</button></div>
|
|
</form>
|
|
</div></div>
|
|
|
|
{% if c.unsubscribed_at or c.bounced_at %}
|
|
<div class="flash error d-flex justify-content-between align-items-center gap-3 flex-wrap">
|
|
<span>{% if c.bounced_at %}Het e-mailadres is onbestelbaar (sinds {{ c.bounced_at|dt }}){% else %}Afgemeld voor nieuwsbrieven op {{ c.unsubscribed_at|dt }}{% endif %}; dit contact krijgt geen nieuwsbrieven.</span>
|
|
<form method="post" action="{{ url_for('contact_mailstatus', cid=c.id) }}" class="inline" onsubmit="return confirm('Dit contact weer nieuwsbrieven sturen?')"><button class="btn btn-outline-secondary btn-sm">Weer aanmelden</button></form>
|
|
</div>
|
|
{% endif %}
|
|
{% if c.email %}
|
|
<div class="card"><div class="card-body">
|
|
<details>
|
|
<summary>E-mail sturen</summary>
|
|
{{ compose({'to': c.email, 'contact_id': c.id}, mail_templates, mail_vars, mailboxes) }}
|
|
</details>
|
|
</div></div>
|
|
{% endif %}
|
|
|
|
<div class="card"><div class="card-body">
|
|
<h2>Tijdlijn</h2>
|
|
<ul class="timeline">
|
|
{% for a in activities %}
|
|
<li>
|
|
<div class="meta">{{ ACT.get(a.type, a.type) }} · {{ a.created_at|dt }}{% if a.user_name %} · {{ a.user_name }}{% endif %}</div>
|
|
<div class="body">{{ a.body }}</div>
|
|
</li>
|
|
{% else %}
|
|
<li class="empty">Nog geen activiteit.</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div></div>
|
|
|
|
{% if emails %}
|
|
<div class="card"><div class="card-body">
|
|
<h2>E-mail</h2>
|
|
<div class="table-responsive"><table class="table table-hover table-stack">
|
|
<tbody>
|
|
{% for e in emails %}
|
|
<tr class="link" onclick="location='{{ url_for('inbox_view', eid=e.id) }}'">
|
|
<td class="stack-title"><a href="{{ url_for('inbox_view', eid=e.id) }}">{{ e.subject }}</a></td>
|
|
<td data-label="Richting"><span class="chip">{{ 'Ontvangen' if e.direction == 'in' else 'Verstuurd' }}</span></td>
|
|
<td data-label="Datum" class="muted num">{{ e.date|dt }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table></div>
|
|
</div></div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="col-lg-5 col-xl-4">
|
|
<div class="card o-1"><div class="card-body">
|
|
<h2>Gegevens</h2>
|
|
<dl class="props">
|
|
<dt>Status</dt><dd><span class="chip {{ c.status }}">{{ STATUS.get(c.status, c.status) }}</span></dd>
|
|
<dt>Toegewezen</dt><dd>{{ c.user_name or '—' }}</dd>
|
|
{% if c.relation_id %}<dt>Relatie</dt><dd><a href="{{ url_for('relation_view', rid=c.relation_id) }}">{{ c.relation_name }}</a><br><span class="muted">{{ RKIND.get(c.relation_kind) }}{% if c.relation_type %} · {{ c.relation_type }}{% endif %}</span>
|
|
{% for h in housemates %}<br><a href="{{ url_for('contact_view', cid=h.id) }}">{{ h.name }}</a>{% if h.relation_role %} <span class="muted">({{ h.relation_role }})</span>{% endif %}{% endfor %}</dd>{% endif %}
|
|
{% if c.value %}<dt>Verwachte waarde</dt><dd>{{ c.value|euro }}</dd>{% endif %}
|
|
{% if c.email %}<dt>E-mail</dt><dd><a href="mailto:{{ c.email }}">{{ c.email }}</a></dd>{% endif %}
|
|
{% if c.phone %}<dt>Telefoon</dt><dd><a href="tel:{{ c.phone }}">{{ c.phone }}</a></dd>{% endif %}
|
|
{% if c.company %}<dt>Bedrijf</dt><dd>{{ c.company }}</dd>{% endif %}
|
|
{% if c.address or c.postcode or c.city %}<dt>Adres</dt><dd>{{ c.address }}{% if c.address %}
|
|
{% endif %}{{ c.postcode }} {{ c.city }}</dd>{% endif %}
|
|
{% if c.source %}<dt>Bron</dt><dd>{{ c.source }}</dd>{% endif %}
|
|
<dt>Aangemaakt</dt><dd>{{ c.created_at|dt }}</dd>
|
|
</dl>
|
|
{% if c.notes %}<h3>Notities</h3><p style="white-space:pre-line;margin:0">{{ c.notes }}</p>{% endif %}
|
|
{% if extra %}
|
|
<h3>Formuliervelden</h3>
|
|
<dl class="props">
|
|
{% for k, v in extra.items() %}<dt>{{ k }}</dt><dd>{{ v if v is string else v|tojson }}</dd>{% endfor %}
|
|
</dl>
|
|
{% endif %}
|
|
</div></div>
|
|
|
|
<div class="card"><div class="card-body">
|
|
<h2>Documenten</h2>
|
|
{{ documents_list(documents, url_for('contact_upload', cid=c.id), c.design_id or -1, url_for('contact_design', cid=c.id)) }}
|
|
</div></div>
|
|
|
|
<div class="card"><div class="card-body">
|
|
<h2>Offertes</h2>
|
|
{% for qt in quotes %}
|
|
<p style="margin:0 0 .6rem"><a href="{{ url_for('quote_edit', qid=qt.id) }}">{{ qt.number }}</a> <span class="chip {{ qt.status }}">{{ QSTATUS.get(qt.status) }}</span><br>
|
|
<span class="muted">{{ qt.date|dt('%d-%m-%Y') }} · {{ qt.total|euro }} incl. btw</span></p>
|
|
{% else %}
|
|
<p class="empty">Nog geen offertes.</p>
|
|
{% endfor %}
|
|
</div></div>
|
|
|
|
<div class="card"><div class="card-body">
|
|
<h2>Klantportaal</h2>
|
|
{% if c.portal_password_hash %}
|
|
<p class="muted">Actief{% if c.portal_last_login %} · laatst ingelogd {{ c.portal_last_login|dt }}{% endif %}</p>
|
|
{% elif portal_link %}
|
|
<p class="muted">Uitnodiging klaar, nog niet geactiveerd:</p>
|
|
<p><code>{{ portal_link }}</code></p>
|
|
{% else %}
|
|
<p class="muted">De klant kan via het portaal offertes bekijken en accepteren, en bestanden en berichten sturen.</p>
|
|
{% endif %}
|
|
<form method="post" action="{{ url_for('contact_portal', cid=c.id) }}" class="actions">
|
|
{% if portal_link and c.email %}<button class="btn btn-primary btn-sm" name="action" value="mail">Mail uitnodiging</button>{% endif %}
|
|
{% if not c.portal_password_hash %}<button class="btn btn-outline-primary btn-sm" name="action" value="invite">{{ 'Nieuwe link' if portal_link else 'Uitnodiging maken' }}</button>{% endif %}
|
|
{% if c.portal_password_hash or portal_link %}<button class="btn btn-outline-secondary btn-sm" name="action" value="revoke">Toegang intrekken</button>{% endif %}
|
|
</form>
|
|
</div></div>
|
|
|
|
<form method="post" action="{{ url_for('contact_delete', cid=c.id) }}" class="mb-4" onsubmit="return confirm('Contact met alle notities en bestanden verwijderen?')">
|
|
<button class="btn btn-outline-secondary btn-sm">Contact verwijderen</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% if c.type == 'lead' %}
|
|
<dialog id="convert" class="dialog">
|
|
<form method="post" action="{{ url_for('contact_convert', cid=c.id) }}">
|
|
<h2>Omzetten naar klant</h2>
|
|
<p class="muted">De contactpersoon komt in een klantrelatie, bijvoorbeeld een huishouden of familie.</p>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" id="mode-new" name="mode" value="new" checked onchange="this.form.relation_id && (this.form.relation_id.disabled=true);this.form.new_relation.disabled=false">
|
|
<label class="form-check-label" for="mode-new">Nieuwe klantrelatie</label>
|
|
</div>
|
|
<input class="form-control" name="new_relation" value="{{ c.company or ('Familie ' ~ c.name.split(' ')[-1]) }}" aria-label="Naam relatie" style="margin:.4rem 0 1rem">
|
|
{% if klant_relations %}
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" id="mode-existing" name="mode" value="existing" onchange="this.form.relation_id.disabled=false;this.form.new_relation.disabled=true">
|
|
<label class="form-check-label" for="mode-existing">Bestaande klantrelatie</label>
|
|
</div>
|
|
<select class="form-select" name="relation_id" disabled aria-label="Bestaande relatie" style="margin-top:.4rem">
|
|
{% for r in klant_relations %}<option value="{{ r.id }}">{{ r.name }}{% if r.type %} · {{ r.type }}{% endif %}</option>{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
<div class="actions">
|
|
<button class="btn btn-primary">Omzetten</button>
|
|
<button class="btn btn-outline-secondary" type="button" onclick="this.closest('dialog').close()">Annuleren</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
{% endif %}
|
|
{% endblock %}
|