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(`