328 lines
20 KiB
HTML
328 lines
20 KiB
HTML
{% extends 'base.html' %}
|
|
|
|
{% block title %}CSV Import Verlauf - Stiftungsverwaltung{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1><i class="fas fa-history text-primary"></i> CSV Import Verlauf</h1>
|
|
<a href="{% url 'stiftung:csv_import_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> Neuer Import
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card bg-primary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">{{ page_obj.paginator.count }}</h4>
|
|
<p class="card-text">Gesamt Imports</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-file-csv fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card bg-success text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">
|
|
{{ page_obj.paginator.count|default:0|add:"0"|floatformat:0 }}%
|
|
</h4>
|
|
<p class="card-text">Erfolgreich</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-check-circle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card bg-warning text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">
|
|
{{ page_obj.paginator.count|default:0|add:"0"|floatformat:0 }}%
|
|
</h4>
|
|
<p class="card-text">Teilweise</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-exclamation-triangle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card bg-danger text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">
|
|
{{ page_obj.paginator.count|default:0|add:"0"|floatformat:0 }}%
|
|
</h4>
|
|
<p class="card-text">Fehlgeschlagen</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-times-circle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Import List -->
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-light">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-list"></i> Import-Verlauf
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if page_obj %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Typ</th>
|
|
<th>Dateiname</th>
|
|
<th>Status</th>
|
|
<th>Ergebnis</th>
|
|
<th>Erstellt von</th>
|
|
<th>Gestartet</th>
|
|
<th>Dauer</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for import in page_obj %}
|
|
<tr>
|
|
<td>
|
|
<span class="badge bg-primary">
|
|
{{ import.get_import_type_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<strong>{{ import.filename }}</strong>
|
|
<br>
|
|
<small class="text-muted">
|
|
{{ import.file_size|filesizeformat }}
|
|
</small>
|
|
</td>
|
|
<td>
|
|
{% if import.status == 'completed' %}
|
|
<span class="badge bg-success">
|
|
<i class="fas fa-check"></i> Abgeschlossen
|
|
</span>
|
|
{% elif import.status == 'partial' %}
|
|
<span class="badge bg-warning">
|
|
<i class="fas fa-exclamation-triangle"></i> Teilweise
|
|
</span>
|
|
{% elif import.status == 'failed' %}
|
|
<span class="badge bg-danger">
|
|
<i class="fas fa-times"></i> Fehlgeschlagen
|
|
</span>
|
|
{% elif import.status == 'processing' %}
|
|
<span class="badge bg-info">
|
|
<i class="fas fa-spinner fa-spin"></i> Wird verarbeitet
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">
|
|
{{ import.get_status_display }}
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if import.total_rows > 0 %}
|
|
<div class="progress" style="height: 20px;">
|
|
{% widthratio import.imported_rows import.total_rows 100 as success_percent %}
|
|
<div class="progress-bar bg-success" style="width: {{ success_percent }}%">
|
|
{{ import.imported_rows }}/{{ import.total_rows }}
|
|
</div>
|
|
</div>
|
|
<small class="text-muted">
|
|
Erfolgsrate: {{ import.get_success_rate|floatformat:1 }}%
|
|
</small>
|
|
{% else %}
|
|
<span class="text-muted">Keine Daten</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<small>{{ import.created_by|default:"Unbekannt" }}</small>
|
|
</td>
|
|
<td>
|
|
<small>{{ import.started_at|date:"d.m.Y H:i" }}</small>
|
|
</td>
|
|
<td>
|
|
{% if import.get_duration %}
|
|
<small>{{ import.get_duration|floatformat:1 }}s</small>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-info btn-sm"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#importDetailModal{{ import.id }}">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
{% if import.error_log %}
|
|
<button type="button" class="btn btn-outline-warning btn-sm"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#errorModal{{ import.id }}">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Import Detail Modal -->
|
|
<div class="modal fade" id="importDetailModal{{ import.id }}" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Import-Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Allgemeine Informationen</h6>
|
|
<table class="table table-sm">
|
|
<tr><td>Typ:</td><td>{{ import.get_import_type_display }}</td></tr>
|
|
<tr><td>Dateiname:</td><td>{{ import.filename }}</td></tr>
|
|
<tr><td>Dateigröße:</td><td>{{ import.file_size|filesizeformat }}</td></tr>
|
|
<tr><td>Status:</td><td>{{ import.get_status_display }}</td></tr>
|
|
<tr><td>Erstellt von:</td><td>{{ import.created_by|default:"Unbekannt" }}</td></tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Ergebnisse</h6>
|
|
<table class="table table-sm">
|
|
<tr><td>Gesamtzeilen:</td><td>{{ import.total_rows }}</td></tr>
|
|
<tr><td>Importiert:</td><td>{{ import.imported_rows }}</td></tr>
|
|
<tr><td>Fehlgeschlagen:</td><td>{{ import.failed_rows }}</td></tr>
|
|
<tr><td>Erfolgsrate:</td><td>{{ import.get_success_rate|floatformat:1 }}%</td></tr>
|
|
<tr><td>Dauer:</td><td>{{ import.get_duration|floatformat:1 }}s</td></tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<h6>Zeitstempel</h6>
|
|
<p><strong>Gestartet:</strong> {{ import.started_at|date:"d.m.Y H:i:s" }}</p>
|
|
{% if import.completed_at %}
|
|
<p><strong>Abgeschlossen:</strong> {{ import.completed_at|date:"d.m.Y H:i:s" }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Log Modal -->
|
|
{% if import.error_log %}
|
|
<div class="modal fade" id="errorModal{{ import.id }}" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-warning text-dark">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-exclamation-triangle"></i> Fehlerprotokoll
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="alert alert-warning">
|
|
<strong>Fehler beim Import:</strong> {{ import.failed_rows }} Zeilen konnten nicht importiert werden.
|
|
</div>
|
|
<h6>Fehlerdetails:</h6>
|
|
<pre class="bg-light p-3 rounded"><code>{{ import.error_log }}</code></pre>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<nav aria-label="Import pagination">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1">« Erste</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Zurück</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Weiter</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Letzte »</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-file-csv fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">Keine CSV-Imports gefunden</h5>
|
|
<p class="text-muted">Starten Sie Ihren ersten CSV-Import, um Daten zu importieren.</p>
|
|
<a href="{% url 'stiftung:csv_import_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> Ersten Import starten
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|