Files
Inventarsystem/Web/templates/student_cards_admin.html
T
Aiirondev_dev e71567db11
Release Inventarsystem / release-docker (push) Successful in 2m26s
changes toi the darkmode style to have iot be a better edesign
2026-09-14 10:54:19 +02:00

557 lines
18 KiB
HTML

<!--
Copyright 2025-2026 AIIrondev
Licensed under the Inventarsystem EULA (Endbenutzer-Lizenzvertrag).
See Legal/LICENSE for the full license text.
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
For commercial licensing inquiries: https://github.com/AIIrondev
-->
{% extends "base.html" %}
{% block title %}Bibliotheksausweise - Inventarsystem{% endblock %}
{% block content %}
<style>
.student-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
/* Neu strukturiertes Layout für Formular & Import */
.dashboard-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.student-card-form {
background: var(--ui-bg);
padding: 20px;
border-radius: 8px;
}
.import-card {
border: 1px solid #dbe4ee;
border-radius: 8px;
padding: 20px;
background: #f8fbff;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.rollover-box {
background: #fff3cd;
border: 1px solid #ffeeba;
border-radius: 6px;
padding: 10px 12px;
margin-top: 4px;
}
.rollover-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 13px;
color: #856404;
font-weight: 700;
margin: 0;
}
.rollover-hint {
display: block;
margin-top: 4px;
font-size: 11px;
color: #856404;
line-height: 1.3;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.form-row.full-width {
grid-template-columns: 1fr;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
font-weight: 600;
margin-bottom: 5px;
color: var(--ui-text);
}
.form-group input,
.form-group select,
.form-group textarea {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
}
.form-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 10px;
}
.form-actions button, .form-actions a {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
text-decoration: none;
}
.btn-save {
background: #28a745;
color: white;
}
.btn-save:hover {
background: #218838;
}
.btn-cancel {
background: #6c757d;
color: white;
}
.btn-cancel:hover {
background: #5a6268;
}
.cards-table {
width: 100%;
border-collapse: collapse;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.cards-table thead {
background: #007bff;
color: white;
font-weight: 600;
}
.cards-table th,
.cards-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
.cards-table tbody tr:hover {
background: #f9f9f9;
}
.cards-table tbody tr:last-child td {
border-bottom: none;
}
.card-actions {
display: flex;
gap: 8px;
align-items: center;
}
.btn-edit, .btn-delete, .btn-export {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
text-decoration: none;
display: inline-block;
}
.btn-edit {
background: #007bff;
color: white;
}
.btn-edit:hover {
background: #0056b3;
}
.btn-delete {
background: #dc3545;
color: white;
}
.btn-delete:hover {
background: #c82333;
}
.btn-print {
padding: 10px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
text-decoration: none;
display: inline-block;
background: #17a2b8;
color: white;
}
.btn-print:hover {
background: #138496;
}
.btn-export {
background: #6f42c1;
color: white;
}
.btn-export:hover {
background: #5a32a3;
}
.export-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
}
.empty-state p {
font-size: 16px;
margin: 10px 0;
}
:root[data-theme="dark"] .student-card-form,
:root[data-theme="dark"] .import-card {
background: var(--ui-surface) !important;
color: var(--ui-text) !important;
border-color: var(--ui-border) !important;
box-shadow: var(--ui-shadow-sm);
}
:root[data-theme="dark"] .import-card {
background: var(--ui-surface-soft) !important;
}
:root[data-theme="dark"] .import-card p,
:root[data-theme="dark"] .rollover-hint,
:root[data-theme="dark"] .empty-state,
:root[data-theme="dark"] .empty-state p {
color: var(--ui-text-muted) !important;
}
:root[data-theme="dark"] .rollover-box {
background: #422006 !important;
border-color: #f59e0b !important;
}
:root[data-theme="dark"] .rollover-label,
:root[data-theme="dark"] .rollover-hint {
color: #fde68a !important;
}
:root[data-theme="dark"] .form-group input,
:root[data-theme="dark"] .form-group select,
:root[data-theme="dark"] .form-group textarea,
:root[data-theme="dark"] .export-buttons form,
:root[data-theme="dark"] .export-buttons select {
background: var(--ui-bg) !important;
background-color: var(--ui-bg) !important;
color: var(--ui-text) !important;
border-color: var(--ui-border) !important;
}
:root[data-theme="dark"] .form-group input:focus,
:root[data-theme="dark"] .form-group select:focus,
:root[data-theme="dark"] .form-group textarea:focus,
:root[data-theme="dark"] .export-buttons select:focus {
border-color: #60a5fa !important;
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.22) !important;
}
:root[data-theme="dark"] .cards-table {
background: var(--ui-surface) !important;
color: var(--ui-text) !important;
border-color: var(--ui-border) !important;
box-shadow: var(--ui-shadow-sm);
}
:root[data-theme="dark"] .cards-table thead {
background: #1d4ed8 !important;
color: #ffffff !important;
}
:root[data-theme="dark"] .cards-table th,
:root[data-theme="dark"] .cards-table td {
background: var(--ui-surface) !important;
color: var(--ui-text) !important;
border-color: var(--ui-border) !important;
}
:root[data-theme="dark"] .cards-table tbody tr:hover td {
background: var(--ui-bg-accent) !important;
}
@media (max-width: 768px) {
.export-buttons {
width: 100%;
}
.export-buttons form,
.export-buttons > a {
flex: 1 1 100%;
}
}
@media (max-width: 992px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.student-card-header {
flex-direction: column;
align-items: flex-start;
}
.cards-table {
font-size: 14px;
}
.cards-table th,
.cards-table td {
padding: 8px 10px;
}
.card-actions {
flex-direction: column;
align-items: stretch;
}
}
</style>
<div class="container">
<!-- Header mit Aktionen -->
<div class="student-card-header">
<div>
<h1 style="margin: 0;">📚 Bibliotheksausweise</h1>
</div>
<div class="export-buttons">
<form method="GET" action="{{ url_for('student_card_class_barcode_download') }}" style="display: inline-flex; gap: 5px; align-items: center; background: white; padding: 2px; border-radius: 4px; border: 1px solid #ddd;">
<select name="class_name" required style="border: none; padding: 8px; outline: none; font-size: 14px; background: transparent; cursor: pointer;">
<option value="" disabled selected>-- Klasse wählen --</option>
{% for cls in available_classes %}
<option value="{{ cls }}">{{ cls }}</option>
{% endfor %}
</select>
<button type="submit" class="btn-print" style="padding: 8px 12px; margin: 0;">📤 PDF</button>
</form>
<a href="{{ url_for('student_card_barcode_download') }}" class="btn-print" style="background: #28a745;">📥 Alle Ausweise (PDF)</a>
<a href="{{ url_for('library_admin') }}" class="btn btn-primary">← Zur Bibliotheks-Upload</a>
</div>
</div>
<!-- Hauptbereich Grid: Formular + Import -->
<div class="dashboard-grid">
<!-- Add/Edit Form -->
<div class="student-card-form">
<h2 style="margin-top:0;">{% if edit_mode %}Ausweis bearbeiten{% else %}Neuer Bibliotheksausweis{% endif %}</h2>
<form method="POST" action="{{ url_for('student_cards_admin') }}">
{% if edit_mode %}
<input type="hidden" name="action" value="edit">
<input type="hidden" name="card_id" value="{{ form_data.get('card_id', '') }}">
{% else %}
<input type="hidden" name="action" value="add">
{% endif %}
<div class="form-row">
<div class="form-group">
<label for="student_name">Schüler Name *</label>
<input type="text" id="student_name" name="student_name" required
value="{{ form_data.get('student_name', '') }}"
placeholder="z.B. Max Mustermann">
</div>
<div class="form-group">
<label for="class_name">Klasse</label>
<input type="text" id="class_name" name="class_name" list="class_list"
value="{{ form_data.get('class_name', '') }}"
placeholder="z.B. 10A (Tippen oder Auswählen)">
<datalist id="class_list">
{% for cls in available_classes %}
<option value="{{ cls }}">
{% endfor %}
</datalist>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="ausweis_id">Ausweis-ID</label>
<input type="text" id="ausweis_id" name="ausweis_id"
value="{{ form_data.get('ausweis_id', '') }}"
placeholder="z.B. SIS2024001">
</div>
<div class="form-group">
<label for="ausweis_ident">Lokales Differenzierungsmerkmal</label>
<input type="text" id="ausweis_ident" name="ausweis_ident"
value="{{ form_data.get('ausweis_ident', '') }}"
placeholder="z.B. xw5oo123bbe">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="default_borrow_days">Standard Ausleihdauer (Tage) *</label>
<input type="number" id="default_borrow_days" name="default_borrow_days"
min="1" max="365" required
value="{{ form_data.get('default_borrow_days', config.get('default', 14)) }}">
</div>
</div>
<div class="form-row full-width">
<div class="form-group">
<label for="notes">Notizen</label>
<textarea id="notes" name="notes" rows="2"
placeholder="Optionale Notizen...">{{ form_data.get('notes', '') }}</textarea>
</div>
</div>
<div class="form-actions">
{% if edit_mode %}
<a href="{{ url_for('student_cards_admin') }}" class="btn-cancel">Abbrechen</a>
{% endif %}
<button type="submit" class="btn-save">
{% if edit_mode %}Speichern{% else %}Hinzufügen{% endif %}
</button>
</div>
</form>
</div>
<!-- Excel-Import mit Abgleich (Rollover-Modus) -->
<div class="import-card">
<div>
<h3 style="margin:0 0 8px 0;">Excel-Import</h3>
<p style="margin:0 0 12px 0; color:#555; font-size:13px; line-height:1.4;">
Laden Sie eine <strong>.xlsx</strong>- oder <strong>.csv</strong>-Datei hoch (z. B. aus <strong>ASV</strong>). Erkannt werden Name, Nachname, Klasse, Ausweis-ID, lokales Differenzierungsmerkmal, Notizen & Ausleihdauer.
</p>
</div>
<form method="POST" action="{{ url_for('upload_student_cards_excel') }}" enctype="multipart/form-data" style="display:flex; flex-direction:column; gap:12px;">
<input type="file" name="student_cards_excel" accept=".xlsx,.csv" required style="font-size:13px;">
<!-- Rollover / Abgleich Option -->
<div class="rollover-box">
<label class="rollover-label">
<input type="checkbox" name="rollover_mode" value="true">
<span>Rollover-Modus (Abgleich / Destruktiv)</span>
</label>
<span class="rollover-hint">
⚠️ <strong>Warnung:</strong> Nicht mehr vorhandene Ausweise/Schüler werden beim Import entfernt bzw. abgeglichen.
</span>
</div>
<div style="display:flex; gap:8px;">
<button type="submit" class="btn btn-secondary" name="excel_action" value="validate" style="flex:1;">Validieren</button>
<button type="submit" class="btn btn-primary" name="excel_action" value="import" style="flex:1;">Importieren</button>
</div>
</form>
</div>
</div>
<!-- Cards List -->
<div>
<h2 style="margin-bottom: 15px;">Registrierte Ausweise</h2>
{% if student_cards %}
<table class="cards-table">
<thead>
<tr>
<th>Ausweis-ID</th>
<th>Schüler Name</th>
<th>Klasse</th>
<th>Standard Ausleihdauer</th>
<th>Erstellt</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for card in student_cards %}
<tr>
<td><strong>{{ card.AusweisId }}</strong></td>
<td>{{ card.SchülerName }}</td>
<td>{{ card.Klasse or '—' }}</td>
<td>{{ card.StandardAusleihdauer }} Tage</td>
<td>{{ card.Erstellt.strftime('%d.%m.%Y') if card.get('Erstellt') else '—' }}</td>
<td>
<div class="card-actions">
<form method="GET" style="display: inline;">
<input type="hidden" name="edit" value="{{ card._id }}">
<button type="submit" class="btn-edit">Bearbeiten</button>
</form>
<a href="{{ url_for('student_card_single_barcode_download', card_id=card._id) }}" class="btn-export">📥 PDF</a>
<form method="POST" style="display: inline;" onsubmit="return confirm('Wirklich löschen?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="card_id" value="{{ card._id }}">
<button type="submit" class="btn-delete">Löschen</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty-state">
<p>Keine Bibliotheksausweise registriert.</p>
<p>Fügen Sie ein neues Ausweis oben hinzu.</p>
</div>
{% endif %}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/qrcode.js"></script>
<script>
// All PDF exports now go through backend routes
</script>
{% endblock %}