feat: Enhance appointment booking interface with date and time selection features
This commit is contained in:
@@ -10,6 +10,135 @@ import Web.modules.database.settings as cfg
|
||||
from Web.tenant import get_tenant_context
|
||||
|
||||
|
||||
def _parse_date_value(value):
|
||||
try:
|
||||
return datetime.datetime.strptime(str(value), '%Y-%m-%d').date()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _parse_time_span_entry(entry):
|
||||
"""Parse entries like '2026-06-01 08:00-12:00' into a date and time window."""
|
||||
text = str(entry or '').strip()
|
||||
if not text:
|
||||
return None
|
||||
|
||||
match = None
|
||||
import re
|
||||
match = re.match(r'^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$', text)
|
||||
if match:
|
||||
date_text, start_text, end_text = match.groups()
|
||||
return {
|
||||
'date': date_text,
|
||||
'start_time': start_text,
|
||||
'end_time': end_text,
|
||||
'raw': text,
|
||||
}
|
||||
|
||||
match = re.match(r'^(\d{2}:\d{2})-(\d{2}:\d{2})$', text)
|
||||
if match:
|
||||
start_text, end_text = match.groups()
|
||||
return {
|
||||
'date': None,
|
||||
'start_time': start_text,
|
||||
'end_time': end_text,
|
||||
'raw': text,
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _iter_date_range(start_date, end_date):
|
||||
if not start_date or not end_date:
|
||||
return []
|
||||
current = start_date
|
||||
days = []
|
||||
while current <= end_date:
|
||||
days.append(current)
|
||||
current += datetime.timedelta(days=1)
|
||||
return days
|
||||
|
||||
|
||||
def _format_display_date(date_value):
|
||||
if not date_value:
|
||||
return ''
|
||||
try:
|
||||
return date_value.strftime('%d.%m.%Y')
|
||||
except Exception:
|
||||
return str(date_value)
|
||||
|
||||
|
||||
def _weekday_name(date_value):
|
||||
names = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
||||
try:
|
||||
return names[date_value.weekday()]
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
|
||||
def _build_slot_overview(termin_range):
|
||||
start_date = _parse_date_value(termin_range.get('date_start'))
|
||||
end_date = _parse_date_value(termin_range.get('date_end'))
|
||||
slot_length = termin_range.get('slot_lenght')
|
||||
try:
|
||||
slot_length = int(slot_length or 0)
|
||||
except Exception:
|
||||
slot_length = 0
|
||||
|
||||
booked_entries = termin_range.get('slots_booked', []) or []
|
||||
booked_starts = set()
|
||||
for booked in booked_entries:
|
||||
if isinstance(booked, (list, tuple)) and len(booked) >= 1:
|
||||
booked_starts.add(str(booked[0]).strip())
|
||||
elif isinstance(booked, dict):
|
||||
booked_starts.add(str(booked.get('start') or booked.get('value') or '').strip())
|
||||
|
||||
windows_by_date = {}
|
||||
fallback_windows = []
|
||||
for entry in termin_range.get('time_span', []) or []:
|
||||
parsed = _parse_time_span_entry(entry)
|
||||
if not parsed:
|
||||
continue
|
||||
if parsed['date']:
|
||||
windows_by_date.setdefault(parsed['date'], []).append(parsed)
|
||||
else:
|
||||
fallback_windows.append(parsed)
|
||||
|
||||
day_items = []
|
||||
for day in _iter_date_range(start_date, end_date):
|
||||
day_key = day.strftime('%Y-%m-%d')
|
||||
day_windows = windows_by_date.get(day_key, []) or fallback_windows
|
||||
slots = []
|
||||
|
||||
for window in day_windows:
|
||||
try:
|
||||
window_start = datetime.datetime.strptime(f"{day_key} {window['start_time']}", '%Y-%m-%d %H:%M')
|
||||
window_end = datetime.datetime.strptime(f"{day_key} {window['end_time']}", '%Y-%m-%d %H:%M')
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
current = window_start
|
||||
while current + datetime.timedelta(minutes=max(1, slot_length)) <= window_end:
|
||||
start_label = current.strftime('%Y-%m-%d %H:%M')
|
||||
end_label = (current + datetime.timedelta(minutes=max(1, slot_length))).strftime('%H:%M')
|
||||
slots.append({
|
||||
'value': start_label,
|
||||
'label': current.strftime('%H:%M') + ' - ' + end_label,
|
||||
'occupied': start_label in booked_starts,
|
||||
})
|
||||
current += datetime.timedelta(minutes=max(1, slot_length))
|
||||
|
||||
day_items.append({
|
||||
'date': day_key,
|
||||
'display_date': _format_display_date(day),
|
||||
'weekday': _weekday_name(day),
|
||||
'slots': slots,
|
||||
'has_slots': bool(slots),
|
||||
})
|
||||
|
||||
return day_items
|
||||
|
||||
|
||||
def _resolve_public_base_url() -> str:
|
||||
if has_request_context():
|
||||
try:
|
||||
@@ -330,6 +459,7 @@ def get_available(id):
|
||||
'slots_total': total_slots,
|
||||
'slots_booked': normalized,
|
||||
'slots_left': slots_left,
|
||||
'slot_days': _build_slot_overview(termin_range),
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error getting available slots: {e}")
|
||||
|
||||
Reference in New Issue
Block a user