557 lines
18 KiB
HTML
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 %} |