Compare commits

...

4 Commits

Author SHA1 Message Date
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 510 additions and 460 deletions
+388 -274
View File
File diff suppressed because it is too large Load Diff
+99 -159
View File
@@ -4,59 +4,60 @@
{% block content %}
<style>
/* The Dark Background Overlay */
/* =========================================
1. Modals & Overlays
========================================= */
.modal {
display: none; /* Managed by JS (none/flex) */
position: fixed;
top: 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;
inset: 0; /* Modern shorthand for top: 0, right: 0, bottom: 0, left: 0 */
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 {
background-color: #fff;
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 */
position: relative;
display: flex;
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 {
align-self: flex-end;
position: absolute;
top: 15px;
right: 20px;
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
line-height: 1;
margin-bottom: 10px;
cursor: pointer;
transition: color 0.2s ease;
}
.close:hover,
.close:focus {
color: #000;
color: var(--ui-text, #000);
text-decoration: none;
}
/* The Scrollable Content Area */
#detailContent {
overflow-y: auto;
padding-right: 10px;
}
/* Library table-only view styles */
/* =========================================
2. Layout & Headers
========================================= */
.library-table-container {
max-width: 1400px;
margin: 0 auto;
@@ -79,13 +80,15 @@
font-size: 0.95em;
}
/* Library search bar */
/* =========================================
3. Search, Filter & Scan Panels
========================================= */
.library-search-bar {
margin-bottom: 20px;
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.library-search-input {
@@ -95,10 +98,9 @@
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
background: white;
background: #fff;
}
/* Scanner panel */
.library-scan-panel {
background: var(--ui-surface);
border: 1px solid #d9dde4;
@@ -129,26 +131,18 @@
color: #4b5563;
}
.library-scan-status.ok {
color: #1f7a38;
}
.library-scan-status.warn {
color: #9a6700;
}
.library-scan-status.error {
color: #b42318;
}
.library-scan-status.ok { color: #1f7a38; }
.library-scan-status.warn { color: #9a6700; }
.library-scan-status.error { color: #b42318; }
.library-scan-reader-wrap {
display: none;
margin-top: 12px;
max-width: 460px;
background: var(--ui-surface);
border: 1px solid #d9dde4;
border-radius: 8px;
overflow: hidden;
background: var(--ui-surface);
max-width: 460px;
}
.library-scan-reader {
@@ -156,14 +150,15 @@
min-height: 280px;
}
/* Filters */
.library-filter-toggle-btn {
padding: 10px 16px;
background: #f0f2f5;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
color: var(--ui-text);
cursor: pointer;
transition: all 0.2s ease;
}
@@ -178,10 +173,9 @@
color: #4f46e5;
}
/* Filter panel */
.library-filter-panel {
display: none;
background: white;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
@@ -210,10 +204,10 @@
}
.filter-item label {
margin-bottom: 6px;
font-weight: 600;
font-size: 0.9em;
color: var(--ui-text);
margin-bottom: 6px;
}
.filter-item input,
@@ -222,7 +216,8 @@
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background: white;
background: #fff;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.filter-item input:focus,
@@ -236,8 +231,8 @@
display: flex;
gap: 10px;
margin-top: 15px;
border-top: 1px solid #eee;
padding-top: 15px;
border-top: 1px solid #eee;
}
.filter-buttons .button {
@@ -246,11 +241,13 @@
font-size: 14px;
}
/* Library table */
/* =========================================
4. Tables & Data Display
========================================= */
.library-items-table {
width: 100%;
background: #fff;
border-collapse: collapse;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
@@ -266,8 +263,8 @@
padding: 14px 16px;
text-align: left;
font-weight: 600;
color: var(--ui-text);
font-size: 0.9em;
color: var(--ui-text);
text-transform: uppercase;
letter-spacing: 0.5px;
}
@@ -288,7 +285,6 @@
border-bottom: none;
}
/* Table cells */
.table-title {
font-weight: 600;
color: var(--ui-text);
@@ -302,26 +298,14 @@
font-weight: 500;
}
.status-available {
background: #e8f5e9;
color: #2e7d32;
}
.status-available { background: #e8f5e9; color: #2e7d32; }
.status-borrowed { background: #fff3e0; color: #e65100; }
.status-damaged { background: #fee2e2; color: #991b1b; }
.status-borrowed {
background: #fff3e0;
color: #e65100;
}
.status-damaged {
background: #fee2e2;
color: #991b1b;
}
/* Actions */
.table-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
gap: 8px;
}
.table-actions .button {
@@ -330,26 +314,25 @@
white-space: nowrap;
}
/* Empty state */
.library-empty-state {
text-align: center;
padding: 60px 20px;
text-align: center;
color: #999;
}
.library-empty-state h3 {
font-size: 1.3em;
margin-bottom: 10px;
font-size: 1.3em;
color: #666;
}
.library-load-row {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
margin-top: 12px;
}
.library-load-hint {
@@ -357,54 +340,9 @@
color: #6b7280;
}
/* Small confirmation popup (toast) */
.small-popup {
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;
}
/* =========================================
5. Edit Grid Forms
========================================= */
.edit-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -416,10 +354,10 @@
}
.edit-grid label {
display: block;
margin-bottom: 4px;
font-weight: 600;
font-size: 0.9em;
margin-bottom: 4px;
display: block;
}
.edit-grid input,
@@ -437,37 +375,44 @@
margin-top: 14px;
}
.close {
position: absolute;
right: 20px;
top: 10px;
font-size: 28px;
font-weight: bold;
color: #999;
/* =========================================
6. Notifications / Toasts
========================================= */
.small-popup {
position: fixed;
bottom: 24px;
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;
}
.close:hover {
color: var(--ui-text);
}
/* Mobile responsive */
/* =========================================
7. Mobile Responsiveness
========================================= */
@media (max-width: 768px) {
.library-table-container {
padding: 10px;
}
.library-search-bar {
flex-direction: column;
}
.library-search-input {
width: 100%;
}
.library-items-table {
font-size: 0.85em;
}
.library-table-container { padding: 10px; }
.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 td {
@@ -479,17 +424,12 @@
font-size: 0.75em;
}
.filter-row {
grid-template-columns: 1fr;
}
.filter-row,
.library-scan-controls {
grid-template-columns: 1fr;
}
.library-scan-reader-wrap {
max-width: 100%;
}
.library-scan-reader-wrap { max-width: 100%; }
}
</style>
+10
View File
@@ -232,6 +232,16 @@
<h1>📚 Bibliotheksausweise (Bibliothek)</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="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('library_admin') }}" class="btn btn-primary">← Zur Bibliotheks-Upload</a>
</div>
+13 -27
View File
@@ -836,7 +836,7 @@
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="formupload_admin-group">
<div class="form-group">
<label for="filter2-4">Wert 4:</label>
<select id="filter2-4" name="filter2" class="filter-dropdown-select">
<option value="">-- Optional --</option>
@@ -876,11 +876,11 @@
</div>
<!-- Image upload (hidden for library mode) -->
<!-- Image upload -->
<div class="form-group">
<label for="images">Bilder/Videos:</label>
<input type="file" id="images" name="images" accept=".jpg, .jpeg, .png, .gif, .mp4, .mov, .avi, .mkv, .webm, .flv, .m4v, .3gp" multiple>
<div class="allowed-formats">Erlaubte Formate: JPG, JPEG, PNG, GIF, MP4, MOV, AVI, MKV, WEBM, FLV, M4V, 3GP</div>
<label for="images">Bilder:</label>
<input type="file" id="images" name="images" accept=".jpg, .jpeg, .png, .gif" multiple>
<div class="allowed-formats">Erlaubte Formate: JPG, JPEG, PNG, GIF</div>
<!-- Add image preview area -->
<div class="image-preview-container" id="image-preview-container"></div>
</div>
@@ -1857,28 +1857,25 @@
function setupImagePreview() {
const imageInput = document.getElementById('images');
const previewContainer = document.getElementById('image-preview-container');
if (imageInput && previewContainer) {
imageInput.addEventListener('change', function(e) {
previewContainer.innerHTML = '';
const files = e.target.files;
// Validate file types before preview
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;
// Validate file types before preview (Strictly Images)
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
for (let i = 0; i < files.length; i++) {
if (!allowedTypes.includes(files[i].type)) {
hasInvalidFile = true;
// Clear the file input to prevent submission
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
}
}
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type.startsWith('image/')) {
@@ -1893,17 +1890,6 @@
previewContainer.appendChild(preview);
};
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);
}
}
});