Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20e5d3bb9a | |||
| f62b22c2f7 | |||
| e76d525e4b | |||
| 7117dac67c | |||
| 5313c507ed |
+1
-1
@@ -1 +1 @@
|
||||
v0.8.25
|
||||
v0.8.27
|
||||
|
||||
+54
-28
@@ -4822,6 +4822,23 @@ def get_user_appointments():
|
||||
appointments = termin.get_upcoming_for_user(username, limit=250)
|
||||
|
||||
result = []
|
||||
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:
|
||||
appt_id = str(appt.get('_id') or '')
|
||||
if not appt_id:
|
||||
@@ -4830,35 +4847,44 @@ def get_user_appointments():
|
||||
date_start = str(appt.get('date_start') or '')
|
||||
date_end = str(appt.get('date_end') or date_start)
|
||||
time_span = appt.get('time_span', []) or []
|
||||
|
||||
# Try to extract a time range from the first time_span entry
|
||||
start_dt_str = date_start
|
||||
end_dt_str = date_end
|
||||
try:
|
||||
import re as _re
|
||||
if time_span:
|
||||
first = str(time_span[0])
|
||||
m = _re.search(r"(\d{2}:\d{2})-(\d{2}:\d{2})", first)
|
||||
if m:
|
||||
start_dt_str = f"{date_start}T{m.group(1)}"
|
||||
end_dt_str = f"{date_start}T{m.group(2)}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
title = appt.get('note') or f"Termin von {appt.get('user') or ''}"
|
||||
result.append({
|
||||
'id': appt_id,
|
||||
'title': title,
|
||||
'start': start_dt_str,
|
||||
'end': end_dt_str,
|
||||
'status': 'planned',
|
||||
'itemId': appt_id,
|
||||
'userName': str(appt.get('user') or ''),
|
||||
'notes': str(appt.get('note') or ''),
|
||||
'period': None,
|
||||
'isCurrentUser': True,
|
||||
'itemBorrower': '',
|
||||
})
|
||||
days_in_range = _date_iter(date_start, date_end) or ([date_start] if date_start else [])
|
||||
|
||||
span_entries = []
|
||||
for entry in time_span:
|
||||
s = str(entry or '').strip()
|
||||
if not s:
|
||||
continue
|
||||
m_date = _re.match(r"^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
||||
if m_date:
|
||||
span_entries.append((m_date.group(1), m_date.group(2), m_date.group(3)))
|
||||
continue
|
||||
m = _re.match(r"^(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
||||
if m:
|
||||
for day in days_in_range:
|
||||
span_entries.append((day, m.group(1), m.group(2)))
|
||||
|
||||
# If the stored span is already day-specific, use it as-is.
|
||||
# If it was generic, we duplicated it to each day in the range above.
|
||||
if not span_entries and days_in_range:
|
||||
# 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'))
|
||||
|
||||
for day, start_time, end_time in span_entries:
|
||||
result.append({
|
||||
'id': f"{appt_id}-{day}-{start_time}",
|
||||
'title': title,
|
||||
'start': f"{day}T{start_time}",
|
||||
'end': f"{day}T{end_time}",
|
||||
'status': 'planned',
|
||||
'itemId': appt_id,
|
||||
'userName': str(appt.get('user') or ''),
|
||||
'notes': str(appt.get('note') or ''),
|
||||
'period': None,
|
||||
'isCurrentUser': True,
|
||||
'itemBorrower': '',
|
||||
})
|
||||
|
||||
return jsonify({'ok': True, 'bookings': result})
|
||||
except Exception as e:
|
||||
|
||||
@@ -357,63 +357,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Build background gap events to grey-out times that are not available
|
||||
function buildBackgroundGaps() {
|
||||
const bgEvents = [];
|
||||
const uiMin = slotMinTime; // UI visible window start
|
||||
const uiMax = slotMaxTime; // UI visible window end
|
||||
|
||||
// Helper to collect allowed intervals per day
|
||||
const spans = Array.isArray(available.time_span) ? available.time_span : [];
|
||||
|
||||
allDays.forEach(function(day) {
|
||||
// collect allowed intervals for this day
|
||||
const allowed = [];
|
||||
spans.forEach(function(entry) {
|
||||
const p = parseTimeSpanEntry(entry);
|
||||
if (!p) return;
|
||||
if (p.date && p.date !== day) return;
|
||||
// for generic spans (no date) include for all days
|
||||
allowed.push({from: p.from + ':00', to: p.to + ':00'});
|
||||
});
|
||||
|
||||
// sort allowed intervals
|
||||
allowed.sort(function(a,b){ return a.from < b.from ? -1 : (a.from > b.from ? 1 : 0); });
|
||||
|
||||
// if no allowed intervals -> grey the full ui window
|
||||
if (allowed.length === 0) {
|
||||
bgEvents.push({
|
||||
start: new Date(day + 'T' + uiMin),
|
||||
end: new Date(day + 'T' + uiMax),
|
||||
display: 'background',
|
||||
backgroundColor: '#f1f3f5'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// gap before first allowed
|
||||
if (allowed[0].from > uiMin) {
|
||||
bgEvents.push({ start: new Date(day + 'T' + uiMin), end: new Date(day + 'T' + allowed[0].from), display: 'background', backgroundColor: '#f1f3f5' });
|
||||
}
|
||||
|
||||
// gaps between allowed intervals
|
||||
for (let i = 0; i < allowed.length - 1; i++) {
|
||||
const endA = allowed[i].to;
|
||||
const startB = allowed[i+1].from;
|
||||
if (startB > endA) {
|
||||
bgEvents.push({ start: new Date(day + 'T' + endA), end: new Date(day + 'T' + startB), display: 'background', backgroundColor: '#f1f3f5' });
|
||||
}
|
||||
}
|
||||
|
||||
// gap after last allowed
|
||||
const last = allowed[allowed.length - 1];
|
||||
if (last.to < uiMax) {
|
||||
bgEvents.push({ start: new Date(day + 'T' + last.to), end: new Date(day + 'T' + uiMax), display: 'background', backgroundColor: '#f1f3f5' });
|
||||
}
|
||||
});
|
||||
|
||||
return bgEvents;
|
||||
}
|
||||
// No background greying: show full calendar skeleton and only mark possible slots
|
||||
|
||||
function updateSliderLabel() {
|
||||
const dateList = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || ''));
|
||||
@@ -471,11 +415,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Add background gap events first so they appear behind slot events
|
||||
const bgGaps = buildBackgroundGaps();
|
||||
bgGaps.forEach(function(be) {
|
||||
calendar.addEvent(be);
|
||||
});
|
||||
// No background gaps: keep full skeleton/grid visible
|
||||
|
||||
// Add candidate free slots (blue)
|
||||
candidateSlots.forEach(function (slot) {
|
||||
|
||||
@@ -287,7 +287,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const filteredEvents = showCompletedBookings
|
||||
? processedEvents
|
||||
: processedEvents.filter(event => event.extendedProps.status !== 'completed');
|
||||
|
||||
|
||||
// Provide events to calendar
|
||||
successCallback(filteredEvents);
|
||||
})
|
||||
@@ -314,22 +314,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
periodDiv.style.display = 'inline-block';
|
||||
periodDiv.style.fontSize = '10px';
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user