diff --git a/output/timetable.js b/output/timetable.js index f86b3ae..27e0bde 100644 --- a/output/timetable.js +++ b/output/timetable.js @@ -1027,6 +1027,7 @@ let selectedShifts = new Set(); let shiftColors = {}; let rotaExportViewEnabled = false; +let shiftDisplayOrder = [...oshifts]; oshifts.forEach((shift, i) => { selectedShifts.add(shift); @@ -1483,6 +1484,24 @@ function renderShiftTimetable() { $("#shift-timetable-div").append(gridTable); } +function formatDateForExport(dateStr) { + if (!dateStr) return ""; + let d = new Date(dateStr); + if (Number.isNaN(d.getTime())) return dateStr; + let dd = String(d.getDate()).padStart(2, "0"); + let mm = String(d.getMonth() + 1).padStart(2, "0"); + let yyyy = d.getFullYear(); + return `${dd}/${mm}/${yyyy}`; +} + +function getShiftHeaderLabel(shift) { + let s = (shift || "").toLowerCase(); + if (s.includes("twil")) return "TWIL"; + if (s.includes("night") || s.includes("oncall")) return "NIGHT"; + if (s.includes("weekend")) return "WEEKEND"; + return (shift || "").toUpperCase(); +} + function renderRotaExportTimetable() { let weeks = []; let daysOfWeek = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; @@ -1502,69 +1521,85 @@ function renderRotaExportTimetable() { weeks = weeks.sort((a, b) => parseInt(a) - parseInt(b)); - let table = $(`
Rota Export (Shifts by Day)
`); - let header = $("Week"); + const orderedSelectedShifts = shiftDisplayOrder.filter(s => selectedShifts.has(s)); + const dayShiftOrder = {}; daysOfWeek.forEach(day => { - header.append(`${day}`); + dayShiftOrder[day] = orderedSelectedShifts.filter(shift => { + let found = false; + $(`table#main-table td[data-day='${day}']`).each(function () { + let shifts = ($(this).attr("data-shift") || "").split(",").map(x => x.trim()).filter(Boolean); + if (shifts.includes(shift)) { + found = true; + return false; + } + }); + return found; + }); }); - table.append(header); + + let table = $(`
Rota Export (Shifts by Day)
`); + let headerTop = $(""); + headerTop.append("Week Start"); + headerTop.append("Week"); + daysOfWeek.forEach(day => { + let span = Math.max(1, dayShiftOrder[day].length); + headerTop.append(`${day.toUpperCase()}`); + }); + table.append(headerTop); + + let headerBottom = $(""); + headerBottom.append(""); + headerBottom.append("No"); + daysOfWeek.forEach(day => { + if (dayShiftOrder[day].length === 0) { + headerBottom.append(""); + return; + } + dayShiftOrder[day].forEach(shift => { + headerBottom.append(`${getShiftHeaderLabel(shift)}`); + }); + }); + table.append(headerBottom); weeks.forEach(week => { let row = $(""); - let weekLabel = `${week}`; - if (showWeekStart && weekStartDates[week]) { - weekLabel += `
${weekStartDates[week]}
`; - } - row.append(`${weekLabel}`); + let startDateFormatted = formatDateForExport(weekStartDates[week]); + row.append(`${startDateFormatted}`); + row.append(`${week}`); daysOfWeek.forEach(day => { - let shiftMap = {}; - let dayDate = ""; - - $(`table#main-table td[data-week='${week}'][data-day='${day}']`).each(function () { - let shifts = ($(this).attr("data-shift") || "").split(",").map(s => s.trim()).filter(Boolean); - let date = $(this).attr("data-date") || ""; - if (!dayDate && date) dayDate = date; - - let workerName = $(this).closest("tr").find("td.worker span.name").text().trim(); - shifts.forEach(shift => { - if (!selectedShifts.has(shift)) return; - if (!shiftMap[shift]) { - shiftMap[shift] = new Set(); - } - if (workerName) { - shiftMap[shift].add(workerName); - } - }); - }); - - let shiftNames = Object.keys(shiftMap).sort(); - if (shiftNames.length === 0) { - let emptyText = dayDate ? `
${dayDate}
-` : "-"; - row.append(`${emptyText}`); - } else { - let content = []; - if (dayDate) { - content.push(`
${dayDate}
`); - } - shiftNames.forEach(shift => { - let workerList = Array.from(shiftMap[shift]).sort(); - let workerSuffix = ""; - if (showWorkers && workerList.length > 0) { - workerSuffix = `: ${workerList.join(" / ")}`; - } - let color = shiftColors[shift] || "#f3f3f3"; - content.push(`
${shift}${workerSuffix}
`); - }); - row.append(`${content.join("")}`); + const dayShifts = dayShiftOrder[day]; + if (dayShifts.length === 0) { + row.append(`-`); + return; } + + dayShifts.forEach(shift => { + let workersForShift = new Set(); + $(`table#main-table td[data-week='${week}'][data-day='${day}']`).each(function () { + let shifts = ($(this).attr("data-shift") || "").split(",").map(s => s.trim()).filter(Boolean); + if (!shifts.includes(shift)) return; + let workerName = $(this).closest("tr").find("td.worker span.name").text().trim(); + if (workerName) workersForShift.add(workerName); + }); + + let workerList = Array.from(workersForShift).sort(); + let text = ""; + if (showWorkers) { + text = workerList.join("/"); + } else { + text = workerList.length > 0 ? "X" : ""; + } + + row.append(`${text}`); + }); }); table.append(row); }); if (weeks.length === 0) { - table.append(`No rota data found.`); + table.append(`No rota data found.`); } $("#shift-timetable-div").append(table); @@ -1587,7 +1622,7 @@ rerenderWorkerCheckboxesIfNeeded(); // Patch the timetable button logic to use renderShiftTimetable oshifts.forEach((shift) => { - let button = $(``); + let button = $(``); button.css({ background: "", border: "1px solid #888", @@ -1612,6 +1647,42 @@ oshifts.forEach((shift) => { $("#shift-timetable-options").append(button); }); +let draggedShiftButton = null; + +$(document).on("dragstart", "#shift-timetable-options button[data-shift]", function (e) { + draggedShiftButton = this; + if (e.originalEvent && e.originalEvent.dataTransfer) { + e.originalEvent.dataTransfer.effectAllowed = "move"; + } +}); + +$(document).on("dragover", "#shift-timetable-options button[data-shift]", function (e) { + e.preventDefault(); +}); + +$(document).on("drop", "#shift-timetable-options button[data-shift]", function (e) { + e.preventDefault(); + if (!draggedShiftButton || draggedShiftButton === this) return; + + const $dragged = $(draggedShiftButton); + const $target = $(this); + const draggedIndex = $dragged.index(); + const targetIndex = $target.index(); + + if (draggedIndex < targetIndex) { + $target.after($dragged); + } else { + $target.before($dragged); + } + + shiftDisplayOrder = $("#shift-timetable-options button[data-shift]").map(function () { + return $(this).attr("data-shift"); + }).get(); + + renderShiftTimetable(); + draggedShiftButton = null; +}); + // Add help text to the shift timetable section $("#shift-timetable-options").prepend(`