Compare commits

...

8 Commits

Author SHA1 Message Date
Aiirondev_dev 0ef928efd8 slight fix of the scanner
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-16 15:50:48 +02:00
Aiirondev_dev 1299140823 Fix of the encryption processing with the Student class
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-16 13:17:20 +02:00
Aiirondev_dev ea48d5c28a decryption of the parsed fields
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-16 13:08:34 +02:00
Aiirondev_dev 514065f4af fixe of a list error
Release Inventarsystem / release-docker (push) Successful in 3m7s
2026-08-16 12:58:31 +02:00
Aiirondev_dev 90da8487f2 feat(student-cards): add class-specific PDF export with dynamic dropdown
Release Inventarsystem / release-docker (push) Successful in 2m16s
- Implement /student_card_class_barcode_download route to filter student cards by class
- Dynamically extract unique class names from the database for frontend selection
- Update student_cards_admin.html to replace the text input with a class selection dropdown
2026-08-15 18:10:05 +02:00
Aiirondev_dev b1ca358d41 Cleanup of the front end and backend of the upload function to only allow images and no videos because of space and memory reasons as well as hardening the uploading process.
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-15 01:38:35 +02:00
Aiirondev_dev 47cba5865d Removal of dupolicate function.
Release Inventarsystem / release-docker (push) Successful in 4m55s
2026-08-15 01:33:38 +02:00
Aiirondev_dev 4a776872e0 Improvement of the style for the Library Module
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-15 00:08:13 +02:00
4 changed files with 517 additions and 463 deletions
+391 -276
View File
File diff suppressed because it is too large Load Diff
+103 -160
View File
@@ -4,59 +4,60 @@
{% block content %} {% block content %}
<style> <style>
/* The Dark Background Overlay */ /* =========================================
1. Modals & Overlays
========================================= */
.modal { .modal {
display: none; /* Managed by JS (none/flex) */
position: fixed; position: fixed;
top: 0; inset: 0; /* Modern shorthand for top: 0, right: 0, bottom: 0, left: 0 */
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
/* Display is managed by JS (none/flex) */
align-items: center;
justify-content: center;
z-index: 1000; z-index: 1000;
background-color: rgba(0, 0, 0, 0.6);
align-items: center;
justify-content: center;
padding: 20px;
} }
/* The White Modal Box */
.modal-content { .modal-content {
background-color: #fff; position: relative;
padding: 20px;
width: 80%;
max-width: 600px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
/* Flexbox allows the header to stay fixed while the body scrolls */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 85vh; width: 100%;
max-width: 900px; /* Consolidated from conflicting 600px/900px rules */
max-height: 90vh;
background-color: var(--ui-surface, #fff);
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
overflow-y: auto;
} }
/* The Close Button */
.close { .close {
align-self: flex-end; position: absolute;
top: 15px;
right: 20px;
color: #aaa; color: #aaa;
font-size: 28px; font-size: 28px;
font-weight: bold; font-weight: bold;
cursor: pointer;
line-height: 1; line-height: 1;
margin-bottom: 10px; cursor: pointer;
transition: color 0.2s ease;
} }
.close:hover, .close:hover,
.close:focus { .close:focus {
color: #000; color: var(--ui-text, #000);
text-decoration: none; text-decoration: none;
} }
/* The Scrollable Content Area */
#detailContent { #detailContent {
overflow-y: auto; overflow-y: auto;
padding-right: 10px; padding-right: 10px;
} }
/* Library table-only view styles */ /* =========================================
2. Layout & Headers
========================================= */
.library-table-container { .library-table-container {
max-width: 1400px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
@@ -79,13 +80,15 @@
font-size: 0.95em; font-size: 0.95em;
} }
/* Library search bar */ /* =========================================
3. Search, Filter & Scan Panels
========================================= */
.library-search-bar { .library-search-bar {
margin-bottom: 20px;
display: flex; display: flex;
gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
gap: 10px;
margin-bottom: 20px;
} }
.library-search-input { .library-search-input {
@@ -95,10 +98,9 @@
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 6px; border-radius: 6px;
font-size: 16px; font-size: 16px;
background: white; background: #fff;
} }
/* Scanner panel */
.library-scan-panel { .library-scan-panel {
background: var(--ui-surface); background: var(--ui-surface);
border: 1px solid #d9dde4; border: 1px solid #d9dde4;
@@ -129,26 +131,18 @@
color: #4b5563; color: #4b5563;
} }
.library-scan-status.ok { .library-scan-status.ok { color: #1f7a38; }
color: #1f7a38; .library-scan-status.warn { color: #9a6700; }
} .library-scan-status.error { color: #b42318; }
.library-scan-status.warn {
color: #9a6700;
}
.library-scan-status.error {
color: #b42318;
}
.library-scan-reader-wrap { .library-scan-reader-wrap {
display: none; display: none;
margin-top: 12px; margin-top: 12px;
max-width: 460px;
background: var(--ui-surface);
border: 1px solid #d9dde4; border: 1px solid #d9dde4;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
background: var(--ui-surface);
max-width: 460px;
} }
.library-scan-reader { .library-scan-reader {
@@ -156,14 +150,15 @@
min-height: 280px; min-height: 280px;
} }
/* Filters */
.library-filter-toggle-btn { .library-filter-toggle-btn {
padding: 10px 16px; padding: 10px 16px;
background: #f0f2f5; background: #f0f2f5;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 6px; border-radius: 6px;
cursor: pointer;
font-weight: 500; font-weight: 500;
color: var(--ui-text); color: var(--ui-text);
cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
@@ -178,10 +173,9 @@
color: #4f46e5; color: #4f46e5;
} }
/* Filter panel */
.library-filter-panel { .library-filter-panel {
display: none; display: none;
background: white; background: #fff;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
@@ -210,10 +204,10 @@
} }
.filter-item label { .filter-item label {
margin-bottom: 6px;
font-weight: 600; font-weight: 600;
font-size: 0.9em; font-size: 0.9em;
color: var(--ui-text); color: var(--ui-text);
margin-bottom: 6px;
} }
.filter-item input, .filter-item input,
@@ -222,7 +216,8 @@
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
background: white; background: #fff;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
} }
.filter-item input:focus, .filter-item input:focus,
@@ -236,8 +231,8 @@
display: flex; display: flex;
gap: 10px; gap: 10px;
margin-top: 15px; margin-top: 15px;
border-top: 1px solid #eee;
padding-top: 15px; padding-top: 15px;
border-top: 1px solid #eee;
} }
.filter-buttons .button { .filter-buttons .button {
@@ -246,11 +241,13 @@
font-size: 14px; font-size: 14px;
} }
/* Library table */ /* =========================================
4. Tables & Data Display
========================================= */
.library-items-table { .library-items-table {
width: 100%; width: 100%;
background: #fff;
border-collapse: collapse; border-collapse: collapse;
background: white;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
@@ -266,8 +263,8 @@
padding: 14px 16px; padding: 14px 16px;
text-align: left; text-align: left;
font-weight: 600; font-weight: 600;
color: var(--ui-text);
font-size: 0.9em; font-size: 0.9em;
color: var(--ui-text);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
@@ -288,7 +285,6 @@
border-bottom: none; border-bottom: none;
} }
/* Table cells */
.table-title { .table-title {
font-weight: 600; font-weight: 600;
color: var(--ui-text); color: var(--ui-text);
@@ -302,26 +298,14 @@
font-weight: 500; font-weight: 500;
} }
.status-available { .status-available { background: #e8f5e9; color: #2e7d32; }
background: #e8f5e9; .status-borrowed { background: #fff3e0; color: #e65100; }
color: #2e7d32; .status-damaged { background: #fee2e2; color: #991b1b; }
}
.status-borrowed {
background: #fff3e0;
color: #e65100;
}
.status-damaged {
background: #fee2e2;
color: #991b1b;
}
/* Actions */
.table-actions { .table-actions {
display: flex; display: flex;
gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px;
} }
.table-actions .button { .table-actions .button {
@@ -330,26 +314,25 @@
white-space: nowrap; white-space: nowrap;
} }
/* Empty state */
.library-empty-state { .library-empty-state {
text-align: center;
padding: 60px 20px; padding: 60px 20px;
text-align: center;
color: #999; color: #999;
} }
.library-empty-state h3 { .library-empty-state h3 {
font-size: 1.3em;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 1.3em;
color: #666; color: #666;
} }
.library-load-row { .library-load-row {
margin-top: 12px;
display: flex; display: flex;
flex-wrap: wrap;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
flex-wrap: wrap; margin-top: 12px;
} }
.library-load-hint { .library-load-hint {
@@ -357,54 +340,9 @@
color: #6b7280; color: #6b7280;
} }
/* Small confirmation popup (toast) */ /* =========================================
.small-popup { 5. Edit Grid Forms
position: fixed; ========================================= */
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(17, 24, 39, 0.96);
color: #fff;
padding: 12px 16px;
border-radius: 8px;
box-shadow: 0 6px 24px rgba(2,6,23,0.6);
z-index: 2000;
display: flex;
gap: 10px;
align-items: center;
max-width: 90%;
font-size: 0.95em;
}
.small-popup.ok { background: rgba(16,185,129,0.95); color: #032; }
.small-popup.error { background: rgba(239,68,68,0.95); color: #210; }
.small-popup .close-x { margin-left: 8px; cursor: pointer; font-weight: 700; }
/* Modal styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
align-items: center;
justify-content: center;
padding: 20px;
}
.modal-content {
background-color: var(--ui-surface);
border-radius: 8px;
padding: 20px;
max-width: 900px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
position: relative;
}
.edit-grid { .edit-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@@ -416,10 +354,10 @@
} }
.edit-grid label { .edit-grid label {
display: block;
margin-bottom: 4px;
font-weight: 600; font-weight: 600;
font-size: 0.9em; font-size: 0.9em;
margin-bottom: 4px;
display: block;
} }
.edit-grid input, .edit-grid input,
@@ -437,37 +375,44 @@
margin-top: 14px; margin-top: 14px;
} }
.close { /* =========================================
position: absolute; 6. Notifications / Toasts
right: 20px; ========================================= */
top: 10px; .small-popup {
font-size: 28px; position: fixed;
font-weight: bold; bottom: 24px;
color: #999; left: 50%;
z-index: 2000;
display: flex;
align-items: center;
gap: 10px;
max-width: 90%;
padding: 12px 16px;
background: rgba(17, 24, 39, 0.96);
color: #fff;
border-radius: 8px;
font-size: 0.95em;
transform: translateX(-50%);
box-shadow: 0 6px 24px rgba(2,6,23,0.6);
}
.small-popup.ok { background: rgba(16,185,129,0.95); color: #032; }
.small-popup.error { background: rgba(239,68,68,0.95); color: #210; }
.small-popup .close-x {
margin-left: 8px;
font-weight: 700;
cursor: pointer; cursor: pointer;
} }
.close:hover { /* =========================================
color: var(--ui-text); 7. Mobile Responsiveness
} ========================================= */
/* Mobile responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.library-table-container { .library-table-container { padding: 10px; }
padding: 10px; .library-search-bar { flex-direction: column; }
} .library-search-input { width: 100%; }
.library-items-table { font-size: 0.85em; }
.library-search-bar {
flex-direction: column;
}
.library-search-input {
width: 100%;
}
.library-items-table {
font-size: 0.85em;
}
.library-items-table th, .library-items-table th,
.library-items-table td { .library-items-table td {
@@ -479,17 +424,12 @@
font-size: 0.75em; font-size: 0.75em;
} }
.filter-row { .filter-row,
grid-template-columns: 1fr;
}
.library-scan-controls { .library-scan-controls {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.library-scan-reader-wrap { .library-scan-reader-wrap { max-width: 100%; }
max-width: 100%;
}
} }
</style> </style>
@@ -876,12 +816,15 @@
if (readerWrap) readerWrap.style.display = 'block'; if (readerWrap) readerWrap.style.display = 'block';
setScanStatus('Initializing camera...', 'warn'); setScanStatus('Initializing camera...', 'warn');
const scannerContainer = document.querySelector('.library-scan-reader-wrap');
scannerContainer.style.display = 'block';
Quagga.init({ Quagga.init({
inputStream: { inputStream: {
name: "Live", name: "Live",
type: "LiveStream", type: "LiveStream",
target: document.querySelector('#library-scanner-container'), target: document.querySelector('.library-scan-reader'),
constraints: { constraints: {
width: 640, width: 640,
height: 480, height: 480,
+10
View File
@@ -232,6 +232,16 @@
<h1>📚 Bibliotheksausweise (Bibliothek)</h1> <h1>📚 Bibliotheksausweise (Bibliothek)</h1>
</div> </div>
<div class="export-buttons"> <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="background: #17a2b8; 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('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> <a href="{{ url_for('library_admin') }}" class="btn btn-primary">← Zur Bibliotheks-Upload</a>
</div> </div>
+13 -27
View File
@@ -836,7 +836,7 @@
<!-- Options will be loaded by JavaScript --> <!-- Options will be loaded by JavaScript -->
</select> </select>
</div> </div>
<div class="formupload_admin-group"> <div class="form-group">
<label for="filter2-4">Wert 4:</label> <label for="filter2-4">Wert 4:</label>
<select id="filter2-4" name="filter2" class="filter-dropdown-select"> <select id="filter2-4" name="filter2" class="filter-dropdown-select">
<option value="">-- Optional --</option> <option value="">-- Optional --</option>
@@ -876,11 +876,11 @@
</div> </div>
<!-- Image upload (hidden for library mode) --> <!-- Image upload -->
<div class="form-group"> <div class="form-group">
<label for="images">Bilder/Videos:</label> <label for="images">Bilder:</label>
<input type="file" id="images" name="images" accept=".jpg, .jpeg, .png, .gif, .mp4, .mov, .avi, .mkv, .webm, .flv, .m4v, .3gp" multiple> <input type="file" id="images" name="images" accept=".jpg, .jpeg, .png, .gif" multiple>
<div class="allowed-formats">Erlaubte Formate: JPG, JPEG, PNG, GIF, MP4, MOV, AVI, MKV, WEBM, FLV, M4V, 3GP</div> <div class="allowed-formats">Erlaubte Formate: JPG, JPEG, PNG, GIF</div>
<!-- Add image preview area --> <!-- Add image preview area -->
<div class="image-preview-container" id="image-preview-container"></div> <div class="image-preview-container" id="image-preview-container"></div>
</div> </div>
@@ -1857,28 +1857,25 @@
function setupImagePreview() { function setupImagePreview() {
const imageInput = document.getElementById('images'); const imageInput = document.getElementById('images');
const previewContainer = document.getElementById('image-preview-container'); const previewContainer = document.getElementById('image-preview-container');
if (imageInput && previewContainer) { if (imageInput && previewContainer) {
imageInput.addEventListener('change', function(e) { imageInput.addEventListener('change', function(e) {
previewContainer.innerHTML = ''; previewContainer.innerHTML = '';
const files = e.target.files; const files = e.target.files;
// Validate file types before preview // Validate file types before preview (Strictly Images)
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
'video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska',
'video/webm', 'video/x-flv', 'video/mp4', 'video/3gpp'];
let hasInvalidFile = false;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
if (!allowedTypes.includes(files[i].type)) { if (!allowedTypes.includes(files[i].type)) {
hasInvalidFile = true;
// Clear the file input to prevent submission // Clear the file input to prevent submission
imageInput.value = ''; imageInput.value = '';
previewContainer.innerHTML = '<div class="error-message">Fehler: Datei "' + files[i].name + '" hat ein nicht unterstütztes Format. Erlaubte Formate: JPG, JPEG, PNG, GIF, MP4, MOV, AVI, MKV, WEBM, FLV, M4V, 3GP</div>'; previewContainer.innerHTML = '<div class="error-message">Fehler: Datei "' + files[i].name + '" hat ein nicht unterstütztes Format. Erlaubte Formate: JPG, JPEG, PNG, GIF</div>';
return; // Stop processing return; // Stop processing
} }
} }
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];
if (file.type.startsWith('image/')) { if (file.type.startsWith('image/')) {
@@ -1893,17 +1890,6 @@
previewContainer.appendChild(preview); previewContainer.appendChild(preview);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} else if (file.type.startsWith('video/')) {
const preview = document.createElement('div');
preview.className = 'image-preview video-preview';
preview.innerHTML = `
<div class="video-placeholder">
<div class="video-icon">🎥</div>
<div class="video-name">${file.name}</div>
</div>
<button type="button" class="remove-image" onclick="removeImagePreview(this, ${i})">×</button>
`;
previewContainer.appendChild(preview);
} }
} }
}); });