Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96771d192c | |||
| 073652c63d | |||
| 23d9d0871a | |||
| 576d31a384 | |||
| c43bb30301 |
+96
-1
@@ -166,7 +166,7 @@ SCHEDULER_INTERVAL = cfg.SCHEDULER_INTERVAL_MIN
|
|||||||
SSL_CERT = cfg.SSL_CERT
|
SSL_CERT = cfg.SSL_CERT
|
||||||
SSL_KEY = cfg.SSL_KEY
|
SSL_KEY = cfg.SSL_KEY
|
||||||
|
|
||||||
LIBRARY_ITEM_TYPES = ['book', 'cd', 'dvd', 'media']
|
LIBRARY_ITEM_TYPES = ['book', 'cd', 'dvd', 'media', 'schulbuch']
|
||||||
INVOICE_CURRENCY = 'EUR'
|
INVOICE_CURRENCY = 'EUR'
|
||||||
|
|
||||||
NOTIFICATION_STATUS_CACHE_TTL = max(3, int(os.getenv('INVENTAR_NOTIFICATION_STATUS_CACHE_TTL', '8')))
|
NOTIFICATION_STATUS_CACHE_TTL = max(3, int(os.getenv('INVENTAR_NOTIFICATION_STATUS_CACHE_TTL', '8')))
|
||||||
@@ -2717,6 +2717,49 @@ def tutorial_page():
|
|||||||
)
|
)
|
||||||
|
|
||||||
@app.route('/library')
|
@app.route('/library')
|
||||||
|
|
||||||
|
@app.route('/library/export/<scope>')
|
||||||
|
def library_export_excel(scope):
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
username = session['username']
|
||||||
|
is_admin_user = us.check_admin(username)
|
||||||
|
|
||||||
|
import excel_export
|
||||||
|
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
items_collection = db['items']
|
||||||
|
|
||||||
|
query = {'ItemType': {'$in': LIBRARY_ITEM_TYPES}, 'Deleted': {'$ne': True}}
|
||||||
|
|
||||||
|
if scope == 'borrowed_by_me':
|
||||||
|
query['User'] = username
|
||||||
|
query['Verfuegbar'] = False
|
||||||
|
filename = f"Bibliothek_Ausgeliehen_{username}.xlsx"
|
||||||
|
elif scope == 'all_borrowed':
|
||||||
|
if not is_admin_user:
|
||||||
|
return redirect(url_for('library_view'))
|
||||||
|
query['Verfuegbar'] = False
|
||||||
|
filename = "Bibliothek_Alle_Ausleihen.xlsx"
|
||||||
|
elif scope == 'schulbuecher':
|
||||||
|
query['ItemType'] = 'schulbuch'
|
||||||
|
filename = "Schulbuecher_Bestand.xlsx"
|
||||||
|
else:
|
||||||
|
# 'all'
|
||||||
|
filename = "Bibliothek_Gesamtbestand.xlsx"
|
||||||
|
|
||||||
|
items = list(items_collection.find(query))
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
excel_file = excel_export.generate_library_excel(items)
|
||||||
|
return flask.send_file(
|
||||||
|
excel_file,
|
||||||
|
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
as_attachment=True,
|
||||||
|
download_name=filename
|
||||||
|
)
|
||||||
|
|
||||||
def library_view():
|
def library_view():
|
||||||
"""
|
"""
|
||||||
Dedicated page for viewing library items (books, CDs, etc.).
|
Dedicated page for viewing library items (books, CDs, etc.).
|
||||||
@@ -8589,11 +8632,15 @@ def manage_filters():
|
|||||||
filter2_values = it.get_predefined_filter_values(2)
|
filter2_values = it.get_predefined_filter_values(2)
|
||||||
filter3_values = it.get_predefined_filter_values(3)
|
filter3_values = it.get_predefined_filter_values(3)
|
||||||
|
|
||||||
|
# Get custom filter names
|
||||||
|
filter_names = it.get_filter_names()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'manage_filters.html',
|
'manage_filters.html',
|
||||||
filter1_values=filter1_values,
|
filter1_values=filter1_values,
|
||||||
filter2_values=filter2_values,
|
filter2_values=filter2_values,
|
||||||
filter3_values=filter3_values,
|
filter3_values=filter3_values,
|
||||||
|
filter_names=filter_names,
|
||||||
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
||||||
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
||||||
)
|
)
|
||||||
@@ -8657,6 +8704,41 @@ def remove_filter_value(filter_num, value):
|
|||||||
|
|
||||||
return redirect(url_for('manage_filters'))
|
return redirect(url_for('manage_filters'))
|
||||||
|
|
||||||
|
@app.route('/edit_filter_value/<int:filter_num>/<string:old_value>', methods=['POST'])
|
||||||
|
def edit_filter_value(filter_num, old_value):
|
||||||
|
"""
|
||||||
|
Edit a predefined value from the specified filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_num (int): Filter number (1, 2 or 3)
|
||||||
|
old_value (str): Value to edit
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
flask.Response: Redirect to filter management page
|
||||||
|
"""
|
||||||
|
if 'username' not in session or not us.check_admin(session['username']):
|
||||||
|
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
||||||
|
|
||||||
|
new_value = sanitize_form_value(request.form.get('new_value'))
|
||||||
|
|
||||||
|
if not new_value:
|
||||||
|
flash('Bitte geben Sie einen neuen Wert ein', 'error')
|
||||||
|
return redirect(url_for('manage_filters'))
|
||||||
|
|
||||||
|
if new_value == FILTER_SELECT_ALL_TOKEN:
|
||||||
|
flash('Dieser Wert ist reserviert und kann nicht als Filterwert gespeichert werden.', 'error')
|
||||||
|
return redirect(url_for('manage_filters'))
|
||||||
|
|
||||||
|
# Update the value from the filter
|
||||||
|
success = it.edit_predefined_filter_value(filter_num, old_value, new_value)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
flash(f'Wert "{old_value}" wurde in "{new_value}" bei Filter {filter_num} geändert', 'success')
|
||||||
|
else:
|
||||||
|
flash(f'Fehler beim Ändern des Wertes oder Wert existiert bereits in Filter {filter_num}', 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('manage_filters'))
|
||||||
|
|
||||||
@app.route('/get_predefined_filter_values/<int:filter_num>')
|
@app.route('/get_predefined_filter_values/<int:filter_num>')
|
||||||
def get_predefined_filter_values(filter_num):
|
def get_predefined_filter_values(filter_num):
|
||||||
"""
|
"""
|
||||||
@@ -9623,14 +9705,27 @@ def admin_school_settings():
|
|||||||
try:
|
try:
|
||||||
updated_school = cfg.update_school_info(school_info)
|
updated_school = cfg.update_school_info(school_info)
|
||||||
current_school = updated_school
|
current_school = updated_school
|
||||||
|
|
||||||
|
# Also process filter names
|
||||||
|
filter_name_1 = request.form.get('filter_name_1')
|
||||||
|
if filter_name_1: it.set_filter_name(1, filter_name_1.strip())
|
||||||
|
filter_name_2 = request.form.get('filter_name_2')
|
||||||
|
if filter_name_2: it.set_filter_name(2, filter_name_2.strip())
|
||||||
|
filter_name_3 = request.form.get('filter_name_3')
|
||||||
|
if filter_name_3: it.set_filter_name(3, filter_name_3.strip())
|
||||||
|
|
||||||
flash('Schulstammdaten wurden erfolgreich gespeichert.', 'success')
|
flash('Schulstammdaten wurden erfolgreich gespeichert.', 'success')
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
app.logger.error(f'Could not update school settings: {exc}\n{traceback.format_exc()}')
|
app.logger.error(f'Could not update school settings: {exc}\n{traceback.format_exc()}')
|
||||||
flash('Die Schulstammdaten konnten nicht gespeichert werden.', 'error')
|
flash('Die Schulstammdaten konnten nicht gespeichert werden.', 'error')
|
||||||
|
|
||||||
|
# Get current filter names to display in form
|
||||||
|
filter_names = it.get_filter_names()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin_school_settings.html',
|
'admin_school_settings.html',
|
||||||
school_info=current_school,
|
school_info=current_school,
|
||||||
|
filter_names=filter_names,
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
tenant_db=tenant_db,
|
tenant_db=tenant_db,
|
||||||
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import io
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import Font, PatternFill
|
||||||
|
|
||||||
|
def generate_library_excel(items):
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = "Export"
|
||||||
|
|
||||||
|
headers = [
|
||||||
|
"Code", "Titel", "Autor", "Typ", "ISBN/Code",
|
||||||
|
"Filter 1", "Filter 2", "Filter 3",
|
||||||
|
"Status", "Ausgeliehen von", "Rückgabe", "Kosten"
|
||||||
|
]
|
||||||
|
|
||||||
|
ws.append(headers)
|
||||||
|
|
||||||
|
header_font = Font(bold=True, color="FFFFFF")
|
||||||
|
header_fill = PatternFill(start_color="1F2937", end_color="1F2937", fill_type="solid")
|
||||||
|
for cell in ws[1]:
|
||||||
|
cell.font = header_font
|
||||||
|
cell.fill = header_fill
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
status = "Verfuegbar" if str(item.get("Verfuegbar", "True")).lower() == "true" else "Ausgeliehen"
|
||||||
|
row = [
|
||||||
|
item.get("Code_4", ""),
|
||||||
|
item.get("Name", ""),
|
||||||
|
item.get("Author", ""),
|
||||||
|
item.get("ItemType", ""),
|
||||||
|
item.get("ISBN", ""),
|
||||||
|
item.get("Filter", ""),
|
||||||
|
item.get("Filter2", ""),
|
||||||
|
item.get("Filter3", ""),
|
||||||
|
status,
|
||||||
|
item.get("User", ""),
|
||||||
|
item.get("ReturnDate", ""),
|
||||||
|
item.get("Anschaffungskosten", "")
|
||||||
|
]
|
||||||
|
ws.append(row)
|
||||||
|
|
||||||
|
for col in ws.columns:
|
||||||
|
max_length = 0
|
||||||
|
column = col[0].column_letter
|
||||||
|
for cell in col:
|
||||||
|
try:
|
||||||
|
if len(str(cell.value)) > max_length:
|
||||||
|
max_length = len(str(cell.value))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
ws.column_dimensions[column].width = min(max_length + 2, 50)
|
||||||
|
|
||||||
|
excel_buffer = io.BytesIO()
|
||||||
|
wb.save(excel_buffer)
|
||||||
|
excel_buffer.seek(0)
|
||||||
|
return excel_buffer
|
||||||
@@ -794,6 +794,80 @@ def remove_predefined_filter_value(filter_num, value):
|
|||||||
return result.modified_count > 0
|
return result.modified_count > 0
|
||||||
|
|
||||||
|
|
||||||
|
def edit_predefined_filter_value(filter_num, old_value, new_value):
|
||||||
|
"""
|
||||||
|
Edit a predefined value from a filter and update all matching items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe)
|
||||||
|
old_value (str): Value to replace
|
||||||
|
new_value (str): New value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if value was updated, False otherwise
|
||||||
|
"""
|
||||||
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
filter_presets = db['filter_presets']
|
||||||
|
|
||||||
|
# Check if the new value already exists
|
||||||
|
existing = filter_presets.find_one({
|
||||||
|
'filter_num': filter_num,
|
||||||
|
'values': new_value
|
||||||
|
})
|
||||||
|
|
||||||
|
if existing and old_value != new_value:
|
||||||
|
client.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Update the value in the filter
|
||||||
|
result = filter_presets.update_one(
|
||||||
|
{'filter_num': filter_num, 'values': old_value},
|
||||||
|
{'$set': {'values.$': new_value}}
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.modified_count > 0:
|
||||||
|
items = db['items']
|
||||||
|
filter_field = 'Filter' if filter_num == 1 else f'Filter{filter_num}'
|
||||||
|
|
||||||
|
# Also update all items that use this filter
|
||||||
|
items.update_many(
|
||||||
|
{filter_field: old_value},
|
||||||
|
{'$set': {filter_field: new_value}}
|
||||||
|
)
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
return result.modified_count > 0
|
||||||
|
|
||||||
|
def get_filter_names():
|
||||||
|
"""Get customized filter category names."""
|
||||||
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
names_doc = db.settings.find_one({'setting_type': 'filter_names'})
|
||||||
|
client.close()
|
||||||
|
if names_doc and 'names' in names_doc:
|
||||||
|
return names_doc['names']
|
||||||
|
return {
|
||||||
|
'1': 'Fach/Kategorie',
|
||||||
|
'2': 'System/Bereich',
|
||||||
|
'3': 'Typ/Art'
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_filter_name(filter_num, name):
|
||||||
|
"""Set custom name for a filter category."""
|
||||||
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
names = get_filter_names()
|
||||||
|
names[str(filter_num)] = name
|
||||||
|
db.settings.update_one(
|
||||||
|
{'setting_type': 'filter_names'},
|
||||||
|
{'$set': {'names': names}},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
client.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# === LOCATION MANAGEMENT ===
|
# === LOCATION MANAGEMENT ===
|
||||||
|
|
||||||
def get_predefined_locations():
|
def get_predefined_locations():
|
||||||
|
|||||||
@@ -981,3 +981,20 @@ html, body {
|
|||||||
color: var(--ui-text) !important;
|
color: var(--ui-text) !important;
|
||||||
border: 1px solid var(--ui-border);
|
border: 1px solid var(--ui-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fix Bookmark/Favorite Button in Dark Mode */
|
||||||
|
:root[data-theme="dark"] .bookmark-btn {
|
||||||
|
background: var(--ui-surface-soft) !important;
|
||||||
|
border-color: #f59e0b !important;
|
||||||
|
color: #f59e0b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] .bookmark-btn:hover,
|
||||||
|
:root[data-theme="dark"] .bookmark-btn.active {
|
||||||
|
background: #f59e0b !important;
|
||||||
|
color: var(--ui-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] .favorite-item {
|
||||||
|
outline: 2px solid #f59e0b !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,6 +71,25 @@
|
|||||||
<small>Upload ersetzt das bisherige Logo. Wenn kein neues Logo gewählt wird, bleibt das aktuelle erhalten.</small>
|
<small>Upload ersetzt das bisherige Logo. Wenn kein neues Logo gewählt wird, bleibt das aktuelle erhalten.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr style="margin: 24px 0; border: 0; border-top: 1px solid #e5e7eb;">
|
||||||
|
<h3 style="font-size: 1.1rem; margin-bottom: 16px; color: #111827;">Globales Filter-Wording</h3>
|
||||||
|
<p style="font-size: 0.9rem; color: #6b7280; margin-bottom: 16px;">Hier kannst du die globalen Bezeichnungen für die drei Haupt-Kategorien (Filter) der Anwendung ändern.</p>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="filter_name_1">Name Filter 1</label>
|
||||||
|
<input type="text" id="filter_name_1" name="filter_name_1" value="{{ filter_names.get('1', 'Fach/Kategorie') }}" placeholder="z. B. Fach/Kategorie">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="filter_name_2">Name Filter 2</label>
|
||||||
|
<input type="text" id="filter_name_2" name="filter_name_2" value="{{ filter_names.get('2', 'System/Bereich') }}" placeholder="z. B. System/Bereich">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="filter_name_3">Name Filter 3</label>
|
||||||
|
<input type="text" id="filter_name_3" name="filter_name_3" value="{{ filter_names.get('3', 'Typ/Art') }}" placeholder="z. B. Typ/Art">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="actions-row">
|
<div class="actions-row">
|
||||||
<button type="submit" class="btn btn-primary">Schulstammdaten speichern</button>
|
<button type="submit" class="btn btn-primary">Schulstammdaten speichern</button>
|
||||||
<a href="{{ url_for('home_admin') }}" class="btn btn-secondary">Zurück zur Admin-Übersicht</a>
|
<a href="{{ url_for('home_admin') }}" class="btn btn-secondary">Zurück zur Admin-Übersicht</a>
|
||||||
|
|||||||
+10
-11
@@ -329,7 +329,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* iPad/Tablet responsive adjustments */
|
/* iPad/Tablet responsive adjustments */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 768px) {
|
||||||
.navbar .container-fluid {
|
.navbar .container-fluid {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding-left: max(env(safe-area-inset-left, 0), 10px);
|
padding-left: max(env(safe-area-inset-left, 0), 10px);
|
||||||
@@ -1096,16 +1096,6 @@
|
|||||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}" data-tutorial-tip="Ihre aktuellen Ausleihen und Rückgaben finden Sie hier.">Meine Ausleihen</a>
|
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}" data-tutorial-tip="Ihre aktuellen Ausleihen und Rückgaben finden Sie hier.">Meine Ausleihen</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}" data-tutorial-tip="Öffnen Sie das Tutorial, um das System Schritt für Schritt kennenzulernen.">Tutorial</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'username' in session and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_insert', True) %}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}" data-tutorial-tip="Hier können Sie neue Artikel ins Inventar einpflegen.">➕ Hochladen</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<button id="themeToggleBtn" class="btn btn-link nav-link px-3" aria-label="Dark Mode umschalten" title="Theme umschalten">
|
<button id="themeToggleBtn" class="btn btn-link nav-link px-3" aria-label="Dark Mode umschalten" title="Theme umschalten">
|
||||||
@@ -1119,6 +1109,15 @@
|
|||||||
Mehr Optionen
|
Mehr Optionen
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
||||||
|
{% if 'username' in session %}
|
||||||
|
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_insert', True) %}
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('upload_admin') }}">➕ Hochladen</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||||
<li><h6 class="dropdown-header">Verwaltung</h6></li>
|
<li><h6 class="dropdown-header">Verwaltung</h6></li>
|
||||||
{% if current_permissions.pages.get('manage_filters', True) %}
|
{% if current_permissions.pages.get('manage_filters', True) %}
|
||||||
|
|||||||
@@ -477,6 +477,7 @@
|
|||||||
<select id="filterType">
|
<select id="filterType">
|
||||||
<option value="">-- Alle Typen --</option>
|
<option value="">-- Alle Typen --</option>
|
||||||
<option value="book">Buch</option>
|
<option value="book">Buch</option>
|
||||||
|
<option value="schulbuch">Schulbuch</option>
|
||||||
<option value="cd">CD</option>
|
<option value="cd">CD</option>
|
||||||
<option value="dvd">DVD</option>
|
<option value="dvd">DVD</option>
|
||||||
<option value="other">Sonstige</option>
|
<option value="other">Sonstige</option>
|
||||||
@@ -495,6 +496,15 @@
|
|||||||
<div class="filter-buttons">
|
<div class="filter-buttons">
|
||||||
<button id="applyFilterBtn" class="button" style="background: #4f46e5; color: white;">Filter anwenden</button>
|
<button id="applyFilterBtn" class="button" style="background: #4f46e5; color: white;">Filter anwenden</button>
|
||||||
<button id="clearFilterBtn" class="button" style="background: #f0f2f5; color: var(--ui-text);">Zurücksetzen</button>
|
<button id="clearFilterBtn" class="button" style="background: #f0f2f5; color: var(--ui-text);">Zurücksetzen</button>
|
||||||
|
<div style="flex-grow: 1;"></div>
|
||||||
|
<button class="button" style="background: #10b981; color: white; display: flex; align-items: center; gap: 0.5rem;" onclick="window.location.href='/library/export/schulbuecher'">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="8" y1="13" x2="16" y2="13"></line><line x1="8" y1="17" x2="16" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
||||||
|
Schulbücher Excel
|
||||||
|
</button>
|
||||||
|
<button class="button" style="background: #10b981; color: white; display: flex; align-items: center; gap: 0.5rem;" onclick="window.location.href='/library/export/all_borrowed'">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="8" y1="13" x2="16" y2="13"></line><line x1="8" y1="17" x2="16" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
||||||
|
Alle ausgeliehen Excel
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1174,6 +1184,7 @@
|
|||||||
<label for="editLibraryType">Medientyp</label>
|
<label for="editLibraryType">Medientyp</label>
|
||||||
<select id="editLibraryType">
|
<select id="editLibraryType">
|
||||||
<option value="book">Buch</option>
|
<option value="book">Buch</option>
|
||||||
|
<option value="schulbuch">Schulbuch</option>
|
||||||
<option value="cd">CD</option>
|
<option value="cd">CD</option>
|
||||||
<option value="dvd">DVD</option>
|
<option value="dvd">DVD</option>
|
||||||
<option value="media">Sonstige Medien</option>
|
<option value="media">Sonstige Medien</option>
|
||||||
|
|||||||
+46
-40
@@ -760,11 +760,14 @@
|
|||||||
if (!mainItemsLoadingIndicator) {
|
if (!mainItemsLoadingIndicator) {
|
||||||
mainItemsLoadingIndicator = document.createElement('div');
|
mainItemsLoadingIndicator = document.createElement('div');
|
||||||
mainItemsLoadingIndicator.id = 'main-items-loading-indicator';
|
mainItemsLoadingIndicator.id = 'main-items-loading-indicator';
|
||||||
mainItemsLoadingIndicator.className = 'items-loading-indicator';
|
mainItemsLoadingIndicator.className = 'items-loading-indicator empty-frames-placeholder';
|
||||||
mainItemsLoadingIndicator.innerHTML = `
|
mainItemsLoadingIndicator.innerHTML = `
|
||||||
<div class="loading-track"><div class="loading-fill"></div></div>
|
<div class="empty-frame p-3 mx-2" style="flex: 1; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.02); min-width: 250px;"></div>
|
||||||
<div class="loading-label">Weitere Objekte werden vorbereitet...</div>
|
<div class="empty-frame p-3 mx-2" style="flex: 1; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.02); min-width: 250px; display: none;" class="d-none d-md-block"></div>
|
||||||
|
<div class="empty-frame p-3 mx-2" style="flex: 1; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.02); min-width: 250px; display: none;" class="d-none d-lg-block"></div>
|
||||||
`;
|
`;
|
||||||
|
mainItemsLoadingIndicator.style.display = 'flex';
|
||||||
|
mainItemsLoadingIndicator.style.width = '100%';
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsContainer.appendChild(mainItemsLoadingIndicator);
|
itemsContainer.appendChild(mainItemsLoadingIndicator);
|
||||||
@@ -1200,6 +1203,7 @@
|
|||||||
card.innerHTML = card.dataset.savedHtml;
|
card.innerHTML = card.dataset.savedHtml;
|
||||||
card.dataset.deloaded = 'false';
|
card.dataset.deloaded = 'false';
|
||||||
card.classList.remove('is-deloaded');
|
card.classList.remove('is-deloaded');
|
||||||
|
card.classList.remove('empty-frame');
|
||||||
const favBtn = card.querySelector('.bookmark-btn');
|
const favBtn = card.querySelector('.bookmark-btn');
|
||||||
if (favBtn) {
|
if (favBtn) {
|
||||||
favBtn.addEventListener('click', (ev) => {
|
favBtn.addEventListener('click', (ev) => {
|
||||||
@@ -1227,12 +1231,15 @@
|
|||||||
card.innerHTML = '';
|
card.innerHTML = '';
|
||||||
card.dataset.deloaded = 'true';
|
card.dataset.deloaded = 'true';
|
||||||
card.classList.add('is-deloaded');
|
card.classList.add('is-deloaded');
|
||||||
|
card.classList.add('empty-frame');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, {
|
}, {
|
||||||
rootMargin: isMobileLayout ? '1500px 0px 1500px 0px' : '0px 2500px 0px 2500px'
|
// Keep approx ~120 items rendered in DOM relative to scroll position.
|
||||||
|
// Assuming ~100-400px per item/row, this margin unloads elements beyond that range.
|
||||||
|
rootMargin: isMobileLayout ? '18000px 0px 18000px 0px' : '9000px 0px 9000px 0px'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3156,46 +3163,45 @@
|
|||||||
border: 1px solid var(--ui-border);
|
border: 1px solid var(--ui-border);
|
||||||
border-radius: var(--ui-radius);
|
border-radius: var(--ui-radius);
|
||||||
box-shadow: var(--ui-shadow-sm);
|
box-shadow: var(--ui-shadow-sm);
|
||||||
padding: 20px;
|
padding: 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 340px;
|
max-width: 340px;
|
||||||
flex: 1 1 300px;
|
flex: 1 1 300px;
|
||||||
scroll-snap-align: center;
|
scroll-snap-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
position: relative;
|
position: relative;
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: 300px 450px;
|
contain-intrinsic-size: 300px 380px;
|
||||||
}
|
}
|
||||||
.item-card:hover {
|
.item-card:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card h3 {
|
.item-card h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
font-size: 1.15rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.item-card .description {
|
.item-card .description {
|
||||||
max-height: 100px;
|
max-height: 60px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.item-card .image-container {
|
.item-card .image-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
margin: 10px 0;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card .item-image {
|
.item-card .item-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 300px;
|
max-height: 180px;
|
||||||
display: none;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-card .item-image:first-child {
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -395,7 +395,7 @@
|
|||||||
border: 1px solid var(--ui-border);
|
border: 1px solid var(--ui-border);
|
||||||
border-radius: var(--ui-radius);
|
border-radius: var(--ui-radius);
|
||||||
box-shadow: var(--ui-shadow-sm);
|
box-shadow: var(--ui-shadow-sm);
|
||||||
padding: 20px;
|
padding: 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 340px;
|
max-width: 340px;
|
||||||
flex: 1 1 300px;
|
flex: 1 1 300px;
|
||||||
@@ -404,7 +404,7 @@
|
|||||||
transition: transform 0.2s, box-shadow 0.2s;
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
position: relative;
|
position: relative;
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: 300px 450px;
|
contain-intrinsic-size: 300px 380px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card.bulk-selected {
|
.item-card.bulk-selected {
|
||||||
@@ -420,17 +420,21 @@
|
|||||||
|
|
||||||
.item-card h3 {
|
.item-card h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card .description {
|
.item-card .description {
|
||||||
max-height: 100px;
|
max-height: 60px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card .image-container {
|
.item-card .image-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
margin: 10px 0;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bulk-delete-row {
|
.bulk-delete-row {
|
||||||
@@ -479,7 +483,7 @@
|
|||||||
.item-card .item-image {
|
.item-card .item-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 300px;
|
max-height: 180px;
|
||||||
display: none;
|
display: none;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
@@ -3383,11 +3387,14 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
if (!mainAdminItemsLoadingIndicator) {
|
if (!mainAdminItemsLoadingIndicator) {
|
||||||
mainAdminItemsLoadingIndicator = document.createElement('div');
|
mainAdminItemsLoadingIndicator = document.createElement('div');
|
||||||
mainAdminItemsLoadingIndicator.id = 'main-admin-items-loading-indicator';
|
mainAdminItemsLoadingIndicator.id = 'main-admin-items-loading-indicator';
|
||||||
mainAdminItemsLoadingIndicator.className = 'items-loading-indicator';
|
mainAdminItemsLoadingIndicator.className = 'items-loading-indicator empty-frames-placeholder';
|
||||||
mainAdminItemsLoadingIndicator.innerHTML = `
|
mainAdminItemsLoadingIndicator.innerHTML = `
|
||||||
<div class="loading-track"><div class="loading-fill"></div></div>
|
<div class="empty-frame p-3 mx-2" style="flex: 1; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.02); min-width: 250px;"></div>
|
||||||
<div class="loading-label">Weitere Objekte werden vorbereitet...</div>
|
<div class="empty-frame p-3 mx-2" style="flex: 1; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.02); min-width: 250px; display: none;" class="d-none d-md-block"></div>
|
||||||
|
<div class="empty-frame p-3 mx-2" style="flex: 1; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.02); min-width: 250px; display: none;" class="d-none d-lg-block"></div>
|
||||||
`;
|
`;
|
||||||
|
mainAdminItemsLoadingIndicator.style.display = 'flex';
|
||||||
|
mainAdminItemsLoadingIndicator.style.width = '100%';
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsContainer.appendChild(mainAdminItemsLoadingIndicator);
|
itemsContainer.appendChild(mainAdminItemsLoadingIndicator);
|
||||||
@@ -3812,6 +3819,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
card.innerHTML = card.dataset.savedHtml;
|
card.innerHTML = card.dataset.savedHtml;
|
||||||
card.dataset.deloaded = 'false';
|
card.dataset.deloaded = 'false';
|
||||||
card.classList.remove('is-deloaded');
|
card.classList.remove('is-deloaded');
|
||||||
|
card.classList.remove('empty-frame');
|
||||||
const favBtn = card.querySelector('.bookmark-btn');
|
const favBtn = card.querySelector('.bookmark-btn');
|
||||||
if (favBtn) {
|
if (favBtn) {
|
||||||
favBtn.addEventListener('click', (ev) => {
|
favBtn.addEventListener('click', (ev) => {
|
||||||
@@ -3840,12 +3848,15 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
card.innerHTML = '';
|
card.innerHTML = '';
|
||||||
card.dataset.deloaded = 'true';
|
card.dataset.deloaded = 'true';
|
||||||
card.classList.add('is-deloaded');
|
card.classList.add('is-deloaded');
|
||||||
|
card.classList.add('empty-frame');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, {
|
}, {
|
||||||
rootMargin: isMobileLayout ? '1500px 0px 1500px 0px' : '0px 2500px 0px 2500px'
|
// Keep approx ~120 items rendered in DOM relative to scroll position.
|
||||||
|
// Assuming ~100-400px per item/row, this margin unloads elements beyond that range.
|
||||||
|
rootMargin: isMobileLayout ? '18000px 0px 18000px 0px' : '9000px 0px 9000px 0px'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title h5 mb-0">Fach/Kategorie (Filter 1)</h2>
|
<h2 class="card-title h5 mb-0">{{ filter_names.get('1', 'Fach/Kategorie') }} (Filter 1)</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" action="{{ url_for('add_filter_value', filter_num=1) }}" class="mb-4">
|
<form method="POST" action="{{ url_for('add_filter_value', filter_num=1) }}" class="mb-4">
|
||||||
@@ -33,13 +33,25 @@
|
|||||||
{% if filter1_values %}
|
{% if filter1_values %}
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for value in filter1_values %}
|
{% for value in filter1_values %}
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
<div class="list-group-item">
|
||||||
{{ value }}
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<form method="POST" action="{{ url_for('remove_filter_value', filter_num=1, value=value) }}" class="d-inline">
|
<span>{{ value }}</span>
|
||||||
<button type="submit" class="btn btn-sm btn-danger"
|
<div>
|
||||||
onclick="return confirm('Sind Sie sicher, dass Sie den Wert \"' + '{{ value }}'.replace(/'/g, '\\\'') + '\" löschen möchten?');">
|
<button type="button" class="btn btn-sm btn-secondary me-1" onclick="toggleEdit('edit-1-{{ loop.index }}')">Bearbeiten</button>
|
||||||
Entfernen
|
<form method="POST" action="{{ url_for('remove_filter_value', filter_num=1, value=value) }}" class="d-inline">
|
||||||
</button>
|
<button type="submit" class="btn btn-sm btn-danger"
|
||||||
|
onclick="return confirm('Sind Sie sicher, dass Sie den Wert \"' + '{{ value }}'.replace(/'/g, '\\\'') + '\" löschen möchten?');">
|
||||||
|
Entfernen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="edit-1-{{ loop.index }}" method="POST" action="{{ url_for('edit_filter_value', filter_num=1, old_value=value) }}" style="display: none;" class="mt-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" name="new_value" class="form-control" value="{{ value }}" required>
|
||||||
|
<button type="submit" class="btn btn-primary" onclick="return confirm('Tipp: Das Ändern des Namens aktualisiert auch alle Einträge in der Datenbank, die diesen Filter verwenden. Fortfahren?');">Speichern</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="toggleEdit('edit-1-{{ loop.index }}')">Abbrechen</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -55,7 +67,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title h5 mb-0">System/Bereich (Filter 2)</h2>
|
<h2 class="card-title h5 mb-0">{{ filter_names.get('2', 'System/Bereich') }} (Filter 2)</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" action="{{ url_for('add_filter_value', filter_num=2) }}" class="mb-4">
|
<form method="POST" action="{{ url_for('add_filter_value', filter_num=2) }}" class="mb-4">
|
||||||
@@ -69,13 +81,25 @@
|
|||||||
{% if filter2_values %}
|
{% if filter2_values %}
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for value in filter2_values %}
|
{% for value in filter2_values %}
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
<div class="list-group-item">
|
||||||
{{ value }}
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<form method="POST" action="{{ url_for('remove_filter_value', filter_num=2, value=value) }}" class="d-inline">
|
<span>{{ value }}</span>
|
||||||
<button type="submit" class="btn btn-sm btn-danger"
|
<div>
|
||||||
onclick="return confirm('Sind Sie sicher, dass Sie den Wert \"' + '{{ value }}'.replace(/'/g, '\\\'') + '\" löschen möchten?');">
|
<button type="button" class="btn btn-sm btn-secondary me-1" onclick="toggleEdit('edit-2-{{ loop.index }}')">Bearbeiten</button>
|
||||||
Entfernen
|
<form method="POST" action="{{ url_for('remove_filter_value', filter_num=2, value=value) }}" class="d-inline">
|
||||||
</button>
|
<button type="submit" class="btn btn-sm btn-danger"
|
||||||
|
onclick="return confirm('Sind Sie sicher, dass Sie den Wert \"' + '{{ value }}'.replace(/'/g, '\\\'') + '\" löschen möchten?');">
|
||||||
|
Entfernen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="edit-2-{{ loop.index }}" method="POST" action="{{ url_for('edit_filter_value', filter_num=2, old_value=value) }}" style="display: none;" class="mt-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" name="new_value" class="form-control" value="{{ value }}" required>
|
||||||
|
<button type="submit" class="btn btn-primary" onclick="return confirm('Tipp: Das Ändern des Namens aktualisiert auch alle Einträge in der Datenbank, die diesen Filter verwenden. Fortfahren?');">Speichern</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="toggleEdit('edit-2-{{ loop.index }}')">Abbrechen</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -91,7 +115,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title h5 mb-0">Typ/Art (Filter 3)</h2>
|
<h2 class="card-title h5 mb-0">{{ filter_names.get('3', 'Typ/Art') }} (Filter 3)</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" action="{{ url_for('add_filter_value', filter_num=3) }}" class="mb-4">
|
<form method="POST" action="{{ url_for('add_filter_value', filter_num=3) }}" class="mb-4">
|
||||||
@@ -105,13 +129,25 @@
|
|||||||
{% if filter3_values %}
|
{% if filter3_values %}
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for value in filter3_values %}
|
{% for value in filter3_values %}
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
<div class="list-group-item">
|
||||||
{{ value }}
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<form method="POST" action="{{ url_for('remove_filter_value', filter_num=3, value=value) }}" class="d-inline">
|
<span>{{ value }}</span>
|
||||||
<button type="submit" class="btn btn-sm btn-danger"
|
<div>
|
||||||
onclick="return confirm('Sind Sie sicher, dass Sie den Wert \"' + '{{ value }}'.replace(/'/g, '\\\'') + '\" löschen möchten?');">
|
<button type="button" class="btn btn-sm btn-secondary me-1" onclick="toggleEdit('edit-3-{{ loop.index }}')">Bearbeiten</button>
|
||||||
Entfernen
|
<form method="POST" action="{{ url_for('remove_filter_value', filter_num=3, value=value) }}" class="d-inline">
|
||||||
</button>
|
<button type="submit" class="btn btn-sm btn-danger"
|
||||||
|
onclick="return confirm('Sind Sie sicher, dass Sie den Wert \"' + '{{ value }}'.replace(/'/g, '\\\'') + '\" löschen möchten?');">
|
||||||
|
Entfernen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="edit-3-{{ loop.index }}" method="POST" action="{{ url_for('edit_filter_value', filter_num=3, old_value=value) }}" style="display: none;" class="mt-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" name="new_value" class="form-control" value="{{ value }}" required>
|
||||||
|
<button type="submit" class="btn btn-primary" onclick="return confirm('Tipp: Das Ändern des Namens aktualisiert auch alle Einträge in der Datenbank, die diesen Filter verwenden. Fortfahren?');">Speichern</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="toggleEdit('edit-3-{{ loop.index }}')">Abbrechen</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -133,4 +169,15 @@
|
|||||||
<a href="{{ url_for('home_admin') }}" class="btn btn-secondary">Zurück zur Admin-Übersicht</a>
|
<a href="{{ url_for('home_admin') }}" class="btn btn-secondary">Zurück zur Admin-Übersicht</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleEdit(id) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el.style.display === 'none') {
|
||||||
|
el.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from pymongo import MongoClient
|
||||||
|
import Web.settings as cfg
|
||||||
|
|
||||||
|
def get_filter_names():
|
||||||
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
names = db.settings.find_one({'setting_type': 'filter_names'})
|
||||||
|
client.close()
|
||||||
|
if names:
|
||||||
|
return names.get('names', {
|
||||||
|
'1': 'Fach/Kategorie',
|
||||||
|
'2': 'System/Bereich',
|
||||||
|
'3': 'Typ/Art'
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'1': 'Fach/Kategorie',
|
||||||
|
'2': 'System/Bereich',
|
||||||
|
'3': 'Typ/Art'
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_filter_name(filter_num, name):
|
||||||
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
names = get_filter_names()
|
||||||
|
names[str(filter_num)] = name
|
||||||
|
db.settings.update_one(
|
||||||
|
{'setting_type': 'filter_names'},
|
||||||
|
{'$set': {'names': names}},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
client.close()
|
||||||
|
return True
|
||||||
Reference in New Issue
Block a user