Compare commits

...

14 Commits

Author SHA1 Message Date
Aiirondev_dev 0be4e3ca58 return is beeing ignored to the submition 2026-06-27 00:35:44 +02:00
Aiirondev_dev e2aeea46f9 Fix of the encryption of when getting the upcoming events for a user 2026-06-27 00:16:59 +02:00
Aiirondev_dev 865fddd45b removal of unused encryption of user to compensate before right implementation of encryption 2026-06-27 00:08:41 +02:00
Aiirondev_dev 1835195d2f development debugging version 2026-06-26 23:58:15 +02:00
Aiirondev_dev 19a585b2ec Implementation of encryption for sensitive information that may be providet by the client, to ensure that all information important or not is safe 2026-06-26 23:39:05 +02:00
Aiirondev_dev 925f07e96f removal of development debugging 2026-06-26 22:00:53 +02:00
Aiirondev_dev b8beec8209 development Changes that will sort out the defect Link pasting in the Other Tab 2026-06-26 21:34:57 +02:00
Aiirondev_dev dd9390c649 fix of a slight mistake with the Link passing 2026-06-26 21:27:09 +02:00
Aiirondev_dev f31c4e2ff7 fix of misspelled atribute of a Arg 2026-06-26 21:18:50 +02:00
Aiirondev_dev 71e2895362 vital changes to the processing of the generation off a new appointment 2026-06-26 19:40:01 +02:00
Aiirondev_dev d2c7e57f8d changes to the Event lisseer and the Display of the booking range for the client 2026-06-26 19:37:04 +02:00
Aiirondev_dev 111d40b787 activate changes calculation akso when changing the Client amount 2026-06-26 19:27:21 +02:00
Aiirondev_dev 1a9513d442 fix 2026-06-26 19:22:21 +02:00
Aiirondev_dev 3900059eb1 Configurierung des Slot amount generation 2026-06-26 19:13:30 +02:00
5 changed files with 220 additions and 112 deletions
+149 -73
View File
@@ -18,9 +18,11 @@ Collection Structure:
- Status fields: slots_used_by - Status fields: slots_used_by
""" """
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
import Web.modules.inventarsystem.data_protection as dp
from Web.modules.database.settings import MongoClient from Web.modules.database.settings import MongoClient
from bson.objectid import ObjectId from bson.objectid import ObjectId
import datetime import datetime
import ast
def _get_tenant_db(client): def _get_tenant_db(client):
@@ -37,8 +39,47 @@ def _active_record_query(extra_query=None):
base_query.update(extra_query) base_query.update(extra_query)
return base_query return base_query
def _decrypt_appointment(item):
"""Helper function to safely decrypt appointment fields back to their original types."""
if not item:
return item
try:
if 'user' in item and item['user']:
item['user'] = dp.decrypt_text(item['user'])
if 'note' in item and item['note']:
item['note'] = dp.decrypt_text(item['note'])
if 'title' in item and item['title']:
item['title'] = dp.decrypt_text(item['title'])
if 'mail' in item and item['mail']:
decrypted_mail = dp.decrypt_text(item['mail'])
try:
item['mail'] = ast.literal_eval(decrypted_mail)
except Exception:
item['mail'] = decrypted_mail
if 'custom_fields' in item and item['custom_fields']:
item['custom_fields'] = [dp.decrypt_text(field) for field in item['custom_fields']]
if 'slots_booked' in item and item['slots_booked']:
# If it's a string, it was encrypted during an update execution
if isinstance(item['slots_booked'], str):
decrypted_slots = dp.decrypt_text(item['slots_booked'])
try:
item['slots_booked'] = ast.literal_eval(decrypted_slots)
except Exception:
item['slots_booked'] = decrypted_slots
except Exception as e:
print(f"Error during decryption: {e}")
return item
def add(date_start: str, date_end: str, time_span: list, slots: int, slot_lenght: int, user: str, mail: list=[], note:str="", calendar_enabled: bool=False, title: str="", custom_fields: list = (), clients_p_slot: int=1): def add(date_start: str, date_end: str, time_span: list, slots: int, slot_lenght: int, user: str, mail: list=[], note:str="", calendar_enabled: bool=False, title: str="", custom_fields: list = (), clients_p_slot: int=1):
client = None
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
@@ -50,11 +91,11 @@ def add(date_start: str, date_end: str, time_span: list, slots: int, slot_lenght
'time_span': time_span, 'time_span': time_span,
'slots': slots, 'slots': slots,
'slot_lenght': slot_lenght, 'slot_lenght': slot_lenght,
'user': user, 'user': dp.encrypt_text(user.strip()),
'mail': mail, 'mail': dp.encrypt_text(str(mail)),
'note': note, 'note': dp.encrypt_text(note),
'title': title, 'title': dp.encrypt_text(title),
'custom_fields': custom_fields, 'custom_fields': [dp.encrypt_text(str(field)) for field in custom_fields],
'calendar_enabled': bool(calendar_enabled), 'calendar_enabled': bool(calendar_enabled),
'clients_per_slot': clients_p_slot, 'clients_per_slot': clients_p_slot,
'slots_booked': [], # -> [(start_time, (names),(custom1, custom2,...)), ...]the list gets there indexes as the slot 1-defined so is can be counted without an extra variable 'slots_booked': [], # -> [(start_time, (names),(custom1, custom2,...)), ...]the list gets there indexes as the slot 1-defined so is can be counted without an extra variable
@@ -64,48 +105,39 @@ def add(date_start: str, date_end: str, time_span: list, slots: int, slot_lenght
result = items.insert_one(item) result = items.insert_one(item)
return result.inserted_id return result.inserted_id
except Exception as e: except Exception as e:
print(f"Exception accured: {e}") print(f"Exception occurred in add: {e}")
return None
finally:
if client:
client.close()
def get_item(id): def get_item(id):
""" """Retrieve a specific appointment by its ID and decrypt it."""
Retrieve a specific appointment by its ID. client = None
Args:
id (str): ID of the appointsment to retrieve
Returns:
dict: The appointment document or None if not found
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
items = db['appointments'] items = db['appointments']
item = items.find_one(_active_record_query({'_id': ObjectId(id)})) item = items.find_one(_active_record_query({'_id': ObjectId(id)}))
client.close()
return item return _decrypt_appointment(item)
except Exception as e: except Exception as e:
print(f"Error retrieving item: {e}") print(f"Error retrieving item: {e}")
return None return None
finally:
if client:
client.close()
def update(id, slots_used: list): def update(id, slots_used: list):
""" """Update an existing appointment's booked slots securely."""
Update an existing appointment. client = None
Args:
id (str): ID of the item to update
Returns:
bool: True if successful, False otherwise
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
items = db['appointments'] items = db['appointments']
update_data = { update_data = {
'slots_booked': slots_used, 'slots_booked': dp.encrypt_text(str(slots_used)),
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now()
} }
@@ -114,53 +146,81 @@ def update(id,slots_used: list):
{'$set': update_data} {'$set': update_data}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
print(f"Error updating item: {e}") print(f"Error updating item: {e}")
return False return False
finally:
if client:
client.close()
def remove_slot(id, date_start_time, name): def remove_slot(id, date_start_time, name):
""" """
Remove a booked slot from an appointment's `slots_booked`. Remove a booked slot from an appointment's encrypted `slots_booked` list.
Args: Because the array is stored as an encrypted string blob, we must decrypt,
id (str): Appointment ID modify it in Python, and re-encrypt it.
date_start_time: The start time value used when booking
name (str): Name associated with the booking
Returns:
bool: True if a slot was removed, False otherwise
""" """
client = None
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
items = db['appointments'] items = db['appointments']
# Attempt to pull the exact element (stored as an array/tuple) item = items.find_one({'_id': ObjectId(id)})
if not item or 'slots_booked' not in item or not item['slots_booked']:
return False
try:
decrypted_slots = dp.decrypt_text(item['slots_booked'])
slots_list = ast.literal_eval(decrypted_slots)
except Exception as e:
print(f"Failed to decrypt or parse slots: {e}")
return False
# Structure format note: [(start_time, (names), (custom1, custom2...)), ...]
updated_slots = []
removed_any = False
for slot in slots_list:
slot_start = slot[0]
slot_names = slot[1]
if slot_start == date_start_time and (slot_names == name or name in slot_names):
removed_any = True
continue
updated_slots.append(slot)
if not removed_any:
return False
result = items.update_one( result = items.update_one(
{'_id': ObjectId(id)}, {'_id': ObjectId(id)},
{'$pull': {'slots_booked': [date_start_time, name]}} {
'$set': {
'slots_booked': dp.encrypt_text(str(updated_slots)),
'LastUpdated': datetime.datetime.now()
}
}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
print(f"Error removing slot: {e}") print(f"Error removing slot: {e}")
return False return False
finally:
if client:
client.close()
def remove(id): def remove(id):
""" """
Soft-delete an appointment by setting its `Deleted` flag. Hard-delete an appointment plan by its ID.
(Note: If your docstring mentions a soft-delete 'Deleted' flag,
Args: change items.delete_one to items.update_one with {'$set': {'Deleted': True}})
id (str): Appointment ID
Returns:
bool: True if the appointment was marked deleted, False otherwise
""" """
client = None
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
@@ -168,70 +228,86 @@ def remove(id):
result = items.delete_one({'_id': ObjectId(id)}) result = items.delete_one({'_id': ObjectId(id)})
client.close()
return result.deleted_count > 0 return result.deleted_count > 0
except Exception as e: except Exception as e:
print(f"Error removing appointment: {e}") print(f"Error removing appointment: {e}")
return False return False
finally:
if client:
client.close()
def remove_done(): def remove_done():
"""removose already finisched appointments""" """Remove all expired appointments whose end date is prior to today in a single call."""
client = None
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
items = db['appointments'] items = db['appointments']
today = datetime.date.today().strftime('%Y-%m-%d') today = datetime.date.today().strftime('%Y-%m-%d')
removed_count = 0
cursor = items.find( result = items.delete_many(
_active_record_query( _active_record_query(
{ {
'date_end': {'$lt': today}, 'date_end': {'$lt': today},
} }
) )
).sort('date_start', 1) )
for item in cursor: return result.deleted_count > 0
item['_id'] = str(item.get('_id'))
result = items.delete_one({'_id': ObjectId(item['_id'])})
removed_count += result.deleted_count
client.close()
return removed_count > 0
except Exception as e: except Exception as e:
print(f"Error removing appointment: {e}") print(f"Error cleaning up finished appointments: {e}")
return False return False
finally:
if client:
client.close()
def get_upcoming_for_user(user: str, limit: int = 25): def get_upcoming_for_user(user: str, limit: int = 25):
"""Return upcoming appointment plans for a user ordered by start date.""" """
Return upcoming appointment plans for a user, handling encrypted database records.
"""
try:
if hasattr(globals(), 'remove_done'):
remove_done() remove_done()
except Exception:
pass
client = None
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
items = db['appointments'] items = db['appointments']
today = datetime.date.today().strftime('%Y-%m-%d') today = datetime.date.today().strftime('%Y-%m-%d')
target_user = str(user or '').strip()
cursor = items.find( cursor = items.find(
_active_record_query( _active_record_query({
{
'user': str(user or '').strip(),
'date_end': {'$gte': today}, 'date_end': {'$gte': today},
} })
)
).sort('date_start', 1) ).sort('date_start', 1)
results = [] results = []
for item in cursor: for item in cursor:
item['_id'] = str(item.get('_id')) decrypted_item = _decrypt_appointment(item)
results.append(item) if not decrypted_item:
continue
if decrypted_item.get('user', '').strip() != target_user:
continue
decrypted_item['_id'] = str(decrypted_item.get('_id'))
results.append(decrypted_item)
if len(results) >= max(1, int(limit)): if len(results) >= max(1, int(limit)):
break break
client.close()
return results return results
except Exception as e: except Exception as e:
print(f"Error retrieving upcoming appointments: {e}") print(f"Error retrieving upcoming appointments: {e}")
return [] return []
finally:
if client:
client.close()
+11 -9
View File
@@ -8,6 +8,7 @@ import Web.modules.emailservice.email as mail_service
import Web.modules.database.termine as termin import Web.modules.database.termine as termin
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
from Web.tenant import get_tenant_context from Web.tenant import get_tenant_context
import Web.modules.inventarsystem.data_protection as dp
def _resolve_public_base_url() -> str: def _resolve_public_base_url() -> str:
@@ -99,8 +100,11 @@ def build_calendar_ics(appointment_id: str) -> str | None:
return None return None
uid = f"terminplaner-{appointment_id}@invario.eu" uid = f"terminplaner-{appointment_id}@invario.eu"
created_at = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
summary = f"Terminplan für {creator}" created_at = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
summary = titel if titel else f"Terminplan für {creator}"
description_lines = [ description_lines = [
f"Buchungslink: {link}", f"Buchungslink: {link}",
f"Zeitraum: {date_start} bis {date_end}", f"Zeitraum: {date_start} bis {date_end}",
@@ -109,7 +113,7 @@ def build_calendar_ics(appointment_id: str) -> str | None:
description_lines.append('Zeitfenster: ' + '; '.join(str(entry) for entry in time_span)) description_lines.append('Zeitfenster: ' + '; '.join(str(entry) for entry in time_span))
if note: if note:
description_lines.append('Notiz: ' + str(note)) description_lines.append('Notiz: ' + str(note))
if titel: if titel and not summary == titel:
description_lines.append('Titel: ' + str(titel)) description_lines.append('Titel: ' + str(titel))
ics_lines = [ ics_lines = [
@@ -125,8 +129,7 @@ def build_calendar_ics(appointment_id: str) -> str | None:
f'DESCRIPTION:{_escape_ics_text(chr(10).join(description_lines))}', f'DESCRIPTION:{_escape_ics_text(chr(10).join(description_lines))}',
f'URL:{_escape_ics_text(link)}', f'URL:{_escape_ics_text(link)}',
f'DTSTART;VALUE=DATE:{_format_ics_date(start_date)}', f'DTSTART;VALUE=DATE:{_format_ics_date(start_date)}',
f'DTEND;VALUE=DATE:{_format_ics_date(end_date + timedelta(days=1))}', f'DTEND;VALUE=DATE:{_format_ics_date(end_date + datetime.timedelta(days=1))}',
f'Titel:{_escape_ics_text(titel)}',
'END:VEVENT', 'END:VEVENT',
'END:VCALENDAR', 'END:VCALENDAR',
'', '',
@@ -172,7 +175,7 @@ def build_client_slot_ics(appointment_id: str, slot_start: str, client_name: str
] ]
uid = f"terminplaner-slot-{appointment_id}-{start_dt.strftime('%Y%m%d%H%M')}@invario.eu" uid = f"terminplaner-slot-{appointment_id}-{start_dt.strftime('%Y%m%d%H%M')}@invario.eu"
created_at = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ') created_at = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
dt_start = start_dt.strftime('%Y%m%dT%H%M%S') dt_start = start_dt.strftime('%Y%m%dT%H%M%S')
dt_end = end_dt.strftime('%Y%m%dT%H%M%S') dt_end = end_dt.strftime('%Y%m%dT%H%M%S')
@@ -198,7 +201,7 @@ def build_client_slot_ics(appointment_id: str, slot_start: str, client_name: str
return '\r\n'.join(ics_lines) return '\r\n'.join(ics_lines)
def new(date_start: str, date_end: str, time_span: list, slots, slot_length, user: str, mail: list=None, note:str="", calendar_enabled: bool=False, title: str="", custom_fields: list = (), client_per_slot: int=1) -> dict: def new(date_start: str, date_end: str, time_span: list, slots, slot_length, user: str, mail: list=None, note:str="", calendar_enabled: bool=False, title: str="", custom_fields: list = (), clients_per_slot: int=1) -> dict:
""" """
Generates a link for the executive to send to his clients to book a time Slot Generates a link for the executive to send to his clients to book a time Slot
""" """
@@ -220,7 +223,7 @@ def new(date_start: str, date_end: str, time_span: list, slots, slot_length, use
normalized_time_span = _normalize_time_span(time_span) normalized_time_span = _normalize_time_span(time_span)
normalized_mail = _normalize_mail_list(mail or []) normalized_mail = _normalize_mail_list(mail or [])
id = termin.add(date_start, date_end, normalized_time_span, slots_int, slot_length_int, user, normalized_mail, note, calendar_enabled=calendar_enabled, title=title, custom_fields=custom_fields, clients_p_slot=client_per_slot) id = termin.add(date_start, date_end, normalized_time_span, slots_int, slot_length_int, user, normalized_mail, note, calendar_enabled=calendar_enabled, title=title, custom_fields=custom_fields, clients_p_slot=clients_per_slot)
id_str = str(id) id_str = str(id)
tenant_id = _current_tenant_id() tenant_id = _current_tenant_id()
@@ -405,7 +408,6 @@ def get_available(id):
def get_available_user(id): def get_available_user(id):
return get_available(id) return get_available(id)
def get_user_upcoming_events(user: str, limit: int = 25) -> list[dict]: def get_user_upcoming_events(user: str, limit: int = 25) -> list[dict]:
user_name = str(user or '').strip() user_name = str(user or '').strip()
if not user_name: if not user_name:
+6 -13
View File
@@ -288,39 +288,31 @@ def configure():
) )
# Call the database service function (standardized to match your underlying code) # Call the database service function (standardized to match your underlying code)
inserted_id = appointment_service.add( inserted_id = appointment_service.new(
date_start=start, date_start=start,
date_end=end, date_end=end,
time_span=time, time_span=time,
slots=slots_amount, slots=slots_amount,
slot_lenght=slot_length, slot_length=slot_length,
user=session["username"], user=session["username"],
mail=mail, mail=mail,
note=note, note=note,
calendar_enabled=add_to_calendar, calendar_enabled=add_to_calendar,
title=title, title=title,
custom_fields=custom, custom_fields=custom,
clients_p_slot=clients_p_slot clients_per_slot=clients_p_slot
) )
if not inserted_id: if not inserted_id:
flash('Fehler beim Erstellen des Terminplans.', 'error') flash('Fehler beim Erstellen des Terminplans.', 'error')
return redirect(url_for('terminplaner.configure')) return redirect(url_for('terminplaner.configure'))
# Resolve the URL string here using Flask's native url_for instead of relying on the database layer
generated_link = url_for(
'terminplaner.client',
appointment_id=str(inserted_id),
tenant=_current_tenant_id() or None,
_external=True
)
flash('Der Terminplan wurde angelegt.', 'success') flash('Der Terminplan wurde angelegt.', 'success')
return render_template( return render_template(
'termin_configure.html', 'termin_configure.html',
school_periods=cfg.SCHOOL_PERIODS, school_periods=cfg.SCHOOL_PERIODS,
generated_link=generated_link, generated_link=inserted_id['link'],
calendar_link=None, # Update with calendar service link generation if needed calendar_link=None,
add_to_calendar=add_to_calendar, add_to_calendar=add_to_calendar,
email_service_enabled=cfg.EMAIL_ENABLED, email_service_enabled=cfg.EMAIL_ENABLED,
title=title, title=title,
@@ -382,6 +374,7 @@ def main():
upcoming_events = appointment_service.get_user_upcoming_events(current_user) if current_user else [] upcoming_events = appointment_service.get_user_upcoming_events(current_user) if current_user else []
tenant_id = _current_tenant_id() tenant_id = _current_tenant_id()
return render_template( return render_template(
'terminplaner.html', 'terminplaner.html',
school_periods=cfg.SCHOOL_PERIODS, school_periods=cfg.SCHOOL_PERIODS,
+1 -1
View File
@@ -452,7 +452,7 @@ document.addEventListener('DOMContentLoaded', function () {
const slotStartSet = new Set(candidateSlots.map(function (slot) { return slot.start; })); const slotStartSet = new Set(candidateSlots.map(function (slot) { return slot.start; }));
const allDays = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || '')); const allDays = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || ''));
let slotMinTime = '08:15:00'; let slotMinTime = '08:00:00';
let slotMaxTime = '20:00:00'; let slotMaxTime = '20:00:00';
const visibleStart = allDays[0] || String(available.date_start || ''); const visibleStart = allDays[0] || String(available.date_start || '');
+46 -9
View File
@@ -82,7 +82,7 @@
</div> </div>
</div> </div>
<div id="custom-fields-container" class="mt-4"> <div id="custom-fields-container" class="mt-4">
<h3 class="mb-3">Custom Fields</h3> <h3 class="mb-3">Benutzerdefinierte Felder</h3>
<div class="mb-3 custom-field-row"> <div class="mb-3 custom-field-row">
<input type="text" <input type="text"
@@ -155,7 +155,7 @@
const slotLengthInput = document.getElementById('slot_length'); const slotLengthInput = document.getElementById('slot_length');
const clientsperslot = document.getElementById('clients_per_slot') const clientsperslot = document.getElementById('clients_per_slot')
const slotsAmountsInput = document.getElementById('slots_amounts'); const slotsAmountsInput = document.getElementById('slots_amounts');
const slotsAmountsDisplay = document.getElementById('slots_amounts_display'); // Neu: Anzeige-Element const slotsAmountsDisplay = document.getElementById('slots_amounts_display');
if (!startDateInput || !endDateInput || !buildButton || !daysContainer || !timeFrameTextarea) { if (!startDateInput || !endDateInput || !buildButton || !daysContainer || !timeFrameTextarea) {
return; return;
@@ -203,6 +203,18 @@
if (slotsAmountsDisplay) slotsAmountsDisplay.innerText = totalSlots + " Slots gesamt"; if (slotsAmountsDisplay) slotsAmountsDisplay.innerText = totalSlots + " Slots gesamt";
} }
// Helper utility in case your timeToMinutes handler doesn't catch empty strings safely
function safeTimeToMinutes(timeString) {
if (!timeString || typeof timeString !== 'string' || !timeString.includes(':')) {
return 0; // Return 0 instead of NaN for empty or missing values
}
const parts = timeString.split(':');
const hours = parseInt(parts[0], 10);
const minutes = parseInt(parts[1], 10);
if (isNaN(hours) || isNaN(minutes)) return 0;
return (hours * 60) + minutes;
}
// Berechnet die Slots basierend auf den konfigurierten Zeiten & Pausen // Berechnet die Slots basierend auf den konfigurierten Zeiten & Pausen
function calculateSlots() { function calculateSlots() {
if (!slotLengthInput || !slotsAmountsInput || !clientsperslot) return; if (!slotLengthInput || !slotsAmountsInput || !clientsperslot) return;
@@ -213,6 +225,9 @@
return; return;
} }
// FIX 1: Make sure we parse the input VALUE, defaulting to 1 if empty or invalid
const multiplier = parseInt(clientsperslot.value, 10) || 1;
let totalSlots = 0; let totalSlots = 0;
const rows = Array.from(daysContainer.querySelectorAll('[data-day-row]')); const rows = Array.from(daysContainer.querySelectorAll('[data-day-row]'));
@@ -224,12 +239,13 @@
if (!startTime || !endTime) return; if (!startTime || !endTime) return;
const startMins = timeToMinutes(startTime); // Using safety parsing to prevent missing values from generating NaN
const endMins = timeToMinutes(endTime); const startMins = safeTimeToMinutes(startTime);
const pauseStartMins = timeToMinutes(pauseStart); const endMins = safeTimeToMinutes(endTime);
const pauseEndMins = timeToMinutes(pauseEnd); const pauseStartMins = safeTimeToMinutes(pauseStart);
const pauseEndMins = safeTimeToMinutes(pauseEnd);
if (endMins <= startMins) return; // Ungültige Zeit if (isNaN(startMins) || isNaN(endMins) || endMins <= startMins) return;
// Wenn eine gültige Pause innerhalb der Start/Endzeit existiert // Wenn eine gültige Pause innerhalb der Start/Endzeit existiert
if (pauseStartMins > 0 && pauseEndMins > 0 && pauseStartMins < pauseEndMins && pauseStartMins > startMins && pauseStartMins < endMins) { if (pauseStartMins > 0 && pauseEndMins > 0 && pauseStartMins < pauseEndMins && pauseStartMins > startMins && pauseStartMins < endMins) {
@@ -248,8 +264,12 @@
totalSlots += Math.floor((endMins - startMins) / slotLength); totalSlots += Math.floor((endMins - startMins) / slotLength);
} }
}); });
totalSlotscomplete = totalSlots * Number(clientsperslot)
updateSlotsDisplay(totalSlotscomplete); // FIX 2: Multiply by our safely parsed input value scalar
const totalSlotscomplete = totalSlots * multiplier;
// Safety check: if anything went wrong anyway, fallback to 0 instead of displaying NaN
updateSlotsDisplay(isNaN(totalSlotscomplete) ? 0 : totalSlotscomplete);
} }
// Synchronisiert das Textfeld und fügt den Break-Cut hinzu // Synchronisiert das Textfeld und fügt den Break-Cut hinzu
@@ -414,10 +434,27 @@
slotLengthInput.addEventListener('input', calculateSlots); slotLengthInput.addEventListener('input', calculateSlots);
slotLengthInput.addEventListener('change', calculateSlots); slotLengthInput.addEventListener('change', calculateSlots);
} }
if (clientsperslot) {
clientsperslot.addEventListener('input', calculateSlots);
clientsperslot.addEventListener('change', calculateSlots);
}
if (startDateInput.value && endDateInput.value) { if (startDateInput.value && endDateInput.value) {
renderRows(); renderRows();
} }
const configForm = document.querySelector('form');
if (configForm) {
configForm.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
if (event.target.tagName === 'TEXTAREA') return;
if (event.target.tagName === 'BUTTON' && event.target.type === 'submit') return;
event.preventDefault();
}
});
}
})(); })();
</script> </script>
{% endblock %} {% endblock %}