Übersicht der Kontakte
Dieser Commit ist enthalten in:
239
v2_adminpanel/leads/templates/leads/all_contacts.html
Normale Datei
239
v2_adminpanel/leads/templates/leads/all_contacts.html
Normale Datei
@@ -0,0 +1,239 @@
|
||||
{% 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 %}
|
||||
@@ -14,6 +14,9 @@
|
||||
<button class="btn btn-primary" onclick="showCreateInstitutionModal()">
|
||||
<i class="bi bi-plus-circle"></i> Neue Institution
|
||||
</button>
|
||||
<a href="{{ url_for('leads.all_contacts') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-people"></i> Alle Kontakte
|
||||
</a>
|
||||
<a href="{{ url_for('customers.customers_licenses') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Zurück zu Kunden
|
||||
</a>
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren