diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c24e0a..72e23e23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **Datetime fields ignore the chosen time format** — Native `datetime-local` inputs (the "Forgot to end your workday?" workday modals, workday history corrections, contact/deal/lead activity forms) render in the browser locale — e.g. 12h AM/PM on en-US — no matter what time format the user or system settings chose. They are now initialized as Flatpickr datetime pickers that display in the user's preferred date + time format (24h by default) while still submitting the same wire format; min/max bounds move to the picker. A regression test enforces that every `datetime-local` input stays prefs-aware. The recurring-tasks "last run" cell and the admin version-update published timestamp also now honor the preference instead of the browser locale. + ## [5.13.2] - 2026-08-27 ### Added diff --git a/app/routes/reports.py b/app/routes/reports.py index ad49af33..e8865eac 100644 --- a/app/routes/reports.py +++ b/app/routes/reports.py @@ -2113,4 +2113,3 @@ def utilization_report(): non_billable_hours=non_billable, can_view_all=can_view_all, ) - diff --git a/app/static/admin-version-update.js b/app/static/admin-version-update.js index a0653824..f278d039 100644 --- a/app/static/admin-version-update.js +++ b/app/static/admin-version-update.js @@ -87,10 +87,12 @@ if (data.published_at) { try { var d = new Date(data.published_at); - published.textContent = d.toLocaleString(undefined, { - dateStyle: "medium", - timeStyle: "short", - }); + published.textContent = window.formatUserDateTime + ? window.formatUserDateTime(d) + : d.toLocaleString(undefined, { + dateStyle: "medium", + timeStyle: "short", + }); } catch (e) { published.textContent = data.published_at; } diff --git a/app/static/date-picker-init.js b/app/static/date-picker-init.js index 24aefa95..b2b0d214 100644 --- a/app/static/date-picker-init.js +++ b/app/static/date-picker-init.js @@ -18,6 +18,19 @@ } } + function getDateTimeAltFormat() { + return getFlatpickrAltFormat() + ' ' + getTimeAltFormat(); + } + + /** + * Parse a wire datetime-local value ("YYYY-MM-DDTHH:MM") into a Date. + */ + function parseWireDateTime(value) { + if (!value) return undefined; + var d = new Date(value); + return isNaN(d.getTime()) ? undefined : d; + } + function getFirstDayOfWeek() { if (window.userPrefs && typeof window.userPrefs.weekStartDay === 'number' && window.userPrefs.weekStartDay >= 0 && window.userPrefs.weekStartDay <= 6) { return window.userPrefs.weekStartDay; @@ -194,9 +207,53 @@ }); } + /** + * Initialize Flatpickr on datetime-local inputs so they display using the + * user's preferred date + time formats while still submitting + * YYYY-MM-DDTHH:MM. Native datetime-local controls render in the browser + * locale (e.g. 12h AM/PM on en-US), ignoring the in-app preference. + */ + function initUserDateTimeInputs() { + if (typeof flatpickr === 'undefined') return; + var inputs = document.querySelectorAll('input.user-datetime-input[type="datetime-local"]'); + var altFormat = getDateTimeAltFormat(); + var use24hr = timePickerUses24hr(); + var firstDay = getFirstDayOfWeek(); + inputs.forEach(function (el) { + if (el._flatpickr) return; + // Preserve existing classes on the visible alt input (form-input, form-control, sizing). + var altClass = (el.className || 'form-input').replace(/\buser-datetime-input\b/g, '').trim() || 'form-input'; + var minDate = parseWireDateTime(el.getAttribute('min')); + var maxDate = parseWireDateTime(el.getAttribute('max')); + // Bounds move to Flatpickr; native min/max on the (now hidden) wire + // input would still block submission with browser validation. + el.removeAttribute('min'); + el.removeAttribute('max'); + flatpickr(el, { + enableTime: true, + dateFormat: 'Y-m-dTH:i', + time_24hr: use24hr, + altInput: true, + altFormat: altFormat, + altInputClass: altClass, + allowInput: false, + minDate: minDate, + maxDate: maxDate, + locale: { firstDayOfWeek: firstDay }, + // type=datetime-local fights Flatpickr; hide the native control. + onReady: function (_selectedDates, _dateStr, instance) { + if (instance.input) { + instance.input.style.display = 'none'; + } + } + }); + }); + } + function initAll() { initUserDateInputs(); initUserTimeInputs(); + initUserDateTimeInputs(); } // Test / debug hooks diff --git a/app/templates/client_portal/quote_detail.html b/app/templates/client_portal/quote_detail.html index 5d25ae78..bcbeac3b 100644 --- a/app/templates/client_portal/quote_detail.html +++ b/app/templates/client_portal/quote_detail.html @@ -169,7 +169,7 @@

{{ _('Sign & Accept') }}

-