.
This commit is contained in:
@@ -8,11 +8,19 @@
|
||||
<h3 class="subtitle">Global leave calendar</h3>
|
||||
{% include 'rota/partials/flatpickr_includes.html' %}
|
||||
<link rel="stylesheet" href="{% static 'css/calendar.css' %}">
|
||||
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.5rem;">
|
||||
<label for="rota-selector" style="margin:0;">Rota:</label>
|
||||
<select id="rota-selector">
|
||||
<option value="">All rotas</option>
|
||||
{% for r in rotas %}
|
||||
<option value="{{ r.id }}" {% if selected_rota_id == r.id %}selected{% endif %}>{{ r.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="calendar" class="calendar" data-mode="inline"></div>
|
||||
<p class="help">View all leave requests across workers. Click and drag to select dates to open the leave form (you'll need to choose a worker in the modal).</p>
|
||||
{% if request.GET.rota_id %}
|
||||
<p class="help">Showing leaves for rota id {{ request.GET.rota_id }}.</p>
|
||||
{% endif %}
|
||||
<p class="help">View all leave requests across workers. Choose a rota to scope the calendar, or select "All rotas" to see everything. Click and drag to select dates to open the leave form (you'll need to choose workers in the modal).</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:0.5rem;">
|
||||
@@ -37,34 +45,43 @@
|
||||
} catch (e) {
|
||||
console.debug('Unable to parse embedded leaves JSON', e);
|
||||
}
|
||||
// Provide a leavesUrl so the calendar will fetch the authoritative all-leaves list
|
||||
// Pass rota_id if present in the querystring so the server filters to rota workers.
|
||||
const rotaParam = new URLSearchParams(window.location.search).get('rota_id');
|
||||
let leavesUrl = '{% url "rota:leaves_global_json" %}';
|
||||
if(rotaParam) leavesUrl += '?rota_id=' + encodeURIComponent(rotaParam);
|
||||
window.initCalendar('calendar', { leaves: leaves, leavesUrl: leavesUrl, requestUrl: '{% url "rota:request_leave" %}', mode: container.dataset.mode || 'inline' });
|
||||
})();
|
||||
</script>
|
||||
// Base URL for leaves JSON
|
||||
let leavesUrlBase = '{% url "rota:leaves_global_json" %}';
|
||||
// Read initial rota selection provided by the view (if any)
|
||||
const initialRota = {% if selected_rota_id %} '{{ selected_rota_id }}' {% else %} null {% endif %};
|
||||
window.initCalendar('calendar', { leaves: leaves, leavesUrl: leavesUrlBase, rotaId: initialRota, requestUrl: '{% url "rota:request_leave" %}', mode: container.dataset.mode || 'inline' });
|
||||
|
||||
{% if request.GET.rota_id %}
|
||||
<div class="box" style="margin-top:0.75rem;">
|
||||
<h4 class="subtitle is-6">Workers on this rota</h4>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
|
||||
<div id="rota-worker-legend">Loading legend…</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
// Wire rota selector to update the calendar scope
|
||||
const selector = document.getElementById('rota-selector');
|
||||
function refreshLegendFor(id){
|
||||
const legend = document.getElementById('rota-worker-legend');
|
||||
const rotaParam = new URLSearchParams(window.location.search).get('rota_id');
|
||||
if(!rotaParam) return;
|
||||
fetch('{% url "rota:leaves_global_json" %}?rota_id=' + encodeURIComponent(rotaParam), {credentials:'same-origin'}).then(r=>r.json()).then(data=>{
|
||||
if(!legend) return;
|
||||
if(!id){ legend.innerText = 'Showing leave for all rotas.'; return; }
|
||||
legend.innerText = 'Loading legend…';
|
||||
fetch(leavesUrlBase + '?rota_id=' + encodeURIComponent(id), {credentials:'same-origin'}).then(r=>r.json()).then(data=>{
|
||||
const byWorker = {};
|
||||
(data.leaves||[]).forEach(l=>{ if(l.worker_id){ byWorker[l.worker_id] = {name:l.reason, color:l.color}; } });
|
||||
if(Object.keys(byWorker).length===0){ legend.innerText = 'No workers with leaves in this rota.'; return; }
|
||||
legend.innerHTML = Object.entries(byWorker).map(([id,info])=> `<div style="display:inline-flex;align-items:center;gap:0.5rem;margin-right:0.5rem"><span style="width:14px;height:14px;background:${info.color};border:1px solid rgba(0,0,0,0.1);display:inline-block"></span><span>${info.name}</span></div>`).join('');
|
||||
}).catch(e=>{ legend.innerText = 'Unable to load legend'; console.debug(e); });
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
}
|
||||
if(selector){
|
||||
selector.addEventListener('change', function(){
|
||||
const v = selector.value || null;
|
||||
try{ const cal = window._calendars && window._calendars['calendar']; if(cal && typeof cal.setRotaId === 'function'){ cal.setRotaId(v); } }
|
||||
catch(e){ console.debug('Unable to set rotaId on calendar', e); }
|
||||
refreshLegendFor(v);
|
||||
});
|
||||
// initial legend
|
||||
refreshLegendFor(initialRota);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="box" style="margin-top:0.75rem;">
|
||||
<h4 class="subtitle is-6">Workers on this rota</h4>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
|
||||
<div id="rota-worker-legend">Select a rota to show its workers.</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="mb-4">
|
||||
<p>
|
||||
<button class="button is-link" hx-get="{% url 'rota:worker_add' %}?rota_id={{ rota.id }}" hx-target="#modal" hx-swap="innerHTML">Add worker</button>
|
||||
<a class="button is-info is-light is-small" href="{% url 'rota:leaves_global' %}?rota_id={{ rota.id }}" style="margin-left:0.5rem;">All leave requests</a>
|
||||
<a class="button is-info is-light is-small" href="{% url 'rota:leaves_global_rota' rota.id %}" style="margin-left:0.5rem;">All leave requests</a>
|
||||
</p>
|
||||
{% include 'rota/partials/worker_list.html' %}
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,10 @@ urlpatterns = [
|
||||
path("worker/settings/token/<uuid:token>/", views.worker_settings_token, name="worker_settings_token"),
|
||||
path("leaves/all/", views.leaves_global, name="leaves_global"),
|
||||
path("leaves/all.json", views.leaves_global_json, name="leaves_global_json"),
|
||||
# Rota-scoped variants which render the same views but with rota_id supplied
|
||||
path("rota/<int:rota_id>/leaves/all/", views.leaves_global, name="leaves_global_rota"),
|
||||
path("rota/<int:rota_id>/leaves/all.json", views.leaves_global_json, name="leaves_global_json_rota"),
|
||||
path("rota/<int:rota_id>/leave/select_workers/", views.select_workers_modal, name="select_workers_modal_rota"),
|
||||
path("leave/select_workers/", views.select_workers_modal, name="select_workers_modal"),
|
||||
path("leave/apply_multi/", views.apply_leave_multi, name="apply_leave_multi"),
|
||||
path("leave/<int:leave_id>/delete/", views.leave_delete, name="leave_delete"),
|
||||
|
||||
@@ -1039,12 +1039,12 @@ def leaves_json(request, worker_id):
|
||||
return JsonResponse({'leaves': data})
|
||||
|
||||
|
||||
def leaves_global_json(request):
|
||||
def leaves_global_json(request, rota_id=None):
|
||||
"""Return JSON list of all leave ranges across all workers."""
|
||||
from .models import Leave
|
||||
# Optionally filter by rota_id (provided as query param) so the global
|
||||
# calendar can be scoped to a particular rota's workers.
|
||||
rota_id = request.GET.get('rota_id')
|
||||
rota_id = request.GET.get('rota_id') if rota_id is None else rota_id
|
||||
qs = Leave.objects.select_related('worker')
|
||||
if rota_id:
|
||||
try:
|
||||
@@ -1078,22 +1078,23 @@ def leaves_global_json(request):
|
||||
return JsonResponse({'leaves': data})
|
||||
|
||||
|
||||
def leaves_global(request):
|
||||
def leaves_global(request, rota_id=None):
|
||||
"""Page showing a global calendar with all leave requests."""
|
||||
# Provide an empty JSON payload initially; the calendar will fetch the authoritative list
|
||||
leaves_json = '[]'
|
||||
return render(request, 'rota/leaves_global.html', {'leaves_json': leaves_json})
|
||||
selected = rota_id if rota_id is not None else request.GET.get('rota_id')
|
||||
return render(request, 'rota/leaves_global.html', {'leaves_json': leaves_json, 'rotas': RotaSchedule.objects.all().order_by('name'), 'selected_rota_id': selected})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def select_workers_modal(request):
|
||||
def select_workers_modal(request, rota_id=None):
|
||||
"""Return an HTMX modal partial that lists workers to apply a selected leave range to.
|
||||
|
||||
Query params: start_date, end_date, rota_id (optional)
|
||||
"""
|
||||
start_date = request.GET.get('start_date')
|
||||
end_date = request.GET.get('end_date')
|
||||
rota_id = request.GET.get('rota_id')
|
||||
rota_id = request.GET.get('rota_id') if rota_id is None else rota_id
|
||||
workers = Worker.objects.all()
|
||||
if rota_id:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user