feat: Refactor appointment handling to support multi-day events and improve calendar visibility
This commit is contained in:
+32
-44
@@ -4823,6 +4823,22 @@ def get_user_appointments():
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
import re as _re
|
import re as _re
|
||||||
|
|
||||||
|
def _date_iter(start_value: str, end_value: str):
|
||||||
|
try:
|
||||||
|
start_date = datetime.datetime.strptime(start_value, '%Y-%m-%d').date()
|
||||||
|
end_date = datetime.datetime.strptime(end_value, '%Y-%m-%d').date()
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
if end_date < start_date:
|
||||||
|
end_date = start_date
|
||||||
|
cursor = start_date
|
||||||
|
days = []
|
||||||
|
while cursor <= end_date:
|
||||||
|
days.append(cursor.strftime('%Y-%m-%d'))
|
||||||
|
cursor += datetime.timedelta(days=1)
|
||||||
|
return days
|
||||||
|
|
||||||
for appt in appointments:
|
for appt in appointments:
|
||||||
appt_id = str(appt.get('_id') or '')
|
appt_id = str(appt.get('_id') or '')
|
||||||
if not appt_id:
|
if not appt_id:
|
||||||
@@ -4831,64 +4847,36 @@ def get_user_appointments():
|
|||||||
date_start = str(appt.get('date_start') or '')
|
date_start = str(appt.get('date_start') or '')
|
||||||
date_end = str(appt.get('date_end') or date_start)
|
date_end = str(appt.get('date_end') or date_start)
|
||||||
time_span = appt.get('time_span', []) or []
|
time_span = appt.get('time_span', []) or []
|
||||||
|
title = appt.get('note') or f"Termin von {appt.get('user') or ''}"
|
||||||
|
days_in_range = _date_iter(date_start, date_end) or ([date_start] if date_start else [])
|
||||||
|
|
||||||
# Determine a sensible start and end datetime for the event.
|
span_entries = []
|
||||||
# Prefer a time from the time_span for the first day and a time for the last day when multi-day.
|
|
||||||
start_dt_str = date_start
|
|
||||||
end_dt_str = date_end
|
|
||||||
first_time = None
|
|
||||||
last_time = None
|
|
||||||
generic_first = None
|
|
||||||
generic_last = None
|
|
||||||
try:
|
|
||||||
# collect times from spans
|
|
||||||
for entry in time_span:
|
for entry in time_span:
|
||||||
s = str(entry or '').strip()
|
s = str(entry or '').strip()
|
||||||
if not s:
|
if not s:
|
||||||
continue
|
continue
|
||||||
m_date = _re.match(r"^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
m_date = _re.match(r"^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
||||||
if m_date:
|
if m_date:
|
||||||
d = m_date.group(1)
|
span_entries.append((m_date.group(1), m_date.group(2), m_date.group(3)))
|
||||||
if d == date_start and not first_time:
|
|
||||||
first_time = m_date.group(2)
|
|
||||||
if d == date_end:
|
|
||||||
last_time = m_date.group(3)
|
|
||||||
continue
|
continue
|
||||||
m = _re.match(r"^(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
m = _re.match(r"^(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
||||||
if m:
|
if m:
|
||||||
if generic_first is None:
|
for day in days_in_range:
|
||||||
generic_first = m.group(1)
|
span_entries.append((day, m.group(1), m.group(2)))
|
||||||
generic_last = m.group(2)
|
|
||||||
|
|
||||||
if not first_time and generic_first:
|
# If the stored span is already day-specific, use it as-is.
|
||||||
first_time = generic_first
|
# If it was generic, we duplicated it to each day in the range above.
|
||||||
if not last_time and generic_last:
|
if not span_entries and days_in_range:
|
||||||
last_time = generic_last
|
# Fallback: create a simple all-day marker for each date so the appointment is visible.
|
||||||
|
for day in days_in_range:
|
||||||
|
span_entries.append((day, '08:00', '16:45'))
|
||||||
|
|
||||||
if first_time:
|
for day, start_time, end_time in span_entries:
|
||||||
start_dt_str = f"{date_start}T{first_time}"
|
|
||||||
|
|
||||||
# If appointment spans multiple days and we have a last_time for the final day, use it
|
|
||||||
if date_end != date_start:
|
|
||||||
if last_time:
|
|
||||||
end_dt_str = f"{date_end}T{last_time}"
|
|
||||||
else:
|
|
||||||
# span until end of final day
|
|
||||||
end_dt_str = f"{date_end}T23:59:59"
|
|
||||||
else:
|
|
||||||
if last_time:
|
|
||||||
end_dt_str = f"{date_start}T{last_time}"
|
|
||||||
except Exception:
|
|
||||||
# fallback: use whole-day span
|
|
||||||
if date_end != date_start:
|
|
||||||
end_dt_str = f"{date_end}T23:59:59"
|
|
||||||
|
|
||||||
title = appt.get('note') or f"Termin von {appt.get('user') or ''}"
|
|
||||||
result.append({
|
result.append({
|
||||||
'id': appt_id,
|
'id': f"{appt_id}-{day}-{start_time}",
|
||||||
'title': title,
|
'title': title,
|
||||||
'start': start_dt_str,
|
'start': f"{day}T{start_time}",
|
||||||
'end': end_dt_str,
|
'end': f"{day}T{end_time}",
|
||||||
'status': 'planned',
|
'status': 'planned',
|
||||||
'itemId': appt_id,
|
'itemId': appt_id,
|
||||||
'userName': str(appt.get('user') or ''),
|
'userName': str(appt.get('user') or ''),
|
||||||
|
|||||||
@@ -288,22 +288,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
? processedEvents
|
? processedEvents
|
||||||
: processedEvents.filter(event => event.extendedProps.status !== 'completed');
|
: processedEvents.filter(event => event.extendedProps.status !== 'completed');
|
||||||
|
|
||||||
// If any event spans multiple days, switch to week view for better visibility
|
|
||||||
try {
|
|
||||||
const multiDay = processedEvents.some(ev => {
|
|
||||||
try {
|
|
||||||
const s = new Date(ev.start);
|
|
||||||
const e = new Date(ev.end);
|
|
||||||
return s && e && s.toDateString() !== e.toDateString();
|
|
||||||
} catch (e) { return false; }
|
|
||||||
});
|
|
||||||
if (multiDay && calendar && calendar.view && calendar.view.type === 'timeGridDay') {
|
|
||||||
calendar.changeView('timeGridWeek');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provide events to calendar
|
// Provide events to calendar
|
||||||
successCallback(filteredEvents);
|
successCallback(filteredEvents);
|
||||||
})
|
})
|
||||||
@@ -330,22 +314,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
periodDiv.style.display = 'inline-block';
|
periodDiv.style.display = 'inline-block';
|
||||||
periodDiv.style.fontSize = '10px';
|
periodDiv.style.fontSize = '10px';
|
||||||
info.el.appendChild(periodDiv);
|
info.el.appendChild(periodDiv);
|
||||||
} else {
|
|
||||||
// Check if it is a multi-day event
|
|
||||||
const start = info.event.start;
|
|
||||||
const end = info.event.end;
|
|
||||||
if (start && end && start.toDateString() !== end.toDateString()) {
|
|
||||||
const periodDiv = document.createElement('div');
|
|
||||||
periodDiv.className = 'fc-event-period-marker';
|
|
||||||
periodDiv.textContent = 'Mehrtägig';
|
|
||||||
periodDiv.style.backgroundColor = 'rgba(255,255,255,0.3)';
|
|
||||||
periodDiv.style.borderRadius = '3px';
|
|
||||||
periodDiv.style.padding = '1px 3px';
|
|
||||||
periodDiv.style.marginTop = '2px';
|
|
||||||
periodDiv.style.display = 'inline-block';
|
|
||||||
periodDiv.style.fontSize = '10px';
|
|
||||||
info.el.appendChild(periodDiv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add borrowed indicator if relevant
|
// Add borrowed indicator if relevant
|
||||||
|
|||||||
Reference in New Issue
Block a user