From f7caeaf90743a1d4fadd2a13e2eabb02141f6de7 Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:18:18 +0200 Subject: [PATCH] A date typed in the order you chose, not the browser's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings › Date format steered every date the app printed, but not one it asked for: `` renders in the browser's own locale and no page setting can move it, so DD-MM-YYYY tables sat above 08/22/2026 boxes. DateField takes over the typing half — a masked box whose segment order comes from the same prefs.dateFormat lib/format.js reads — and leaves the picking half to the browser, behind a calendar button. The value in and out stays ISO, so no caller changed. Native validation now also catches a full-but-impossible date; the old input let a half-typed one through as no date at all. Co-Authored-By: Claude Opus 5 --- Web App/web/src/components/CarFormModal.vue | 3 +- .../web/src/components/ChargingFormModal.vue | 3 +- Web App/web/src/components/DateField.vue | 232 ++++++++++++++++++ .../web/src/components/DocumentFormModal.vue | 5 +- Web App/web/src/components/FuelFormModal.vue | 3 +- .../src/components/MaintenanceFormModal.vue | 5 +- .../web/src/components/PartialDateField.vue | 23 +- .../web/src/components/ReminderFormModal.vue | 3 +- .../web/src/components/ServiceFormModal.vue | 3 +- .../components/TechnicalCheckFormModal.vue | 5 +- Web App/web/src/i18n/da.json | 7 +- Web App/web/src/i18n/en.json | 7 +- Web App/web/src/i18n/pl.json | 7 +- Web App/web/src/style.css | 37 +++ 14 files changed, 317 insertions(+), 26 deletions(-) create mode 100644 Web App/web/src/components/DateField.vue diff --git a/Web App/web/src/components/CarFormModal.vue b/Web App/web/src/components/CarFormModal.vue index 3c74515..9687bc7 100644 --- a/Web App/web/src/components/CarFormModal.vue +++ b/Web App/web/src/components/CarFormModal.vue @@ -2,6 +2,7 @@ import { ref } from "vue"; import { api } from "../api"; import { t } from "../i18n"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; import PartialDateField from "./PartialDateField.vue"; @@ -126,7 +127,7 @@ async function submit() {
- +
diff --git a/Web App/web/src/components/ChargingFormModal.vue b/Web App/web/src/components/ChargingFormModal.vue index 2cb1e61..4669693 100644 --- a/Web App/web/src/components/ChargingFormModal.vue +++ b/Web App/web/src/components/ChargingFormModal.vue @@ -8,6 +8,7 @@ import { api } from "../api"; import { applyAttachment } from "../lib/attachment.js"; import { t, tSplit } from "../i18n"; import AttachmentField from "./AttachmentField.vue"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; const props = defineProps({ @@ -88,7 +89,7 @@ function payload() {
- +
diff --git a/Web App/web/src/components/DateField.vue b/Web App/web/src/components/DateField.vue new file mode 100644 index 0000000..20344fb --- /dev/null +++ b/Web App/web/src/components/DateField.vue @@ -0,0 +1,232 @@ + + + diff --git a/Web App/web/src/components/DocumentFormModal.vue b/Web App/web/src/components/DocumentFormModal.vue index 1da86cf..6459617 100644 --- a/Web App/web/src/components/DocumentFormModal.vue +++ b/Web App/web/src/components/DocumentFormModal.vue @@ -4,6 +4,7 @@ import { api } from "../api"; import { applyAttachment } from "../lib/attachment.js"; import { t } from "../i18n"; import AttachmentField from "./AttachmentField.vue"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; const props = defineProps({ @@ -100,11 +101,11 @@ function payload() {
- +
- +

{{ t("forms.document.renewalHint") }}

diff --git a/Web App/web/src/components/FuelFormModal.vue b/Web App/web/src/components/FuelFormModal.vue index b0cd415..7469c46 100644 --- a/Web App/web/src/components/FuelFormModal.vue +++ b/Web App/web/src/components/FuelFormModal.vue @@ -4,6 +4,7 @@ import { api } from "../api"; import { applyAttachment } from "../lib/attachment.js"; import { t, tSplit } from "../i18n"; import AttachmentField from "./AttachmentField.vue"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; const props = defineProps({ @@ -82,7 +83,7 @@ function payload() {
- +
diff --git a/Web App/web/src/components/MaintenanceFormModal.vue b/Web App/web/src/components/MaintenanceFormModal.vue index 9d293a9..c0fff6f 100644 --- a/Web App/web/src/components/MaintenanceFormModal.vue +++ b/Web App/web/src/components/MaintenanceFormModal.vue @@ -5,6 +5,7 @@ import { formatMoney } from "../lib/format.js"; import { applyAttachment } from "../lib/attachment.js"; import { t, tSplit } from "../i18n"; import AttachmentField from "./AttachmentField.vue"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; const props = defineProps({ @@ -94,7 +95,7 @@ function payload() {
- +
@@ -161,7 +162,7 @@ function payload() {
- +
diff --git a/Web App/web/src/components/PartialDateField.vue b/Web App/web/src/components/PartialDateField.vue index c7e00ae..2d726c5 100644 --- a/Web App/web/src/components/PartialDateField.vue +++ b/Web App/web/src/components/PartialDateField.vue @@ -11,6 +11,7 @@ // prints back at exactly the precision it was given. import { ref, watch } from "vue"; import { t } from "../i18n"; +import DateField from "./DateField.vue"; const props = defineProps({ modelValue: { type: String, default: "" } }); const emit = defineEmits(["update:modelValue"]); @@ -74,21 +75,19 @@ watch(year, (value) => { - - + -
- +
diff --git a/Web App/web/src/components/ServiceFormModal.vue b/Web App/web/src/components/ServiceFormModal.vue index dbed732..199e975 100644 --- a/Web App/web/src/components/ServiceFormModal.vue +++ b/Web App/web/src/components/ServiceFormModal.vue @@ -6,6 +6,7 @@ import { applyAttachment } from "../lib/attachment.js"; import { SERVICE_PARTS } from "../lib/serviceParts.js"; import { t } from "../i18n"; import AttachmentField from "./AttachmentField.vue"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; const props = defineProps({ @@ -74,7 +75,7 @@ async function submit() {
- +
diff --git a/Web App/web/src/components/TechnicalCheckFormModal.vue b/Web App/web/src/components/TechnicalCheckFormModal.vue index a47b553..0bae9c1 100644 --- a/Web App/web/src/components/TechnicalCheckFormModal.vue +++ b/Web App/web/src/components/TechnicalCheckFormModal.vue @@ -5,6 +5,7 @@ import { formatDate } from "../lib/format.js"; import { applyAttachment } from "../lib/attachment.js"; import { t, tSplit } from "../i18n"; import AttachmentField from "./AttachmentField.vue"; +import DateField from "./DateField.vue"; import Modal from "./Modal.vue"; const props = defineProps({ @@ -82,7 +83,7 @@ async function submit() {
- +
@@ -95,7 +96,7 @@ async function submit() {
- +

{{ t("forms.technical.failedHint") }}

diff --git a/Web App/web/src/i18n/da.json b/Web App/web/src/i18n/da.json index bccdfda..45911b0 100644 --- a/Web App/web/src/i18n/da.json +++ b/Web App/web/src/i18n/da.json @@ -19,7 +19,8 @@ "download": "Download", "empty": "—", "yes": "Ja", - "no": "Nej" + "no": "Nej", + "dateUnits": { "y": "åååå", "m": "mm", "d": "dd" } }, "nav": { @@ -643,6 +644,10 @@ }, "forms": { + "common": { + "pickDate": "Åbn kalender", + "dateInvalid": "Indtast en gyldig dato i dette format." + }, "car": { "addTitle": "Tilføj en bil", "editTitle": "Rediger bil", diff --git a/Web App/web/src/i18n/en.json b/Web App/web/src/i18n/en.json index 02182e4..c452542 100644 --- a/Web App/web/src/i18n/en.json +++ b/Web App/web/src/i18n/en.json @@ -19,7 +19,8 @@ "download": "Download", "empty": "—", "yes": "Yes", - "no": "No" + "no": "No", + "dateUnits": { "y": "yyyy", "m": "mm", "d": "dd" } }, "nav": { @@ -642,6 +643,10 @@ }, "forms": { + "common": { + "pickDate": "Open calendar", + "dateInvalid": "Enter a real date in this format." + }, "car": { "addTitle": "Add a car", "editTitle": "Edit car", diff --git a/Web App/web/src/i18n/pl.json b/Web App/web/src/i18n/pl.json index 6609ceb..cb7382b 100644 --- a/Web App/web/src/i18n/pl.json +++ b/Web App/web/src/i18n/pl.json @@ -19,7 +19,8 @@ "download": "Pobierz", "empty": "—", "yes": "Tak", - "no": "Nie" + "no": "Nie", + "dateUnits": { "y": "rrrr", "m": "mm", "d": "dd" } }, "nav": { @@ -657,6 +658,10 @@ }, "forms": { + "common": { + "pickDate": "Otwórz kalendarz", + "dateInvalid": "Wpisz istniejącą datę w tym formacie." + }, "car": { "addTitle": "Dodaj samochód", "editTitle": "Edytuj samochód", diff --git a/Web App/web/src/style.css b/Web App/web/src/style.css index 45557ce..d5f90ba 100644 --- a/Web App/web/src/style.css +++ b/Web App/web/src/style.css @@ -324,6 +324,43 @@ body { box-shadow: var(--shadow-focus); } + /* DateField: the browser's picker parked out of sight behind the calendar + button, so the visible box can keep the user's own segment order. Not + display:none — showPicker() needs a rendered element to open against. */ + .dv-date-picker { + position: absolute; + right: 0.5rem; + bottom: 0; + width: 1px; + height: 1px; + padding: 0; + border: 0; + opacity: 0; + pointer-events: none; + } + .dv-date-open { + position: absolute; + top: 50%; + right: 0.25rem; + transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; + padding: 0.25rem; + border-radius: var(--radius-control); + color: var(--text-muted); + transition: color 0.15s, background 0.15s; + } + .dv-date-open:hover { + color: var(--text-strong); + background: var(--surface-sunken); + } + .dv-date-open:focus-visible { + outline: none; + color: var(--text-strong); + box-shadow: var(--shadow-focus); + } + .dh-label { display: block; margin-bottom: 0.375rem;