239 Zeilen
9.6 KiB
HTML
239 Zeilen
9.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Alle Kontakte{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="row mb-4">
|
|
<div class="col-md-8">
|
|
<h1 class="h2 mb-0">
|
|
<i class="bi bi-people"></i> Alle Kontakte
|
|
</h1>
|
|
<p class="text-muted mb-0">Übersicht aller Kontakte aus allen Institutionen</p>
|
|
</div>
|
|
<div class="col-md-4 text-end">
|
|
<a href="{{ url_for('leads.institutions') }}" class="btn btn-secondary">
|
|
<i class="bi bi-building"></i> Zu Institutionen
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search and Filter Bar -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
|
<input type="text" class="form-control" id="searchInput"
|
|
placeholder="Nach Name, Institution oder Position suchen..."
|
|
onkeyup="filterContacts()">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select class="form-select" id="institutionFilter" onchange="filterContacts()">
|
|
<option value="">Alle Institutionen</option>
|
|
{% set institutions = contacts | map(attribute='institution_name') | unique | sort %}
|
|
{% for institution in institutions %}
|
|
<option value="{{ institution }}">{{ institution }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="sortContacts('name')">
|
|
<i class="bi bi-sort-alpha-down"></i> Name
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="sortContacts('institution')">
|
|
<i class="bi bi-building"></i> Institution
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="sortContacts('updated')">
|
|
<i class="bi bi-clock"></i> Aktualisiert
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contacts Table -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" id="contactsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Institution</th>
|
|
<th>Position</th>
|
|
<th>Kontaktdaten</th>
|
|
<th>Notizen</th>
|
|
<th>Zuletzt aktualisiert</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for contact in contacts %}
|
|
<tr data-name="{{ contact.last_name }} {{ contact.first_name }}"
|
|
data-institution="{{ contact.institution_name }}"
|
|
data-updated="{{ contact.updated_at or contact.created_at }}">
|
|
<td>
|
|
<a href="{{ url_for('leads.contact_detail', contact_id=contact.id) }}"
|
|
class="text-decoration-none">
|
|
<strong>{{ contact.last_name }}, {{ contact.first_name }}</strong>
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<a href="{{ url_for('leads.institution_detail', institution_id=contact.institution_id) }}"
|
|
class="text-decoration-none text-muted">
|
|
{{ contact.institution_name }}
|
|
</a>
|
|
</td>
|
|
<td>{{ contact.position or '-' }}</td>
|
|
<td>
|
|
{% if contact.phone_count > 0 %}
|
|
<span class="badge bg-info me-1" title="Telefonnummern">
|
|
<i class="bi bi-telephone"></i> {{ contact.phone_count }}
|
|
</span>
|
|
{% endif %}
|
|
{% if contact.email_count > 0 %}
|
|
<span class="badge bg-primary" title="E-Mail-Adressen">
|
|
<i class="bi bi-envelope"></i> {{ contact.email_count }}
|
|
</span>
|
|
{% endif %}
|
|
{% if contact.phone_count == 0 and contact.email_count == 0 %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if contact.note_count > 0 %}
|
|
<span class="badge bg-secondary">
|
|
<i class="bi bi-sticky"></i> {{ contact.note_count }}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{{ (contact.updated_at or contact.created_at).strftime('%d.%m.%Y %H:%M') }}
|
|
</td>
|
|
<td>
|
|
<a href="{{ url_for('leads.contact_detail', contact_id=contact.id) }}"
|
|
class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-eye"></i> Details
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% if not contacts %}
|
|
<div class="text-center py-4">
|
|
<p class="text-muted">Noch keine Kontakte vorhanden.</p>
|
|
<a href="{{ url_for('leads.institutions') }}" class="btn btn-primary">
|
|
<i class="bi bi-building"></i> Zu Institutionen
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contact Count -->
|
|
{% if contacts %}
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<p class="text-muted">
|
|
<span id="visibleCount">{{ contacts|length }}</span> von {{ contacts|length }} Kontakten angezeigt
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
// Current sort order
|
|
let currentSort = { field: 'name', ascending: true };
|
|
|
|
// Filter contacts
|
|
function filterContacts() {
|
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
const institutionFilter = document.getElementById('institutionFilter').value.toLowerCase();
|
|
const rows = document.querySelectorAll('#contactsTable tbody tr');
|
|
let visibleCount = 0;
|
|
|
|
rows.forEach(row => {
|
|
const text = row.textContent.toLowerCase();
|
|
const institution = row.getAttribute('data-institution').toLowerCase();
|
|
|
|
const matchesSearch = searchTerm === '' || text.includes(searchTerm);
|
|
const matchesInstitution = institutionFilter === '' || institution === institutionFilter;
|
|
|
|
if (matchesSearch && matchesInstitution) {
|
|
row.style.display = '';
|
|
visibleCount++;
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Update visible count
|
|
const countElement = document.getElementById('visibleCount');
|
|
if (countElement) {
|
|
countElement.textContent = visibleCount;
|
|
}
|
|
}
|
|
|
|
// Sort contacts
|
|
function sortContacts(field) {
|
|
const tbody = document.querySelector('#contactsTable tbody');
|
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
|
|
// Toggle sort order if same field
|
|
if (currentSort.field === field) {
|
|
currentSort.ascending = !currentSort.ascending;
|
|
} else {
|
|
currentSort.field = field;
|
|
currentSort.ascending = true;
|
|
}
|
|
|
|
// Sort rows
|
|
rows.sort((a, b) => {
|
|
let aValue, bValue;
|
|
|
|
switch(field) {
|
|
case 'name':
|
|
aValue = a.getAttribute('data-name');
|
|
bValue = b.getAttribute('data-name');
|
|
break;
|
|
case 'institution':
|
|
aValue = a.getAttribute('data-institution');
|
|
bValue = b.getAttribute('data-institution');
|
|
break;
|
|
case 'updated':
|
|
aValue = new Date(a.getAttribute('data-updated'));
|
|
bValue = new Date(b.getAttribute('data-updated'));
|
|
break;
|
|
}
|
|
|
|
if (field === 'updated') {
|
|
return currentSort.ascending ? aValue - bValue : bValue - aValue;
|
|
} else {
|
|
const comparison = aValue.localeCompare(bValue);
|
|
return currentSort.ascending ? comparison : -comparison;
|
|
}
|
|
});
|
|
|
|
// Re-append sorted rows
|
|
rows.forEach(row => tbody.appendChild(row));
|
|
|
|
// Update button states
|
|
document.querySelectorAll('.btn-group button').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
event.target.classList.add('active');
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Set initial sort
|
|
sortContacts('name');
|
|
});
|
|
</script>
|
|
{% endblock %} |