diff --git a/gen_cons.py b/gen_cons.py index 29a3962..9e593a7 100644 --- a/gen_cons.py +++ b/gen_cons.py @@ -694,6 +694,9 @@ def main( subprocess.run( ["scp", Rota.exported_rota_file, "ross@46.101.13.46:proc/proc-rota/output/"] ) + subprocess.run( + ["scp", "output/timetable.js", "ross@46.101.13.46:proc/proc-rota/output/"] + ) if suspend_on_finish: os.system("systemctl suspend") diff --git a/output/timetable.js b/output/timetable.js index 4fec5b3..bfe18eb 100644 --- a/output/timetable.js +++ b/output/timetable.js @@ -403,36 +403,50 @@ function renderWorkerFilterOptions() { } function renderWorkerList() { - console.log("Rendering worker list with filters"); const $wd = $("#worker_details"); - console.log("Worker details container:", $wd.length ? $wd.get(0) : "not found"); if (!$wd.length) return; let workers = $wd.data("workers"); try { if (typeof workers === 'string') workers = JSON.parse(workers); } catch (e) { workers = null; } const filter = ($("#worker-filter").val() || '').toLowerCase(); - console.log("Text filter:", filter); const shiftFilter = ($("#worker-shift-select").val() || ''); + // Parse the original pre block into per-worker text blocks so rich details are preserved. + const detailsByName = {}; + const preText = (($wd.find("pre").first().text() || '') + '').trim(); + const escapeHtml = (text) => String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + if (preText.length > 0) { + preText.split(/\n\s*\n(?=Name:\s*)/g).forEach(block => { + const trimmed = (block || '').trim(); + if (!trimmed) return; + const nameMatch = trimmed.match(/^Name:\s*(.+)$/m); + if (!nameMatch) return; + detailsByName[nameMatch[1].trim()] = trimmed; + }); + } + let $list = $("#worker-list"); if ($list.length === 0) { $list = $("
"); $("#worker_details").append($list); } + // Hide the static pre block since we render interactive cards + $("#worker_details pre").hide(); + $list.show(); $list.empty(); if (!workers) return; workers.forEach(w => { - console.log("Processing worker:", w.name); const name = (w.name || '') + ''; - const displayName = (w.display_name || '') + ''; const site = (w.site || '') + ''; - const searchName = (displayName || name).toLowerCase(); - console.log(`Worker "${name}" at site "${site}" with display name "${displayName}"`); - if (filter) { - const matchesName = searchName.indexOf(filter) !== -1; + if (filter.length > 0) { + const matchesName = name.toLowerCase().indexOf(filter) !== -1; const matchesSite = site.toLowerCase().indexOf(filter) !== -1; if (!matchesName && !matchesSite) return; } - console.log(`Worker "${name}" passed text filter`); // if shiftFilter, ensure worker has target or assigned for that shift // attempt to read per-worker targets and counts from DOM if not present in JSON let targets = w.shift_target_number || {}; @@ -455,38 +469,62 @@ function renderWorkerList() { if (!hasTarget && !hasAssigned) return; } const fte = w.fte || ''; - const labelName = displayName || name; const grade = w.grade || ''; const id = w.id ? String(w.id).substring(0, 8) : ''; - const card = $(`
${labelName} ${id}
${site}Grade: ${grade}FTE: ${fte}%
`); + const card = $(`
${name} ${id}
${site}Grade: ${grade}FTE: ${fte}%
`); // show shift targets summary - if (w.shift_target_number && Object.keys(w.shift_target_number).length > 0) { + if (targets && Object.keys(targets).length > 0) { const tbl = $("
"); const header = $("ShiftAssignedTarget"); tbl.append(header); const tbody = $(""); - Object.keys(w.shift_target_number).forEach(s => { - const t = w.shift_target_number[s]; - const c = (w.shift_counts && w.shift_counts[s]) ? w.shift_counts[s] : 0; + Object.keys(targets).forEach(s => { + const t = targets[s]; + const c = (counts && counts[s]) ? counts[s] : 0; tbody.append(`${s}${c}${parseFloat(t).toFixed(2)}`); }); tbl.append(tbody); card.append(tbl); } + + // Include the original full worker detail text block so no information is lost. + const fullDetails = detailsByName[name] || ''; + if (fullDetails) { + card.append(`
Full details
${escapeHtml(fullDetails)}
`); + } $list.append(card); }); - console.log($("#worker-list")) } -// Add CSS for worker details styling and dark mode +// Add CSS for worker details styling and comprehensive dark mode support if ($('#worker-details-style').length === 0) { $('head').append(``); } @@ -597,6 +739,15 @@ if ($('#tsummary-style').length === 0) { /* Slightly thinner avoid highlight for compact layout */ td.rota-day.avoid-shift.avoid-highlight { border-width: 1.5px !important; box-shadow: 0 0 4px rgba(255,140,0,0.18); } + + /* Higher-contrast dark mode for timetable readability */ + html.dark-mode table.tsummary { background: #111922; color: #e8eef5; } + html.dark-mode table.tsummary th, + html.dark-mode table.tsummary td { border-color: #516274; } + html.dark-mode table.tsummary th { background: #253241; color: #f5f8fc; } + html.dark-mode table.tsummary td { background: #16212d; color: #e8eef5; } + html.dark-mode table.tsummary tr:nth-child(even) td { background: #1a2734; } + html.dark-mode table.tsummary td.target-assigned { color: #f2f6fb; } `); }