.
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
html {
|
||||
|
||||
/* color: red; */
|
||||
}
|
||||
|
||||
table {
|
||||
/* margin-top: 50px; */
|
||||
text-align: center;
|
||||
/* table-layout : fixed; */
|
||||
/* width:150px */
|
||||
/* width: 200%; */
|
||||
/* overflow-x: auto;
|
||||
display: block;
|
||||
position: relative; */
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
|
||||
}
|
||||
|
||||
#main-table {
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
.shift-table {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.shift-table thead {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-div {
|
||||
/* overflow-x: auto; */
|
||||
/* width: 80%; */
|
||||
overflow-x: scroll;
|
||||
margin-left: 200px;
|
||||
overflow-y: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#main-table th {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
transform: rotate(90deg) translateX(-100px);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
}
|
||||
|
||||
#main-table th p {
|
||||
margin: 0 -100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#main-table th p:before {
|
||||
content: '';
|
||||
width: 0;
|
||||
padding-top: 110%;
|
||||
/* takes width as reference, + 10% for faking some extra padding */
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#main-table td,
|
||||
th {
|
||||
max-width: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#main-table tr {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#main-table tr:hover {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
#main-table tr:hover .unavailable {
|
||||
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
.Sat {
|
||||
/* opacity: 30%; */
|
||||
/* background-color: lightsteelblue; */
|
||||
border-left: solid 1px lightsteelblue;
|
||||
}
|
||||
|
||||
.Sun {
|
||||
border-right: solid 1px lightsteelblue;
|
||||
|
||||
}
|
||||
|
||||
.bank-holiday {
|
||||
border-bottom: dotted 1px blue;
|
||||
|
||||
}
|
||||
|
||||
#main-table th.bank-holiday {
|
||||
color: darkblue;
|
||||
|
||||
}
|
||||
|
||||
.plymouth {
|
||||
color: #9169FF;
|
||||
}
|
||||
|
||||
.exeter {
|
||||
color: #5F60E8;
|
||||
}
|
||||
|
||||
.truro {
|
||||
color: #0074D9;
|
||||
}
|
||||
|
||||
.torquay,
|
||||
.torbay {
|
||||
color: #5FA8E8;
|
||||
}
|
||||
|
||||
.taunton {
|
||||
color: #e85fdf;
|
||||
}
|
||||
|
||||
|
||||
.unavailable {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.shift-requested {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.worker-summary {
|
||||
position: absolute;
|
||||
display: none;
|
||||
overflow-x: visible;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.site-summary {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.site-summary .site {
|
||||
border: 1px solid black;
|
||||
padding: 5px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.worker-summary span {
|
||||
min-width: 160px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.training-days-lost {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.tdl-settings {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.table-div+pre {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shift-breakdown-button+pre {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shift-breakdown-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nwd {
|
||||
color: lightcoral;
|
||||
}
|
||||
|
||||
#main-table th.worker,
|
||||
#main-table td.worker {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
left: 0;
|
||||
top: auto;
|
||||
border-top-width: 1px;
|
||||
/*only relevant for first row*/
|
||||
margin-top: -1px;
|
||||
/*compensate for top border*/
|
||||
}
|
||||
|
||||
.hidden {
|
||||
opacity: 0%;
|
||||
}
|
||||
|
||||
.tsummary {
|
||||
max-width: auto;
|
||||
}
|
||||
|
||||
#main-table.header th {
|
||||
max-width: 200px;
|
||||
|
||||
}
|
||||
|
||||
table.transposed {}
|
||||
|
||||
table.transposed tr {}
|
||||
|
||||
table.transposed th,
|
||||
table.transposed td {
|
||||
width: fit-content;
|
||||
max-width: unset;
|
||||
position: relative;
|
||||
border-top: solid 0.5px black;
|
||||
border-right: solid 0.5px black;
|
||||
}
|
||||
|
||||
table.transposed .Sat {
|
||||
/* opacity: 30%; */
|
||||
/* background-color: lightsteelblue; */
|
||||
border-top: solid 2px lightsteelblue !important;
|
||||
}
|
||||
|
||||
table.transposed .Sun {
|
||||
border-bottom: solid 2px lightsteelblue !important;
|
||||
|
||||
}
|
||||
|
||||
table.transposed .bank-holiday {
|
||||
border-bottom: dotted 2px blue !important;
|
||||
|
||||
}
|
||||
|
||||
table.transposed th.bank-holiday {
|
||||
color: darkblue;
|
||||
|
||||
}
|
||||
|
||||
.target-assigned.no-colour {
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
.locum-shift {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
.multi-shift {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
button.selected {
|
||||
background: #0074d9;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#main-table th.col-hover {
|
||||
background: #ffe066 !important;
|
||||
color: #222 !important;
|
||||
}
|
||||
|
||||
.highlight-day-btn.highlighted {
|
||||
background: #ffe066 !important;
|
||||
color: #222 !important;
|
||||
border: 2px solid #bfa600 !important;
|
||||
}
|
||||
|
||||
.highlight-day-btn.highlighted {
|
||||
background: #ffe066 !important;
|
||||
color: #222 !important;
|
||||
border: 2px solid #bfa600 !important;
|
||||
}
|
||||
|
||||
#main-table td.day-highlighted,
|
||||
#main-table th.day-highlighted {
|
||||
/* background is set inline for custom color */
|
||||
}
|
||||
|
||||
#display-settings-modal {
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
#main-table td.force-assigned.force-assigned-highlight {
|
||||
background-color: #ffe066 !important;
|
||||
border: 2px solid #ff8800 !important;
|
||||
}
|
||||
|
||||
#linear-shift-timetable td, #linear-shift-timetable th {
|
||||
max-width: unset;
|
||||
}
|
||||
@@ -3,6 +3,14 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2 class="title">Request leave{% if worker %} for {{ worker.name }}{% endif %}</h2>
|
||||
|
||||
<div class="box">
|
||||
<h3 class="subtitle">Request leave on calendar</h3>
|
||||
{% include 'rota/partials/flatpickr_includes.html' %}
|
||||
<div id="calendar" class="calendar" data-worker-id="{% if worker %}{{ worker.id }}{% endif %}" data-mode="inline"></div>
|
||||
<p class="help">Click a start date then a second date to select a range and prefill the leave form.</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
@@ -13,4 +21,107 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.calendar { display:flex; flex-direction:column; gap:0.5rem; margin-bottom:1rem }
|
||||
.calendar .week { display:flex; gap:0.5rem; }
|
||||
.calendar .day { flex:1; padding:0.5rem; border:1px solid #eee; min-height:3rem; cursor:pointer; }
|
||||
.calendar .day.other-month { background:#f9f9f9; color:#999; }
|
||||
.calendar .day.leave { background:#ffecec; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const container = document.getElementById('calendar');
|
||||
if(!container) return;
|
||||
const workerId = container.dataset.workerId;
|
||||
const leaves = [
|
||||
{% if worker %}
|
||||
{% for l in worker.leaves.all %}
|
||||
{start:'{{ l.start_date }}', end:'{{ l.end_date }}'},
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
];
|
||||
|
||||
let viewYear = new Date().getFullYear();
|
||||
let viewMonth = new Date().getMonth();
|
||||
let selStart = null; let selEnd = null; let previewEnd = null;
|
||||
|
||||
function iso(y,m,d){ return `${y}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}` }
|
||||
function inLeaves(isoStr){ for(const L of leaves){ if(!L.start) continue; if(L.start<=isoStr && isoStr<=L.end) return true } return false }
|
||||
|
||||
function buildControls(){
|
||||
let controls = document.getElementById('calendar-controls');
|
||||
if(!controls){
|
||||
controls = document.createElement('div'); controls.id='calendar-controls'; controls.style.marginTop='0.5rem';
|
||||
controls.innerHTML = `<span id="sel-label">No dates selected</span> <button id="sel-confirm" class="button is-primary" style="margin-left:0.5rem;">Apply</button> <button id="sel-clear" class="button" style="margin-left:0.5rem;">Clear</button>`;
|
||||
container.parentNode.insertBefore(controls, container.nextSibling);
|
||||
document.getElementById('sel-clear').onclick = function(){ selStart = null; selEnd = null; previewEnd = null; render(); };
|
||||
document.getElementById('sel-confirm').onclick = function(){
|
||||
if(!selStart) return;
|
||||
if(!selEnd) selEnd = selStart;
|
||||
const startInput = document.querySelector('input[name="start_date"]');
|
||||
const endInput = document.querySelector('input[name="end_date"]');
|
||||
if(startInput) startInput.value = selStart;
|
||||
if(endInput) endInput.value = selEnd;
|
||||
if(typeof initFlatpickr === 'function') initFlatpickr();
|
||||
// scroll to form
|
||||
const form = document.querySelector('form'); if(form) form.scrollIntoView({behavior:'smooth'});
|
||||
};
|
||||
}
|
||||
const label = document.getElementById('sel-label');
|
||||
if(selStart && selEnd) label.textContent = `Selected: ${selStart} → ${selEnd}`;
|
||||
else if(selStart) label.textContent = `Start: ${selStart}`;
|
||||
else label.textContent = 'No dates selected';
|
||||
document.getElementById('sel-confirm').disabled = !selStart;
|
||||
}
|
||||
|
||||
function render(){
|
||||
container.innerHTML='';
|
||||
const first = new Date(viewYear, viewMonth, 1);
|
||||
const startDay = first.getDay();
|
||||
const days = new Date(viewYear, viewMonth+1, 0).getDate();
|
||||
let cur = 1 - startDay;
|
||||
const header = document.createElement('div'); header.style.display='flex'; header.style.justifyContent='space-between'; header.style.alignItems='center'; header.style.marginBottom='0.5rem';
|
||||
const title = document.createElement('div'); title.textContent = first.toLocaleString(undefined,{month:'long', year:'numeric'});
|
||||
const prev = document.createElement('button'); prev.textContent='◀'; prev.className='button'; prev.onclick=()=>{ viewMonth--; if(viewMonth<0){ viewMonth=11; viewYear--; } render(); };
|
||||
const next = document.createElement('button'); next.textContent='▶'; next.className='button'; next.onclick=()=>{ viewMonth++; if(viewMonth>11){ viewMonth=0; viewYear++; } render(); };
|
||||
header.appendChild(prev); header.appendChild(title); header.appendChild(next);
|
||||
container.appendChild(header);
|
||||
|
||||
while(cur<=days){
|
||||
const week = document.createElement('div'); week.className='week';
|
||||
for(let i=0;i<7;i++){
|
||||
const dayDiv = document.createElement('div'); dayDiv.className='day';
|
||||
if(cur<1 || cur>days){ dayDiv.classList.add('other-month'); dayDiv.textContent=''; }
|
||||
else{
|
||||
const isoStr = iso(viewYear, viewMonth, cur);
|
||||
dayDiv.textContent = cur;
|
||||
if(inLeaves(isoStr)) dayDiv.classList.add('leave');
|
||||
const rangeEnd = selEnd || previewEnd;
|
||||
if(selStart && rangeEnd){ if(selStart<=isoStr && isoStr<=rangeEnd) dayDiv.classList.add('in-range'); }
|
||||
else if(selStart && selStart===isoStr) dayDiv.classList.add('selected');
|
||||
dayDiv.dataset.iso = isoStr;
|
||||
dayDiv.onclick = function(){
|
||||
const clicked = this.dataset.iso;
|
||||
if(!selStart){ selStart = clicked; selEnd = null; }
|
||||
else if(selStart && !selEnd){ if(clicked < selStart){ selEnd = selStart; selStart = clicked } else selEnd = clicked }
|
||||
else { selStart = clicked; selEnd = null }
|
||||
previewEnd = null;
|
||||
render();
|
||||
};
|
||||
dayDiv.onmouseover = function(){ if(selStart && !selEnd){ previewEnd = this.dataset.iso; render(); } };
|
||||
dayDiv.onmouseout = function(){ if(selStart && !selEnd){ previewEnd = null; render(); } };
|
||||
}
|
||||
week.appendChild(dayDiv);
|
||||
cur++;
|
||||
}
|
||||
container.appendChild(week);
|
||||
}
|
||||
buildControls();
|
||||
}
|
||||
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
|
||||
{% include 'rota/partials/flatpickr_includes.html' %}
|
||||
<style>
|
||||
.calendar { display:flex; flex-direction:column; gap:0.5rem; }
|
||||
.calendar .week { display:flex; gap:0.5rem; }
|
||||
.calendar .day { flex:1; padding:0.5rem; border:1px solid #eee; min-height:3rem; cursor:pointer; }
|
||||
.calendar { max-width:900px; margin:0 auto; }
|
||||
.calendar .week { display:flex; gap:0.25rem; }
|
||||
.calendar .day { flex:1; padding:0.5rem; border:1px solid #eee; min-height:3rem; cursor:pointer; text-align:center }
|
||||
.calendar .day.other-month { background:#f9f9f9; color:#999; }
|
||||
.calendar .day.leave { background:#ffecec; }
|
||||
.calendar .day.selected { background:#cdeffd; }
|
||||
.calendar .day.in-range { background:#d6f5d6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -18,8 +20,8 @@
|
||||
<div class="container">
|
||||
<div class="box">
|
||||
<h2 class="subtitle">Request leave on calendar</h2>
|
||||
<div id="calendar" class="calendar" data-worker-id="{{ worker.id }}"></div>
|
||||
<p class="help">Click a date to request leave for that day.</p>
|
||||
<div id="calendar" data-worker-id="{{ worker.id }}"></div>
|
||||
<p class="help">Select a date or drag to select a range to request leave.</p>
|
||||
</div>
|
||||
<h1 class="title">{{ worker.name }}</h1>
|
||||
<p class="subtitle">{{ worker.email }} - {{ worker.site }}</p>
|
||||
@@ -51,66 +53,104 @@
|
||||
</section>
|
||||
<script>
|
||||
(function(){
|
||||
// Render a simple month calendar for current month; mark existing leaves
|
||||
const container = document.getElementById('calendar');
|
||||
if(!container) return;
|
||||
const workerId = container.dataset.workerId;
|
||||
const today = new Date();
|
||||
const year = today.getFullYear();
|
||||
const month = today.getMonth();
|
||||
|
||||
// Collect leave dates from template variable
|
||||
// collect existing leaves
|
||||
const leaves = [
|
||||
{% for l in worker.leaves.all %}
|
||||
{start:'{{ l.start_date }}', end:'{{ l.end_date }}'},
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
function dateInLeaves(y,m,d){
|
||||
const s = `${y}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
|
||||
const today = new Date();
|
||||
let viewYear = today.getFullYear();
|
||||
let viewMonth = today.getMonth();
|
||||
let selStart = null; // ISO string
|
||||
let selEnd = null;
|
||||
let previewEnd = null;
|
||||
|
||||
function inLeaves(iso){
|
||||
for(const L of leaves){
|
||||
if(!L.start) continue;
|
||||
if(L.start <= s && s <= L.end) return true;
|
||||
if(L.start <= iso && iso <= L.end) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderMonth(y, m){
|
||||
function iso(y,m,d){ return `${y}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}` }
|
||||
|
||||
function buildControls(){
|
||||
let controls = document.getElementById('calendar-controls');
|
||||
if(!controls){
|
||||
controls = document.createElement('div'); controls.id = 'calendar-controls'; controls.style.marginTop = '0.5rem';
|
||||
controls.innerHTML = `<span id="sel-label">No dates selected</span> <button id="sel-confirm" class="button is-primary" style="margin-left:0.5rem;">Confirm</button> <button id="sel-clear" class="button" style="margin-left:0.5rem;">Clear</button>`;
|
||||
container.parentNode.insertBefore(controls, container.nextSibling);
|
||||
document.getElementById('sel-clear').onclick = function(){ selStart = null; selEnd = null; previewEnd = null; render(); };
|
||||
document.getElementById('sel-confirm').onclick = function(){
|
||||
if(!selStart) return;
|
||||
if(!selEnd) selEnd = selStart;
|
||||
const url = '{% url "rota:request_leave" %}' + '?worker_id=' + workerId + '&start_date=' + selStart + '&end_date=' + selEnd;
|
||||
try{ htmx.ajax('GET', url, {target:'#modal', swap:'innerHTML'}); }catch(e){ window.location = url; }
|
||||
};
|
||||
}
|
||||
const label = document.getElementById('sel-label');
|
||||
if(selStart && selEnd) label.textContent = `Selected: ${selStart} → ${selEnd}`;
|
||||
else if(selStart) label.textContent = `Start: ${selStart}`;
|
||||
else label.textContent = 'No dates selected';
|
||||
document.getElementById('sel-confirm').disabled = !selStart;
|
||||
}
|
||||
|
||||
function render(){
|
||||
container.innerHTML='';
|
||||
const first = new Date(y,m,1);
|
||||
const startDay = first.getDay(); // 0 Sun..6 Sat
|
||||
const days = new Date(y, m+1, 0).getDate();
|
||||
let cur = 1 - startDay; // offset to start week
|
||||
// header
|
||||
const first = new Date(viewYear, viewMonth, 1);
|
||||
const startDay = first.getDay();
|
||||
const days = new Date(viewYear, viewMonth+1, 0).getDate();
|
||||
let cur = 1 - startDay;
|
||||
|
||||
const header = document.createElement('div'); header.style.display='flex'; header.style.justifyContent='space-between'; header.style.alignItems='center'; header.style.marginBottom='0.5rem';
|
||||
const title = document.createElement('div'); title.textContent = first.toLocaleString(undefined,{month:'long', year:'numeric'}); header.appendChild(title);
|
||||
const title = document.createElement('div'); title.textContent = first.toLocaleString(undefined,{month:'long', year:'numeric'});
|
||||
const prev = document.createElement('button'); prev.textContent='◀'; prev.className='button'; prev.onclick = ()=>{ viewMonth--; if(viewMonth<0){ viewMonth=11; viewYear--; } render(); };
|
||||
const next = document.createElement('button'); next.textContent='▶'; next.className='button'; next.onclick = ()=>{ viewMonth++; if(viewMonth>11){ viewMonth=0; viewYear++; } render(); };
|
||||
header.appendChild(prev); header.appendChild(title); header.appendChild(next);
|
||||
container.appendChild(header);
|
||||
|
||||
while(cur <= days){
|
||||
const week = document.createElement('div'); week.className='week';
|
||||
for(let i=0;i<7;i++){
|
||||
const d = cur;
|
||||
const dayDiv = document.createElement('div'); dayDiv.className='day';
|
||||
const day = cur;
|
||||
if(day < 1 || day > days){ dayDiv.classList.add('other-month'); dayDiv.textContent=''; }
|
||||
if(d<1 || d>days){ dayDiv.classList.add('other-month'); dayDiv.textContent=''; }
|
||||
else{
|
||||
dayDiv.textContent = day;
|
||||
const inLeave = dateInLeaves(y,m,day);
|
||||
if(inLeave) dayDiv.classList.add('leave');
|
||||
// clicking opens leave request modal prefilled for that date
|
||||
const dayIso = iso(viewYear, viewMonth, d);
|
||||
dayDiv.textContent = d;
|
||||
if(inLeaves(dayIso)) dayDiv.classList.add('leave');
|
||||
// determine visible selection range: prefer explicit selEnd, else previewEnd
|
||||
const rangeEnd = selEnd || previewEnd;
|
||||
if(selStart && rangeEnd){ if(selStart<=dayIso && dayIso<=rangeEnd) dayDiv.classList.add('in-range'); }
|
||||
else if(selStart && selStart===dayIso) dayDiv.classList.add('selected');
|
||||
dayDiv.dataset.iso = dayIso;
|
||||
dayDiv.onclick = function(){
|
||||
const dateStr = `${y}-${String(m+1).padStart(2,'0')}-${String(day).padStart(2,'0')}`;
|
||||
const url = '{% url "rota:request_leave" %}' + '?worker_id=' + workerId + '&date=' + dateStr;
|
||||
// HTMX GET into #modal
|
||||
try{ htmx.ajax('GET', url, {target:'#modal', swap:'innerHTML'}); }catch(e){ window.location = url; }
|
||||
const clicked = this.dataset.iso;
|
||||
if(!selStart){ selStart = clicked; selEnd = null; }
|
||||
else if(selStart && !selEnd){ if(clicked < selStart){ selEnd = selStart; selStart = clicked; } else selEnd = clicked; }
|
||||
else { selStart = clicked; selEnd = null; }
|
||||
previewEnd = null;
|
||||
render();
|
||||
};
|
||||
dayDiv.onmouseover = function(){ if(selStart && !selEnd){ previewEnd = this.dataset.iso; render(); } };
|
||||
dayDiv.onmouseout = function(){ if(selStart && !selEnd){ previewEnd = null; render(); } };
|
||||
}
|
||||
week.appendChild(dayDiv);
|
||||
cur++;
|
||||
}
|
||||
container.appendChild(week);
|
||||
}
|
||||
buildControls();
|
||||
}
|
||||
|
||||
renderMonth(year, month);
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -515,7 +515,34 @@ def request_leave(request, token=None):
|
||||
return response
|
||||
return redirect("rota:worker_detail", worker_id=worker.id)
|
||||
else:
|
||||
form = LeaveForm()
|
||||
# Prefill dates when provided via query params for both full page and HTMX modal
|
||||
date_q = request.GET.get("date") or request.POST.get("date")
|
||||
start_q = request.GET.get("start_date") or request.POST.get("start_date")
|
||||
end_q = request.GET.get("end_date") or request.POST.get("end_date")
|
||||
if date_q:
|
||||
try:
|
||||
import datetime
|
||||
|
||||
d = datetime.date.fromisoformat(date_q)
|
||||
form = LeaveForm(initial={"start_date": d, "end_date": d})
|
||||
except Exception:
|
||||
form = LeaveForm()
|
||||
elif start_q or end_q:
|
||||
try:
|
||||
import datetime
|
||||
|
||||
sd = datetime.date.fromisoformat(start_q) if start_q else None
|
||||
ed = datetime.date.fromisoformat(end_q) if end_q else None
|
||||
init = {}
|
||||
if sd:
|
||||
init["start_date"] = sd
|
||||
if ed:
|
||||
init["end_date"] = ed
|
||||
form = LeaveForm(initial=init)
|
||||
except Exception:
|
||||
form = LeaveForm()
|
||||
else:
|
||||
form = LeaveForm()
|
||||
|
||||
# Render modal or full page
|
||||
if is_hx:
|
||||
|
||||
Reference in New Issue
Block a user