feat(fusion_clock): multi-day leave requests (date range)

Request Leave now takes a From/To date range instead of a single day (the To
field is optional -> single-day). Added date_to to fusion.clock.leave.request
(start kept as leave_date), with overlap detection on submit and a date_to >=
leave_date constraint. The absence check and reports now treat a leave as
covering its whole span. The form shows two date inputs; the controller accepts
date_from/date_to (the old single leave_date payload is still honoured). A
migration backfills date_to = leave_date for existing rows.

Live and verified on entech 19.0.3.13.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-30 23:01:19 -04:00
parent 87639a12b5
commit 6a9c7c74ea
10 changed files with 128 additions and 27 deletions

View File

@@ -1319,6 +1319,33 @@ html.o_dark .fclk-wizard-overlay {
text-decoration: underline;
}
/* Leave request: From / To date-range row */
.fclk-leave-daterange {
display: flex;
gap: 10px;
}
.fclk-leave-daterange-col {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.fclk-leave-daterange-col .fclk-wizard-input {
width: 100%;
}
.fclk-leave-daterange-cap {
font-size: 11px;
color: var(--fclk-text-muted);
text-transform: uppercase;
letter-spacing: 0.4px;
}
.fclk-leave-daterange-cap small {
text-transform: none;
letter-spacing: 0;
color: var(--fclk-text-dim);
}
/* ---- Reports Page ---- */
.fclk-reports-container {
max-width: 600px;

View File

@@ -655,26 +655,34 @@ export class FusionClockPortal extends Interaction {
}
async _submitLeave() {
const dateEl = document.getElementById("fclk-leave-date");
const fromEl = document.getElementById("fclk-leave-date");
const toEl = document.getElementById("fclk-leave-date-to");
const reasonEl = document.getElementById("fclk-leave-reason");
const leaveDate = dateEl ? dateEl.value : "";
const dateFrom = fromEl ? fromEl.value : "";
let dateTo = toEl && toEl.value ? toEl.value : dateFrom; // single day if "To" left blank
const reason = reasonEl ? reasonEl.value.trim() : "";
if (!leaveDate || !reason) {
this._showToast("Please provide both a date and reason.", "error");
if (!dateFrom || !reason) {
this._showToast("Please provide a start date and a reason.", "error");
return;
}
if (dateTo < dateFrom) {
this._showToast("End date can't be before the start date.", "error");
return;
}
try {
const result = await rpc("/fusion_clock/request_leave", {
leave_date: leaveDate,
date_from: dateFrom,
date_to: dateTo,
reason: reason,
});
if (result.success) {
const modal = document.getElementById("fclk-leave-modal");
if (modal) modal.style.display = "none";
this._showToast(result.message, "success");
if (dateEl) dateEl.value = "";
if (fromEl) fromEl.value = "";
if (toEl) toEl.value = "";
if (reasonEl) reasonEl.value = "";
} else {
this._showToast(result.error || "Failed to submit.", "error");