Add calendar and list view toggle buttons with dynamic content loading
This commit is contained in:
@@ -11,7 +11,15 @@
|
||||
|
||||
{# Rota selection is handled by route scoping; no dropdown needed here #}
|
||||
|
||||
<div style="display:flex;gap:0.5rem;margin-bottom:0.5rem;align-items:center;">
|
||||
<div>
|
||||
<button id="view-calendar-btn" class="button is-small is-link">Calendar</button>
|
||||
<button id="view-list-btn" class="button is-small">List</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar" class="calendar" data-mode="inline"></div>
|
||||
<div id="calendar-list-view" style="display:none; margin-top:0.5rem;"></div>
|
||||
<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>
|
||||
|
||||
@@ -55,6 +63,8 @@
|
||||
const sel = document.getElementById('selected_rota_json');
|
||||
if(sel) initialRota = JSON.parse(sel.textContent || sel.innerText || 'null');
|
||||
}catch(e){ initialRota = null; }
|
||||
// expose initial rota id so other scripts (list view) can access it
|
||||
window._initialRota = initialRota;
|
||||
window.initCalendar('calendar', { leaves: leaves, leavesUrl: leavesUrlBase, rotaId: initialRota, requestUrl: '{% url "rota:request_leave" %}', mode: container.dataset.mode || 'inline' });
|
||||
function refreshLegendFor(id){
|
||||
const globalLegendContent = document.getElementById('global-legend-content');
|
||||
@@ -86,5 +96,106 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const calendarEl = document.getElementById('calendar');
|
||||
const listEl = document.getElementById('calendar-list-view');
|
||||
const btnCal = document.getElementById('view-calendar-btn');
|
||||
const btnList = document.getElementById('view-list-btn');
|
||||
let isList = false;
|
||||
|
||||
function showCalendar(){
|
||||
isList = false;
|
||||
if(listEl) listEl.style.display = 'none';
|
||||
if(calendarEl) calendarEl.style.display = '';
|
||||
if(btnCal) btnCal.classList.add('is-link');
|
||||
if(btnList) btnList.classList.remove('is-link');
|
||||
}
|
||||
|
||||
async function renderList(){
|
||||
if(!listEl) return;
|
||||
listEl.innerHTML = '<p>Loading…</p>';
|
||||
try{
|
||||
let url = '{% url "rota:leaves_global_json" %}';
|
||||
const rotaForList = (typeof window !== 'undefined' && window._initialRota) ? window._initialRota : null;
|
||||
if(rotaForList) url += (url.includes('?') ? '&' : '?') + 'rota_id=' + encodeURIComponent(rotaForList);
|
||||
const res = await fetch(url, {credentials:'same-origin'});
|
||||
if(!res.ok) throw new Error('Failed to fetch leaves');
|
||||
const data = await res.json();
|
||||
const leavesArr = (data.leaves||[]).map(l => ({ start: l.start, end: l.end, reason: l.reason, worker_id: l.worker_id, color: l.color }));
|
||||
|
||||
// find min/max date across all leaves
|
||||
if(leavesArr.length===0){ listEl.innerHTML = '<p>No leaves found.</p>'; return; }
|
||||
function toDate(s){ const p = s.split('-'); return new Date(parseInt(p[0]), parseInt(p[1],10)-1, parseInt(p[2],10)); }
|
||||
function isoDate(d){ return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0'); }
|
||||
let minD = null, maxD = null;
|
||||
leavesArr.forEach(l=>{
|
||||
try{
|
||||
const s = toDate(l.start);
|
||||
const e = toDate(l.end || l.start);
|
||||
if(!minD || s < minD) minD = s;
|
||||
if(!maxD || e > maxD) maxD = e;
|
||||
}catch(e){}
|
||||
});
|
||||
if(!minD || !maxD){ listEl.innerHTML = '<p>No valid leave dates found.</p>'; return; }
|
||||
|
||||
// build a map of dates -> leaves that cover that date
|
||||
const dateMap = {};
|
||||
for(let d = new Date(minD); d <= maxD; d.setDate(d.getDate()+1)){
|
||||
dateMap[isoDate(new Date(d))] = [];
|
||||
}
|
||||
leavesArr.forEach(l=>{
|
||||
const s = toDate(l.start);
|
||||
const e = toDate(l.end || l.start);
|
||||
for(let d = new Date(s); d <= e; d.setDate(d.getDate()+1)){
|
||||
const key = isoDate(new Date(d));
|
||||
if(key in dateMap) dateMap[key].push(l);
|
||||
else dateMap[key] = [l];
|
||||
}
|
||||
});
|
||||
|
||||
// Render dates in ascending order
|
||||
const parts = [];
|
||||
const keys = Object.keys(dateMap).sort();
|
||||
for(const dateKey of keys){
|
||||
const items = dateMap[dateKey];
|
||||
const header = `<div style="margin-top:0.5rem; font-weight:bold">${dateKey}</div>`;
|
||||
if(!items || items.length===0){
|
||||
parts.push(header + `<div style="margin-left:1.5rem;margin-top:0.25rem;color:#666">No leaves</div>`);
|
||||
continue;
|
||||
}
|
||||
const rows = items.map(l=>{
|
||||
const name = l.reason || 'Unknown';
|
||||
const color = l.color || '#ddd';
|
||||
return `<div style="margin-left:1.5rem;margin-top:0.25rem;display:flex;align-items:center;gap:0.5rem"><span style="width:12px;height:12px;background:${color};display:inline-block;border:1px solid rgba(0,0,0,0.1)"></span><span>${name}</span></div>`;
|
||||
}).join('');
|
||||
parts.push(header + rows);
|
||||
}
|
||||
listEl.innerHTML = parts.join('');
|
||||
}catch(err){ console.error('renderList failed', err); listEl.innerHTML = '<p>Unable to load list view.</p>'; }
|
||||
}
|
||||
|
||||
function showList(){
|
||||
isList = true;
|
||||
if(calendarEl) calendarEl.style.display = 'none';
|
||||
if(listEl) listEl.style.display = '';
|
||||
if(btnList) btnList.classList.add('is-link');
|
||||
if(btnCal) btnCal.classList.remove('is-link');
|
||||
renderList();
|
||||
}
|
||||
|
||||
if(btnCal) btnCal.addEventListener('click', showCalendar);
|
||||
if(btnList) btnList.addEventListener('click', showList);
|
||||
// default view
|
||||
showCalendar();
|
||||
|
||||
// Refresh views when leaves change
|
||||
window.addEventListener('leaveUpdated', function(){
|
||||
if(isList) renderList();
|
||||
else try{ if(window._calendars && window._calendars['calendar'] && window._calendars['calendar'].refreshLeaves) window._calendars['calendar'].refreshLeaves(); }catch(e){}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# Consolidated legend above replaces the separate 'Workers on this rota' box #}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user