diff --git a/shopfloor_mobile_base/static/wms/src/components/datepicker/date_picker.js b/shopfloor_mobile_base/static/wms/src/components/datepicker/date_picker.js
index 5cc16c00ec9..670be580988 100644
--- a/shopfloor_mobile_base/static/wms/src/components/datepicker/date_picker.js
+++ b/shopfloor_mobile_base/static/wms/src/components/datepicker/date_picker.js
@@ -1,24 +1,96 @@
/**
* Copyright 2022 Camptocamp SA (http://www.camptocamp.com)
+ * Copyright 2025 ACSONE SA/NV (https://www.acsone.com)
* License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
*/
import event_hub from "../../services/event_hub.js";
+function getFormatAndMask(locale) {
+ const sample = new Date(2025, 8, 2); // 2 septembre 2025
+ const parts = new Intl.DateTimeFormat(locale).formatToParts(sample);
+
+ let format = "";
+ let mask = "";
+
+ for (const p of parts) {
+ if (p.type === "day") {
+ format += "dd";
+ mask += "##";
+ } else if (p.type === "month") {
+ format += "MM";
+ mask += "##";
+ } else if (p.type === "year") {
+ format += "yyyy";
+ mask += "####";
+ } else if (p.type === "literal") {
+ format += p.value;
+ mask += p.value;
+ }
+ }
+ return {format, mask};
+}
+
+Vue.directive("date-mask", {
+ bind(el, binding) {
+ const locale = binding.value || navigator.language || "en-US";
+ const {format, mask} = getFormatAndMask(locale);
+
+ el.addEventListener("input", (e) => {
+ let v = e.target.value.replace(/\D/g, ""); // Only digits
+ if (v.length > 8) v = v.slice(0, 8);
+
+ // Detects separator (first non # in the mask)
+ const sep = mask.match(/[^#]/)?.[0] || "/";
+ const order = format.split(sep); // Ex: ["dd","MM","yyyy"]
+
+ const chunks = [];
+ let cursor = 0;
+
+ order.forEach((token) => {
+ const size = token === "yyyy" ? 4 : 2;
+ if (v.length >= cursor + size) {
+ chunks.push(v.slice(cursor, cursor + size));
+ } else if (v.length > cursor) {
+ chunks.push(v.slice(cursor));
+ }
+ cursor += size;
+ });
+
+ e.target.value = chunks.join(sep);
+
+ // Propagate to v-model
+ e.target.dispatchEvent(new Event("input"));
+ });
+ },
+});
+
export var DatePicker = Vue.component("date-picker-input", {
props: {
- // Method passed from the parent to update the picker's date
- // from outside as required.
handler_to_update_date: Function,
+ locale: {
+ type: String,
+ default: () => navigator.language || "en-US",
+ },
},
- data: function () {
+ data() {
+ const {format, mask} = getFormatAndMask(this.locale);
return {
- date: "",
+ date: "", // Iso format (YYYY-MM-DD)
+ dateInput: "", // Formatted display (DD/MM/YYYY or similar according to locale)
+ menu: false,
+ format,
+ mask,
};
},
watch: {
- date: function () {
- this.$emit("date_picker_selected", this.date);
+ date(newVal) {
+ if (newVal) {
+ this.dateInput = this.formatISOToInput(newVal);
+ } else {
+ this.dateInput = "";
+ }
+ this.$emit("date_picker_selected", newVal);
},
},
mounted() {
@@ -26,25 +98,75 @@ export var DatePicker = Vue.component("date-picker-input", {
this.date = this.handler_to_update_date(data);
});
},
+ methods: {
+ onEnter(event) {
+ event.preventDefault();
+ this.validateInput();
+ },
+
+ validateInput() {
+ if (!this.dateInput) {
+ this.date = "";
+ return;
+ }
+ const parts = new Intl.DateTimeFormat(this.locale)
+ .formatToParts(new Date(2025, 8, 2)) // Model
+ .filter((p) => ["day", "month", "year"].includes(p.type))
+ .map((p) => p.type);
+
+ const sep = this.dateInput.match(/\D/)[0];
+ const values = this.dateInput.split(sep);
+
+ let day = null,
+ month = null,
+ year = null;
+ parts.forEach((token, i) => {
+ if (token === "day") day = values[i];
+ if (token === "month") month = values[i];
+ if (token === "year") year = values[i];
+ });
+
+ if (year.length === 2) {
+ year = String(2000 + parseInt(year, 10));
+ }
+
+ const parsed = new Date(`${year}-${month}-${day}`);
+ if (!isNaN(parsed.getTime())) {
+ this.date = parsed.toISOString().substr(0, 10);
+ }
+ },
+
+ formatISOToInput(iso) {
+ if (!iso) return "";
+ const d = new Date(iso);
+ return new Intl.DateTimeFormat(this.locale).format(d);
+ },
+ },
template: `
-
-
-
-
-
-
- `,
+
+
+
+
+
+
+ `,
});