Compare commits

...

10 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
Aiirondev_dev 55a74654db Addition of a missing ','
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-14 23:18:29 +02:00
Aiirondev_dev 7473334714 Changes to the Inventarsystem Workflow to Reflekt the new workflow and have an according set of buttons. So this removed all metioning of the duplication function.
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-14 23:01:01 +02:00
5 changed files with 528 additions and 854 deletions
+391 -474
View File
File diff suppressed because it is too large Load Diff
+104 -161
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>
@@ -876,12 +816,15 @@
if (readerWrap) readerWrap.style.display = 'block';
setScanStatus('Initializing camera...', 'warn');
const scannerContainer = document.querySelector('.library-scan-reader-wrap');
scannerContainer.style.display = 'block';
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#library-scanner-container'),
target: document.querySelector('.library-scan-reader'),
constraints: {
width: 640,
height: 480,
@@ -939,7 +882,7 @@
const response = await fetch(`/api/is_student_card/${encodeURIComponent(code)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
// Falls du in Flask CSRF-Protect nutzt, muss der Token mitgesendet werden:
'X-CSRFToken': '{{ csrf_token }}',
'X-CSRF-Token': '{{ csrf_token }}'
+6 -80
View File
@@ -819,7 +819,7 @@
flex: 0 0 auto;
}
.ausleihen, .edit-button, .delete-button, .details-button, .duplicate-button, .schedule-button, .damage-button {
.ausleihen, .edit-button, .delete-button, .details-button, .schedule-button, .damage-button {
padding: 10px 18px;
border: none;
border-radius: 5px;
@@ -942,27 +942,6 @@
box-shadow: 0 2px 4px rgba(23, 162, 184, 0.3);
}
/* Duplicate Button */
.duplicate-button {
background-color: #6f42c1;
color: white;
border: 2px solid #6f42c1;
}
.duplicate-button:hover {
background-color: #5a32a3;
border-color: #4e2a8e;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(111, 66, 193, 0.3);
color: white;
text-decoration: none;
}
.duplicate-button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(111, 66, 193, 0.3);
}
.damage-button {
background-color: #fd7e14;
color: white;
@@ -1645,7 +1624,7 @@
gap: 10px !important;
}
.ausleihen, .edit-button, .delete-button, .details-button, .duplicate-button, .schedule-button {
.ausleihen, .edit-button, .delete-button, .details-button, .schedule-button {
width: 100% !important;
min-height: 44px !important;
padding: 12px 20px !important;
@@ -1777,7 +1756,7 @@
justify-content: flex-start;
}
.ausleihen, .edit-button, .delete-button, .details-button, .duplicate-button, .schedule-button {
.ausleihen, .edit-button, .delete-button, .details-button, .schedule-button {
padding: 10px 20px;
font-size: 0.9rem;
min-width: 110px;
@@ -1790,7 +1769,7 @@
gap: 8px;
}
.ausleihen, .edit-button, .delete-button, .details-button, .duplicate-button, .schedule-button {
.ausleihen, .edit-button, .delete-button, .details-button, .schedule-button {
padding: 8px 16px;
font-size: 0.85rem;
min-width: 95px;
@@ -1808,7 +1787,7 @@
gap: 15px;
}
.ausleihen, .edit-button, .delete-button, .details-button, .duplicate-button, .schedule-button {
.ausleihen, .edit-button, .delete-button, .details-button, .schedule-button {
padding: 12px 24px;
font-size: 1rem;
min-width: 120px;
@@ -1980,7 +1959,7 @@
}
/* Standardize form and document buttons */
.save-button, .cancel-button, .remove-book-cover-button, .remove-duplicate-image-button,
.save-button, .cancel-button, .remove-book-cover-button,
.import-book-button, .fetch-isbn-button, .nav-back-button {
display: inline-flex;
align-items: center;
@@ -3465,9 +3444,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
{% if current_permissions.actions.get('can_edit', False) %}
<button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-card-${item._id}')">Bearbeiten</button>
{% endif %}
{% if current_permissions.actions.get('can_insert', False) %}
<button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button>
{% endif %}
{% if current_permissions.pages.get('admin_school_settings', False) %}
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
{% endif %}
@@ -4244,7 +4220,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
: `<button class="ausleihen disabled-button" disabled>Ausgeliehen</button>`
}
<button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-modal-${item._id}')">Bearbeiten</button>
<button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button>
${damageReports && damageReports.length > 0 ? `<button class="damage-button" onclick="markDamageAsRepaired('${item._id}')">Repariert</button>` : `<button class="damage-button" onclick="registerDamage('${item._id}')">Schaden melden</button>`}
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
<form method="POST" action="/delete_item/${item._id}" style="display:inline;" onsubmit="return confirm('Sind Sie sicher?')">
@@ -5044,55 +5019,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
}
// Duplication function
function duplicateItem(itemId) {
// Show loading indicator
const loadingDiv = document.createElement('div');
loadingDiv.innerHTML = `
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); z-index: 10000; display: flex;
align-items: center; justify-content: center;">
<div class="modal-dialog-white">
<div>Element wird dupliziert...</div>
<div class="modal-content-margin">
<div class="spinner"></div>
</div>
</div>
</div>
`;
document.body.appendChild(loadingDiv);
// Create form data for the duplicate_item request
const formData = new FormData();
formData.append('original_item_id', itemId);
// Send duplication request to get item data
fetch('/duplicate_item', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
document.body.removeChild(loadingDiv);
if (data.success) {
// Store duplication data in sessionStorage for the upload page
sessionStorage.setItem('duplicateItemData', JSON.stringify(data.item_data));
// Redirect to upload admin page
window.location.href = '/upload_admin?duplicate=true';
} else {
alert('Fehler beim Duplizieren: ' + (data.message || 'Unbekannter Fehler'));
}
})
.catch(error => {
document.body.removeChild(loadingDiv);
console.error('Error duplicating item:', error);
alert('Fehler beim Duplizieren des Elements. Bitte versuchen Sie es erneut.');
});
}
// Helper functions for appointment display
function formatAppointmentDate(dateString) {
if (!dateString) return '';
+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>
+17 -139
View File
@@ -11,59 +11,6 @@
{% block title %}{{ page_title|default('Artikel hochladen') }} - Inventarsystem{% endblock %}
{% block content %}
{% if duplicate_data and duplicate_data.images %}
<!-- Hidden server-side values for duplication, will be processed by JavaScript -->
<div id="server-duplicate-data"
data-name="{{ duplicate_data.name|default('') }}"
data-description="{{ duplicate_data.description|default('') }}"
data-location="{{ duplicate_data.location|default('') }}"
data-room="{{ duplicate_data.room|default('') }}"
data-year="{{ duplicate_data.year|default('') }}"
data-cost="{{ duplicate_data.cost|default('') }}"
data-images="{{ duplicate_data.images|tojson|safe }}"
data-filter1="{{ duplicate_data.filter1|tojson|safe if duplicate_data.filter1 else '[]' }}"
data-filter2="{{ duplicate_data.filter2|tojson|safe if duplicate_data.filter2 else '[]' }}"
data-filter3="{{ duplicate_data.filter3|tojson|safe if duplicate_data.filter3 else '[]' }}"
data-original-id="{{ duplicate_data.original_id|default('') }}"
style="display:none;">
</div>
<script>
// Pre-initialize duplicate data from server
var serverDuplicateData = null;
const dataElement = document.getElementById('server-duplicate-data');
if (dataElement) {
// Safely parse JSON arrays with error handling
function safeJsonParse(jsonStr, defaultValue = []) {
try {
if (!jsonStr) return defaultValue;
return JSON.parse(jsonStr);
} catch (e) {
console.error("Error parsing JSON:", e, "Value was:", jsonStr);
return defaultValue;
}
}
// Get images data and log it for debugging
const imagesData = dataElement.getAttribute('data-images') || "[]";
console.log("Raw images data attribute:", imagesData);
serverDuplicateData = {
name: dataElement.getAttribute('data-name') || "",
description: dataElement.getAttribute('data-description') || "",
location: dataElement.getAttribute('data-location') || "",
room: dataElement.getAttribute('data-room') || "",
year: dataElement.getAttribute('data-year') || "",
cost: dataElement.getAttribute('data-cost') || "",
images: safeJsonParse(imagesData),
filter1: safeJsonParse(dataElement.getAttribute('data-filter1')),
filter2: safeJsonParse(dataElement.getAttribute('data-filter2')),
filter3: safeJsonParse(dataElement.getAttribute('data-filter3')),
original_id: dataElement.getAttribute('data-original-id') || ""
};
console.log("Server-provided duplicate data:", serverDuplicateData);
}
</script>
{% endif %}
<style>
/* Book information display styles */
@@ -296,38 +243,11 @@
color: #721c24 !important;
}
/* Duplicate code popup styling */
.popup-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
animation: overlayFadeIn 0.3s ease;
}
@keyframes overlayFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.duplicate-code-popup {
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 90%;
text-align: center;
position: relative;
animation: popupSlideIn 0.3s ease;
}
@keyframes popupSlideIn {
from {
opacity: 0;
@@ -916,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>
@@ -956,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>
@@ -1878,11 +1798,11 @@
if (existingPopup) {
existingPopup.remove();
}
// Create popup overlay
const overlay = document.createElement('div');
overlay.className = 'popup-overlay';
// Create popup content
overlay.innerHTML = `
<div class="duplicate-code-popup">
@@ -1895,9 +1815,9 @@
<button class="popup-close-button" onclick="this.closest('.popup-overlay').remove()">Verstanden</button>
</div>
`;
document.body.appendChild(overlay);
// Auto-remove after 10 seconds
setTimeout(() => {
if (overlay.parentNode) {
@@ -1937,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/')) {
@@ -1973,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);
}
}
});
@@ -2049,34 +1955,6 @@
}
});
}
// Handle duplication data if available (from sessionStorage or server)
if (typeof prefillFormWithDuplicateData === 'function') {
setTimeout(() => {
prefillFormWithDuplicateData();
}, 500); // Wait for all dropdowns to load
}
});
// Initialize duplication data - handle both server-side and sessionStorage data
let duplicateData = null;
// First check if serverDuplicateData is defined by the script above
if (typeof serverDuplicateData !== 'undefined' && serverDuplicateData) {
console.log("Using server-provided duplicateData:", serverDuplicateData);
duplicateData = serverDuplicateData;
// Ensure images is properly handled as an array
if (duplicateData.images) {
if (typeof duplicateData.images === 'string') {
try {
duplicateData.images = JSON.parse(duplicateData.images);
} catch (e) {
console.error("Error parsing duplicate images array:", e);
}
}
console.log("Parsed duplicate images successfully.");
}
}
</script>
{% endblock %}