Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a32a078bc | |||
| 5c4ab6f0fc | |||
| 79cf958c3c | |||
| 826db109c3 | |||
| f9b991d991 | |||
| 363f0df530 | |||
| 34bb3cf4c3 | |||
| 18706c6d1c | |||
| 9771b98075 | |||
| e380ad6092 | |||
| d6e8e08b10 | |||
| ddb6ab82ad | |||
| baefc55257 | |||
| 6c359089cd | |||
| 479cf3d98c | |||
| f00e7d8f74 | |||
| 7af729ac88 | |||
| 847349b419 | |||
| 91a80c6330 |
+88
-50
@@ -158,6 +158,41 @@ def print(*args, **kwargs):
|
||||
STUDENT_CARD_ENCRYPTED_FIELDS = ('ausweis_ident', 'SchülerName', 'Klasse', 'Notizen')
|
||||
|
||||
|
||||
def _display_username(user_value):
|
||||
"""Return a username for a stored user ID, with legacy value support."""
|
||||
if user_value is None or str(user_value).strip() == '':
|
||||
return ''
|
||||
|
||||
raw_value = str(user_value).strip()
|
||||
user_doc = us.get_user_by_id(raw_value)
|
||||
if not user_doc:
|
||||
user_doc = us.get_user_by_student_card(raw_value)
|
||||
if not user_doc:
|
||||
user_doc = us.get_user_by_student_card_record_id(raw_value)
|
||||
if user_doc:
|
||||
return str(user_doc.get('Username') or user_doc.get('username') or raw_value)
|
||||
|
||||
return decrypt_text(raw_value)
|
||||
|
||||
|
||||
def _resolve_library_card_id(user_value):
|
||||
"""Return the card ID for a stored user reference."""
|
||||
if user_value is None or str(user_value).strip() == '':
|
||||
return ''
|
||||
|
||||
raw_value = str(user_value).strip()
|
||||
user_doc = us.get_user_by_id(raw_value)
|
||||
if user_doc:
|
||||
return str(user_doc.get('StudentCardId') or raw_value).strip().upper()
|
||||
|
||||
decrypted_value = decrypt_text(raw_value)
|
||||
user_doc = us.get_user_by_student_card(decrypted_value)
|
||||
if user_doc:
|
||||
return str(user_doc.get('StudentCardId') or decrypted_value).strip().upper()
|
||||
|
||||
return str(decrypted_value or raw_value).strip().upper()
|
||||
|
||||
|
||||
def _decrypt_student_card_doc(card_doc):
|
||||
if not card_doc:
|
||||
return card_doc
|
||||
@@ -1212,7 +1247,7 @@ def create_return_reminders():
|
||||
|
||||
# Falls der Benutzername verschlüsselt ist, hier entschlüsseln,
|
||||
# damit der Admin den echten Namen/Ausweis lesen kann.
|
||||
display_user = decrypt_text(username) if 'decrypt_text' in globals() else username
|
||||
display_user = _display_username(username)
|
||||
|
||||
item_id = str(borrow_doc.get('Item', '')).strip()
|
||||
item_doc = item_docs.get(item_id, {})
|
||||
@@ -1383,7 +1418,7 @@ def update_appointment_statuses():
|
||||
old_status = appointment.get('Status')
|
||||
activation_user = str(appointment.get('User') or '').strip()
|
||||
try:
|
||||
activation_user = decrypt_text(activation_user) if activation_user else ''
|
||||
activation_user = _display_username(activation_user)
|
||||
except Exception:
|
||||
pass
|
||||
activation_item_name = str(appointment.get('Item') or 'Termin')
|
||||
@@ -1450,7 +1485,7 @@ def update_appointment_statuses():
|
||||
holder = ''
|
||||
if item_doc and item_doc.get('User'):
|
||||
try:
|
||||
holder = decrypt_text(str(item_doc['User']))
|
||||
holder = _display_username(item_doc['User'])
|
||||
except Exception:
|
||||
holder = str(item_doc['User'])
|
||||
if not holder:
|
||||
@@ -1464,12 +1499,12 @@ def update_appointment_statuses():
|
||||
)
|
||||
if active_holder and active_holder.get('User'):
|
||||
try:
|
||||
holder = decrypt_text(str(active_holder['User']))
|
||||
holder = _display_username(active_holder['User'])
|
||||
except Exception:
|
||||
holder = str(active_holder['User'])
|
||||
requested_user = str(appointment.get('User') or '').strip()
|
||||
try:
|
||||
requested_user = decrypt_text(requested_user) if requested_user else ''
|
||||
requested_user = _display_username(requested_user)
|
||||
except Exception:
|
||||
pass
|
||||
item_name = (item_doc or {}).get('Name') or str(item_id)
|
||||
@@ -1647,8 +1682,7 @@ def update_appointment_statuses():
|
||||
raw_user = str(appt.get('User', '')).strip()
|
||||
|
||||
# Entschlüsselung der AusweisId
|
||||
decrypted_user = decrypt_text(raw_user) if 'decrypt_text' in globals() else raw_user
|
||||
target_ausweis_id = decrypted_user if decrypted_user else raw_user
|
||||
target_ausweis_id = _resolve_library_card_id(raw_user)
|
||||
item_id = appt.get('Item')
|
||||
|
||||
if not target_ausweis_id:
|
||||
@@ -3541,7 +3575,7 @@ def library_loans_admin():
|
||||
fmt_reports, fmt_repairs = format_damage_history(raw_reports, raw_repairs)
|
||||
|
||||
raw_user = record.get('User', '')
|
||||
decrypted_user = safe_decrypt(raw_user)
|
||||
decrypted_user = _display_username(raw_user)
|
||||
|
||||
user_class = (
|
||||
safe_decrypt(record.get('Klasse'))
|
||||
@@ -3702,8 +3736,7 @@ def mahnungen_admin():
|
||||
item_code = item_doc.get('Code_4', '')
|
||||
|
||||
raw_user = str(record.get('User') or '')
|
||||
decrypted_user = decrypt_text(raw_user)
|
||||
ausweis_id = (decrypted_user if decrypted_user else raw_user).strip().upper()
|
||||
ausweis_id = _resolve_library_card_id(raw_user)
|
||||
|
||||
student_card = card_map.get(ausweis_id, {})
|
||||
student_name = student_card.get('SchülerName', ausweis_id if ausweis_id else 'Unbekannt')
|
||||
@@ -3790,8 +3823,7 @@ def mahnungen_reset(days):
|
||||
|
||||
# 3. Schülerausweis ermitteln und entsperren
|
||||
raw_user = str(record.get('User', ''))
|
||||
decrypted_user = decrypt_text(raw_user)
|
||||
ausweis_id = decrypted_user if decrypted_user else raw_user
|
||||
ausweis_id = _resolve_library_card_id(raw_user)
|
||||
|
||||
card = student_cards_col.find_one({'AusweisId': ausweis_id})
|
||||
if not card and raw_user != ausweis_id:
|
||||
@@ -3852,8 +3884,7 @@ def mahnungen_send_email():
|
||||
|
||||
# Gegenstand und Schülerausweis ermitteln
|
||||
raw_user = str(record.get('User', ''))
|
||||
decrypted_user = decrypt_text(raw_user)
|
||||
ausweis_id = decrypted_user if decrypted_user else raw_user
|
||||
ausweis_id = _resolve_library_card_id(raw_user)
|
||||
|
||||
card = student_cards_col.find_one({'AusweisId': ausweis_id})
|
||||
student_card = _decrypt_student_card_doc(card) if card and '_decrypt_student_card_doc' in globals() else (
|
||||
@@ -4205,8 +4236,7 @@ def api_library_return_by_code():
|
||||
continue
|
||||
|
||||
# AusweisId entschlüsseln
|
||||
decrypted_user = decrypt_text(raw_user) if 'safe_decrypt' in globals() else raw_user
|
||||
ausweis_id = decrypted_user if decrypted_user else raw_user
|
||||
ausweis_id = _resolve_library_card_id(raw_user)
|
||||
|
||||
# Prüfen, ob der Schüler noch andere eskalierte Ausleihungen hat
|
||||
# Da die aktuelle Ausleihe oben auf 'completed' gesetzt wurde, wird sie hier nicht mitgezählt!
|
||||
@@ -4349,10 +4379,14 @@ def api_library_scan_action():
|
||||
|
||||
due_date = now + datetime.timedelta(days=borrow_duration_days)
|
||||
|
||||
it.update_item_status(item_id, False, borrower_name)
|
||||
app.logger.info(f"Library scan action: borrowing item {item_id} for student card {student_card_id} until {due_date.isoformat()}")
|
||||
borrower_user = us.get_user_by_student_card(student_card_id)
|
||||
app.logger.info(f"Found borrower user: {borrower_user}")
|
||||
if not borrower_user or not borrower_user.get('_id'):
|
||||
app.logger.warning(f"No user found for student card {student_card_id}. Cannot record borrowing in ausleihungen.")
|
||||
return jsonify({'ok': False, 'message': 'Kein Benutzer für diesen Schülerausweis gefunden.'}), 404
|
||||
|
||||
it.update_item_status(item_id, False, borrower_name)
|
||||
au.add_ausleihung(
|
||||
item_id,
|
||||
str(borrower_user['_id']),
|
||||
@@ -4367,7 +4401,7 @@ def api_library_scan_action():
|
||||
'channel': 'library_scan',
|
||||
'item_id': item_id,
|
||||
'item_name': item_doc.get('Name', ''),
|
||||
'borrower':borrower_name,
|
||||
'borrower': borrower_name,
|
||||
'student_card_id': student_card_id,
|
||||
'borrow_duration_days': borrow_duration_days,
|
||||
}
|
||||
@@ -4457,6 +4491,7 @@ def api_item_detail(item_id):
|
||||
db = client[MONGODB_DB]
|
||||
ausleihungen_col = db['ausleihungen']
|
||||
items_col = db['items']
|
||||
student_cards_col = db['student_cards']
|
||||
|
||||
# Determine parent/group id (if this is a child, use its ParentItemId)
|
||||
parent_id = str(item.get('ParentItemId') or str(item.get('_id')))
|
||||
@@ -4493,23 +4528,32 @@ def api_item_detail(item_id):
|
||||
else:
|
||||
status_label = 'Verfügbar'
|
||||
|
||||
# Helper function to get borrower display name
|
||||
def get_borrower_name(user_raw):
|
||||
if not user_raw:
|
||||
return ''
|
||||
try:
|
||||
user_doc = student_cards_col.find_one({'_id': ObjectId(user_raw)})
|
||||
if user_doc:
|
||||
if user_doc.get('SchülerName'):
|
||||
return decrypt_text(str(user_doc.get('SchülerName', '')))
|
||||
return str(user_doc.get('AusweisId', ''))
|
||||
except Exception:
|
||||
pass
|
||||
return str(user_raw)
|
||||
|
||||
# Prefer the most recent active borrower across the group
|
||||
borrower_value = ''
|
||||
for rec in borrow_records:
|
||||
if rec.get('Status') == 'active' and rec.get('User'):
|
||||
try:
|
||||
user_raw = rec.get('User')
|
||||
if user_raw is not None:
|
||||
borrower_value = decrypt_text(str(user_raw))
|
||||
except Exception:
|
||||
borrower_value = str(rec.get('User', ''))
|
||||
borrower_value = get_borrower_name(rec.get('User'))
|
||||
break
|
||||
|
||||
if not borrower_value and item.get('User'):
|
||||
try:
|
||||
item_user = item.get('User')
|
||||
if item_user is not None:
|
||||
borrower_value = decrypt_text(str(item_user))
|
||||
borrower_value = _display_username(item_user)
|
||||
except Exception:
|
||||
borrower_value = str(item.get('User', ''))
|
||||
|
||||
@@ -4525,17 +4569,13 @@ def api_item_detail(item_id):
|
||||
rows = []
|
||||
|
||||
for rec in borrow_records[:3]:
|
||||
user_raw = rec.get('User')
|
||||
try:
|
||||
user = decrypt_text(user_raw) if user_raw is not None else ''
|
||||
except Exception:
|
||||
user = user_raw
|
||||
rec_borrower_name = get_borrower_name(rec.get('User')) or '-'
|
||||
status = rec.get('Status', '')
|
||||
start = fmt_dt(rec.get('Start'))
|
||||
end = fmt_dt(rec.get('End'))
|
||||
notes = html.escape(str(rec.get('Notes') or ''))
|
||||
rows.append(
|
||||
f"<li><strong>{html.escape(str(user or '-'))}</strong> — "
|
||||
f"<li><strong>{html.escape(rec_borrower_name)}</strong> — "
|
||||
f"{html.escape(str(status))} — "
|
||||
f"{html.escape(str(start))} → {html.escape(str(end))}"
|
||||
f"{(' — ' + notes) if notes else ''}</li>"
|
||||
@@ -8009,10 +8049,7 @@ def get_planned_bookings(item_id):
|
||||
bookings = []
|
||||
for r in cursor:
|
||||
raw_user = r.get('User') or ''
|
||||
try:
|
||||
booking_user = decrypt_text(raw_user) if raw_user else ''
|
||||
except Exception:
|
||||
booking_user = str(raw_user)
|
||||
booking_user = _display_username(raw_user)
|
||||
bookings.append({
|
||||
'id': str(r.get('_id')),
|
||||
'user': booking_user,
|
||||
@@ -8043,10 +8080,7 @@ def get_planned_bookings_public(item_id):
|
||||
bookings = []
|
||||
for r in cursor:
|
||||
raw_user = r.get('User') or ''
|
||||
try:
|
||||
booking_user = decrypt_text(raw_user) if raw_user else ''
|
||||
except Exception:
|
||||
booking_user = str(raw_user)
|
||||
booking_user = _display_username(raw_user)
|
||||
bookings.append({
|
||||
'user': booking_user,
|
||||
'period': r.get('Period'),
|
||||
@@ -8890,7 +8924,7 @@ def user_del():
|
||||
fullname = None
|
||||
|
||||
users_list.append({
|
||||
'username': decrypt_text(username),
|
||||
'username': _display_username(username),
|
||||
'admin': user.get('Admin', False),
|
||||
'fullname': fullname,
|
||||
'name': name,
|
||||
@@ -9031,7 +9065,8 @@ def admin_borrowings():
|
||||
has_damage = False
|
||||
|
||||
raw_user = r.get('User', '')
|
||||
decrypted_user = decrypt_text(raw_user) if raw_user else ''
|
||||
decrypted_user = _display_username(raw_user)
|
||||
|
||||
|
||||
entries.append({
|
||||
'id': str(r.get('_id')),
|
||||
@@ -10265,8 +10300,8 @@ def library_item_invoices(item_id):
|
||||
entries.append({
|
||||
'borrow_id': str(borrow_doc.get('_id')),
|
||||
'borrow_status': borrow_doc.get('Status', ''),
|
||||
'borrow_user': decrypt_text(borrow_doc.get('User', '')) if hasattr(borrow_doc,
|
||||
'get') and borrow_doc.get(
|
||||
'borrow_user': _display_username(borrow_doc.get('User', '')) if hasattr(borrow_doc,
|
||||
'get') and borrow_doc.get(
|
||||
'User') else '',
|
||||
'borrow_start': borrow_doc.get('Start').strftime('%d.%m.%Y %H:%M') if isinstance(
|
||||
borrow_doc.get('Start'), datetime.datetime) else '',
|
||||
@@ -10366,7 +10401,7 @@ def library_item_invoices(item_id):
|
||||
))
|
||||
|
||||
for b in damage_borrow_docs:
|
||||
borrow_user = decrypt_text(b.get('User', '')) if b.get('User') else '—'
|
||||
borrow_user = _display_username(b.get('User', '')) if b.get('User') else '—'
|
||||
|
||||
d_data = b.get('DamageData') or {}
|
||||
if not isinstance(d_data, dict): d_data = {}
|
||||
@@ -10656,7 +10691,7 @@ def logs():
|
||||
|
||||
formatted_items.append({
|
||||
'Item': item_name,
|
||||
'User': decrypt_text(username),
|
||||
'User': _display_username(username),
|
||||
'Start': start_date,
|
||||
'End': end_date,
|
||||
'Duration': duration,
|
||||
@@ -10698,7 +10733,7 @@ def logs():
|
||||
note = log_item.get('note', '')
|
||||
formatted_items.append({
|
||||
'Item': item_name,
|
||||
'User': decrypt_text(log_item.get('user', 'Unknown User')),
|
||||
'User': _display_username(log_item.get('user', 'Unknown User')),
|
||||
'Start': ts_display,
|
||||
'End': '-',
|
||||
'Duration': '-',
|
||||
@@ -10712,7 +10747,7 @@ def logs():
|
||||
resolved_count = log_item.get('resolved_count', 0)
|
||||
formatted_items.append({
|
||||
'Item': item_name,
|
||||
'User': decrypt_text(log_item.get('user', 'Unknown User')),
|
||||
'User': _display_username(log_item.get('user', 'Unknown User')),
|
||||
'Start': ts_display,
|
||||
'End': '-',
|
||||
'Duration': '-',
|
||||
@@ -11515,6 +11550,7 @@ def my_borrowed_items():
|
||||
username,
|
||||
str(current_user.get('_id')) if current_user.get('_id') else '',
|
||||
str(session.get('user_id') or ''),
|
||||
str(current_user.get('StudentCardId') or '').strip().upper(),
|
||||
}
|
||||
user_ids.discard('')
|
||||
|
||||
@@ -11537,6 +11573,7 @@ def my_borrowed_items():
|
||||
try:
|
||||
if raw_user:
|
||||
user_values.add(str(decrypt_text(raw_user)).strip())
|
||||
user_values.add(_resolve_library_card_id(raw_user))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -11594,6 +11631,7 @@ def my_borrowed_items():
|
||||
try:
|
||||
if raw_item_user:
|
||||
item_user_values.add(str(decrypt_text(raw_item_user)).strip())
|
||||
item_user_values.add(_resolve_library_card_id(raw_item_user))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -11917,10 +11955,10 @@ def admin_damaged_items():
|
||||
|
||||
# Benutzernamen der Ausleihe entschlüsseln
|
||||
if active_borrow and active_borrow.get('User'):
|
||||
active_borrow['User'] = decrypt_text(active_borrow['User'])
|
||||
active_borrow['User'] = _display_username(active_borrow['User'])
|
||||
|
||||
# Direkt am Item hinterlegten Nutzer entschlüsseln
|
||||
item_user = decrypt_text(item.get('User')) if item.get('User') else ''
|
||||
item_user = _display_username(item.get('User')) if item.get('User') else ''
|
||||
|
||||
# Datenstruktur exakt für dein Jinja2 Template aufbauen
|
||||
damaged_items.append({
|
||||
@@ -12493,7 +12531,7 @@ def cancel_ausleihung_route(id):
|
||||
ausleihung_status = ausleihung.get('Status', 'unknown')
|
||||
raw_ausleihung_user = ausleihung.get('User', '')
|
||||
try:
|
||||
ausleihung_user = decrypt_text(raw_ausleihung_user) if raw_ausleihung_user else ''
|
||||
ausleihung_user = _display_username(raw_ausleihung_user)
|
||||
except Exception:
|
||||
ausleihung_user = str(raw_ausleihung_user or '')
|
||||
print(f"Found ausleihung: ID={id}, Status={ausleihung_status}")
|
||||
|
||||
@@ -695,13 +695,42 @@ def get_user_by_student_card(student_card_id):
|
||||
if not normalized:
|
||||
return None
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = _get_tenant_db(client)
|
||||
users = db['student_cards']
|
||||
found_user = users.find_one({'SchülerName': normalized})
|
||||
client.close()
|
||||
try:
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
return users.find_one({
|
||||
'$or': [
|
||||
{'StudentCardId': normalized},
|
||||
{'StudentCardId': student_card_id},
|
||||
]
|
||||
})
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def get_user_by_student_card_record_id(card_id):
|
||||
"""Return the user assigned to a student-card document ID."""
|
||||
if card_id is None:
|
||||
return None
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
try:
|
||||
db = _get_tenant_db(client)
|
||||
card = None
|
||||
if isinstance(card_id, ObjectId):
|
||||
card = db['student_cards'].find_one({'_id': card_id})
|
||||
elif ObjectId.is_valid(str(card_id)):
|
||||
card = db['student_cards'].find_one({'_id': ObjectId(str(card_id))})
|
||||
if not card:
|
||||
return None
|
||||
|
||||
card_id_value = normalize_student_card_id(card.get('AusweisId'))
|
||||
if not card_id_value:
|
||||
return None
|
||||
return db['users'].find_one({'StudentCardId': card_id_value})
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
# Do not call dp.decrypt_text() here because found_user is a MongoDB dictionary.
|
||||
return found_user
|
||||
|
||||
def get_user_by_student_ident(student_ident):
|
||||
"""Return user dict by student ident by decrypting all cards and matching."""
|
||||
@@ -874,6 +903,42 @@ def get_user(username):
|
||||
client.close()
|
||||
|
||||
|
||||
def get_user_by_id(user_id):
|
||||
"""Retrieve a user by MongoDB document ID."""
|
||||
if user_id is None:
|
||||
return None
|
||||
|
||||
lookup_values = [str(user_id)]
|
||||
if ObjectId.is_valid(str(user_id)):
|
||||
lookup_values.insert(0, ObjectId(str(user_id)))
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
try:
|
||||
def find_in_db(database_name):
|
||||
users = client[database_name]['users']
|
||||
for lookup_value in lookup_values:
|
||||
user = users.find_one({'_id': lookup_value})
|
||||
if user:
|
||||
return user
|
||||
return None
|
||||
|
||||
tenant_db, tenant_id = _resolve_request_tenant_db()
|
||||
if tenant_db:
|
||||
user = find_in_db(tenant_db)
|
||||
if user:
|
||||
return user
|
||||
|
||||
if _has_tenant_configs() and tenant_db is None:
|
||||
logger.warning(
|
||||
"Refusing default DB fallback for user ID lookup because tenant configs exist and no tenant was resolved."
|
||||
)
|
||||
return None
|
||||
|
||||
return find_in_db(cfg.MONGODB_DB)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def check_admin(username):
|
||||
"""Check if a user has administrator privileges."""
|
||||
user = get_user(username)
|
||||
|
||||
@@ -266,6 +266,11 @@
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.code-search-label {
|
||||
color: #b93845;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 8px 12px;
|
||||
background-color: #007bff;
|
||||
@@ -284,10 +289,10 @@
|
||||
}
|
||||
|
||||
#code-search.searching {
|
||||
background-color: #fff3cd;
|
||||
background-color: #fff5f5;
|
||||
transition: background-color 0.3s;
|
||||
border: 2px solid #ffc107;
|
||||
box-shadow: 0 0 8px rgba(255, 193, 7, 0.5);
|
||||
border: 2px solid #d65a62;
|
||||
box-shadow: 0 0 8px rgba(190, 55, 66, 0.28);
|
||||
}
|
||||
|
||||
/* QR Scanner styles */
|
||||
@@ -562,32 +567,55 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navigation-buttons {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.items-navigation {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-columns: 42px minmax(0, 1fr) 42px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
column-gap: 16px;
|
||||
width: calc(100% + 116px);
|
||||
margin-left: -58px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.navigation-buttons .prev-button,
|
||||
.navigation-buttons .next-button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
.navigation-button-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navigation-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 0;
|
||||
background: var(--ui-surface-soft);
|
||||
color: var(--ui-title);
|
||||
border: 1px solid var(--ui-border);
|
||||
box-shadow: var(--ui-shadow-sm);
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border-radius: 10px;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease, border-color 160ms ease;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.navigation-buttons .prev-button:hover,
|
||||
.navigation-buttons .next-button:hover {
|
||||
background-color: #0056b3;
|
||||
.navigation-button:hover {
|
||||
background: var(--ui-surface);
|
||||
border-color: var(--module-accent-color);
|
||||
box-shadow: var(--ui-shadow-md);
|
||||
transform: translateY(-2px) scale(1.04);
|
||||
}
|
||||
|
||||
.navigation-button:active {
|
||||
transform: translateY(0) scale(0.96);
|
||||
}
|
||||
|
||||
.navigation-button:focus-visible {
|
||||
outline: 3px solid rgba(37, 99, 235, 0.35);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
@@ -750,15 +778,15 @@
|
||||
}
|
||||
|
||||
.code-match {
|
||||
border: 2px solid #17a2b8 !important;
|
||||
box-shadow: 0 0 15px rgba(23, 162, 184, 0.5) !important;
|
||||
border: 2px solid #d65a62 !important;
|
||||
box-shadow: 0 0 15px rgba(190, 55, 66, 0.28) !important;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.exact-code-match {
|
||||
border: 3px solid #28a745 !important;
|
||||
box-shadow: 0 0 20px rgba(40, 167, 69, 0.7) !important;
|
||||
border: 3px solid #b93845 !important;
|
||||
box-shadow: 0 0 20px rgba(185, 56, 69, 0.42) !important;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
}
|
||||
@@ -1641,9 +1669,11 @@
|
||||
/* Navigation buttons mobile */
|
||||
.items-navigation {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.navigation-buttons {
|
||||
.navigation-button-slot {
|
||||
display: none !important; /* Hide horizontal navigation on mobile */
|
||||
}
|
||||
|
||||
@@ -1956,7 +1986,7 @@
|
||||
/* Standardized button styles across the application */
|
||||
.search-button, .scan-button, .filter-toggle, .clear-filter,
|
||||
.add-new-btn, .popup-close-button, .prev-image-button, .next-image-button,
|
||||
.navigation-buttons .prev-button, .navigation-buttons .next-button {
|
||||
.navigation-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -2511,7 +2541,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
|
||||
<div class="filter-group search-field">
|
||||
<div class="filter-header">
|
||||
<label for="code-search">Code suchen:</label>
|
||||
<label class="code-search-label" for="code-search">Code suchen:</label>
|
||||
<button type="button" class="clear-filter" onclick="clearCodeSearch()">Clear</button>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
@@ -2554,13 +2584,15 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
</div>
|
||||
|
||||
<div class="items-navigation">
|
||||
<div class="navigation-buttons">
|
||||
<button class="prev-button" type="button" onclick="scrollPrev()" aria-label="Vorherige Elemente">❮</button>
|
||||
<button class="next-button" type="button" onclick="scrollNext()" aria-label="Nächste Elemente">❯</button>
|
||||
<div class="navigation-button-slot">
|
||||
<button class="navigation-button prev-button" type="button" onclick="scrollPrev()" aria-label="Vorherige Elemente">❮</button>
|
||||
</div>
|
||||
<div id="items-container" class="items-container">
|
||||
<!-- Items will be dynamically loaded here -->
|
||||
</div>
|
||||
<div class="navigation-button-slot">
|
||||
<button class="navigation-button next-button" type="button" onclick="scrollNext()" aria-label="Nächste Elemente">❯</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="item-modal" class="item-modal">
|
||||
@@ -3336,10 +3368,11 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
|
||||
function loadItems(offset = 0, append = false, searchTerm = '', searchScope = 'all') {
|
||||
// Keep list payload lightweight; full details are fetched on-demand in openItemQuick.
|
||||
const searchParams = searchTerm
|
||||
const searchParams = searchTerm && searchScope === 'title_description'
|
||||
? `&search=${encodeURIComponent(searchTerm)}&search_scope=${encodeURIComponent(searchScope)}`
|
||||
: '';
|
||||
return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}&light_mode=true${searchParams}`)
|
||||
const limit = (searchTerm && searchScope === 'all') ? 5000 : MAIN_ADMIN_ITEMS_PAGE_SIZE;
|
||||
return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${limit}&light_mode=true${searchParams}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const itemsContainer = document.querySelector('#items-container');
|
||||
@@ -3358,8 +3391,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
if (!append && (!pageItems || pageItems.length === 0)) {
|
||||
itemsContainer.innerHTML = `
|
||||
<div class="no-items-message">
|
||||
<div>Keine Objekte gefunden</div>
|
||||
<a class="empty-state-cta" href="{{ url_for('upload_admin') }}">Erstellen sie hier ihr erstes Item</a>
|
||||
<div>Kein Item gefunden</div>
|
||||
</div>
|
||||
`;
|
||||
if (itemsIndicator) {
|
||||
|
||||
Reference in New Issue
Block a user