@@ -53,6 +53,7 @@ import IconLucideFactory from '~icons/lucide/factory'
import IconLucideMapPin from '~icons/lucide/map-pin'
import IconLucidePhone from '~icons/lucide/phone'
import IconLucideUser from '~icons/lucide/user'
+import { formatPhone } from '~/utils/formatters/phone'
const props = defineProps({
site: {
@@ -64,4 +65,9 @@ const props = defineProps({
const emit = defineEmits(['edit', 'delete'])
const machineCount = computed(() => props.site?.machines?.length || 0)
+const formattedContactPhone = computed(() => {
+ const value = props.site?.contactPhone ?? ''
+ const formatted = formatPhone(value)
+ return formatted || value || '—'
+})
diff --git a/app/pages/constructeurs.vue b/app/pages/constructeurs.vue
index bb68f54..f9c24a6 100644
--- a/app/pages/constructeurs.vue
+++ b/app/pages/constructeurs.vue
@@ -69,7 +69,7 @@
| {{ constructeur.name }} |
{{ constructeur.email || '—' }} |
- {{ constructeur.phone || '—' }} |
+ {{ formatPhoneDisplay(constructeur.phone) }} |
{
}, 0)
})
+const formatPhoneDisplay = (value) => {
+ const formatted = formatPhone(value)
+ if (formatted) {
+ return formatted
+ }
+ return value || '—'
+}
+
const filteredSites = computed(() => {
let filtered = sites.value
diff --git a/app/shared/constructeurUtils.ts b/app/shared/constructeurUtils.ts
index e9adb6b..02649ed 100644
--- a/app/shared/constructeurUtils.ts
+++ b/app/shared/constructeurUtils.ts
@@ -1,3 +1,5 @@
+import { formatPhone } from '~/utils/formatters/phone';
+
export interface ConstructeurSummary {
id: string;
name?: string | null;
@@ -92,8 +94,16 @@ export const resolveConstructeurs = (
export const formatConstructeurContact = (
constructeur?: ConstructeurSummary | null,
-): string =>
- [constructeur?.email, constructeur?.phone].filter(Boolean).join(' • ');
+): string => {
+ if (!constructeur) {
+ return '';
+ }
+
+ const formattedPhone = formatPhone(constructeur.phone);
+ const phone = formattedPhone || constructeur.phone || null;
+
+ return [constructeur.email, phone].filter(Boolean).join(' • ');
+};
export const buildConstructeurRequestPayload = >(
payload: T,
diff --git a/app/shared/validation/email.ts b/app/shared/validation/email.ts
index 19b0f97..f2ea100 100644
--- a/app/shared/validation/email.ts
+++ b/app/shared/validation/email.ts
@@ -1,9 +1,9 @@
import { normalizeEmail } from '~/utils/formatters/email'
-export const EMAIL_INPUT_PATTERN = '[^\s@]+'
-
const EMAIL_VALIDATION_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+export const EMAIL_INPUT_PATTERN = EMAIL_VALIDATION_PATTERN.source
+
export type EmailValidationResult = {
valid: boolean
error?: string
diff --git a/app/shared/validation/phone.ts b/app/shared/validation/phone.ts
index a7577b2..b656b13 100644
--- a/app/shared/validation/phone.ts
+++ b/app/shared/validation/phone.ts
@@ -1,7 +1,7 @@
import { normalizePhone } from '~/utils/formatters/phone'
/** Pattern used for the HTML input `pattern` attribute on phone fields. */
-export const PHONE_INPUT_PATTERN = '[0-9+ ]*'
+export const PHONE_INPUT_PATTERN = '[0-9+ .]*'
const PHONE_VALIDATION_PATTERN = /^\+?\d{7,15}$/
diff --git a/app/utils/formatters/phone.ts b/app/utils/formatters/phone.ts
index 2eb4a27..fce3868 100644
--- a/app/utils/formatters/phone.ts
+++ b/app/utils/formatters/phone.ts
@@ -11,8 +11,8 @@ const PHONE_CHAR_PATTERN = /[^+\d]/g
* Normalises a phone number by trimming whitespace, removing spacing/separators and
* converting international prefixes written with `00` to their `+` variant.
*/
-export const normalizePhone = (rawValue: string): string => {
- const trimmed = (rawValue || '').trim()
+export const normalizePhone = (rawValue: string | null | undefined): string => {
+ const trimmed = typeof rawValue === 'string' ? rawValue.trim() : ''
if (!trimmed) {
return ''
}
@@ -26,30 +26,57 @@ export const normalizePhone = (rawValue: string): string => {
}
/**
- * Formats a phone number by grouping digits by two while keeping any international
- * prefix. The function remains tolerant to partially entered numbers.
+ * Formats a phone number by grouping digits by two and joining them with dots while
+ * keeping any international prefix. The function remains tolerant to partially
+ * entered numbers and returns an empty string for nullish inputs.
*/
-export const formatPhone = (rawValue: string): string => {
+export const formatPhone = (rawValue: string | null | undefined): string => {
+ if (rawValue == null) {
+ return ''
+ }
+
const normalized = normalizePhone(rawValue)
if (!normalized) {
return ''
}
+ if (normalized.startsWith('+33')) {
+ let nationalNumber = normalized.slice(3)
+ if (nationalNumber.startsWith('0')) {
+ nationalNumber = nationalNumber.slice(1)
+ }
+
+ if (nationalNumber.length % 2 !== 0) {
+ nationalNumber = `0${nationalNumber}`
+ }
+
+ const groups = nationalNumber.match(/\d{1,2}/g) ?? []
+ if (groups.length === 0) {
+ return '+33'
+ }
+
+ return ['+33', ...groups].join('.')
+ }
+
+ if (/^0\d{9}$/.test(normalized)) {
+ return normalized
+ }
+
const hasInternationalPrefix = normalized.startsWith('+')
const prefix = hasInternationalPrefix ? normalized.slice(0, 1) : ''
const digits = hasInternationalPrefix ? normalized.slice(1) : normalized
- const groups = digits.match(/.{1,2}/g) ?? []
- const grouped = groups.join(' ')
+ const groups = digits.match(/\d{1,2}/g) ?? []
+ const grouped = groups.join('.')
- return prefix ? `${prefix}${grouped ? ' ' : ''}${grouped}` : grouped
+ return prefix ? `${prefix}${grouped}` : grouped
}
/**
* Masks a phone number for display purposes by replacing the middle digits with ·.
* Useful for UI fragments where the full number should not be exposed.
*/
-export const maskPhone = (rawValue: string): string => {
+export const maskPhone = (rawValue: string | null | undefined): string => {
const normalized = normalizePhone(rawValue)
if (!normalized) {
return ''
diff --git a/app/utils/printTemplates/machineReport.js b/app/utils/printTemplates/machineReport.js
index 08f75ef..5e0077e 100644
--- a/app/utils/printTemplates/machineReport.js
+++ b/app/utils/printTemplates/machineReport.js
@@ -1,6 +1,7 @@
import {
uniqueConstructeurIds,
resolveConstructeurs,
+ formatConstructeurContact,
} from '~/shared/constructeurUtils'
const formatSize = (size) => {
@@ -202,10 +203,11 @@ const normalizeCustomFields = (values = []) => {
const normalizeConstructeur = (constructeur) => {
if (!constructeur) { return null }
+ const contact = formatConstructeurContact(constructeur)
return {
id: constructeur.id || null,
name: constructeur.name || '—',
- contact: [constructeur.email, constructeur.phone].filter(Boolean).join(' • ') || '—'
+ contact: contact || '—'
}
}
|