import { type ValidationRule as SyValidationRule } from '@/composables/validation/useValidation' import { computed, nextTick, onBeforeUnmount, onMounted, ref, type Ref } from 'vue' import { checkNIR, isNIRKeyValid } from './nirValidation' import { locales } from './locales' import { useValidation } from '@/composables/unifyValidation/useValidation' import type { SyTextField } from '@/main' import type { ValidationRule as VuetifyValidationRule } from 'vuetify' export type NirValidationProps = { customKeyRules?: SyValidationRule[] customKeyWarningRules?: SyValidationRule[] customNumberRules?: SyValidationRule[] customNumberWarningRules?: SyValidationRule[] customRulesPrecedence?: boolean disabled?: boolean isValidateOnBlur?: boolean keyLabel?: string keyRules?: VuetifyValidationRule[] nirType?: 'simple' | 'complexe' numberLabel?: string numberRules?: VuetifyValidationRule[] readonly?: boolean required?: boolean showSuccessMessages?: boolean useVuetifyValidation?: boolean } /** * Handle validation for NIR fields, including both the number and the key */ export function useNirValidation( numberValue: Ref, keyValue: Ref, unmaskedNumberValue: Ref, unmaskedKeyValue: Ref, readonly: Ref, disabled: Ref, required: Ref, numberField: Ref | null>, keyField: Ref | null>, customLocale: Ref, numberLabel: Ref, keyLabel: Ref, customNumberRules: Ref, customKeyRules: Ref, customNumberWarningRules: Ref, customKeyWarningRules: Ref, displayKey: Ref, customRulesPrecedence: Ref, nirType: Ref<'simple' | 'complexe'>, label: Ref, showSuccessMessages: Ref, disableErrorHandling: Ref, isValidateOnBlur: Ref, useVuetifyValidation: Ref, vuetifyNumberRules: Ref, vuetifyKeyRules: Ref, ) { // Règles de validation const numberRules = computed(() => { if (useVuetifyValidation.value) { return [] } const rules: SyValidationRule[] = [] if (required.value) { rules.push({ type: 'required', options: { message: customLocale.value.errorRequiredNumber || locales.errorRequiredNumber, fieldIdentifier: numberLabel.value, }, }) } // Ajout des règles personnalisées avec prévalence si demandé if (customRulesPrecedence.value && customNumberRules.value && customNumberRules.value.length > 0) { rules.push(...customNumberRules.value.map(rule => ({ ...rule, options: rule.options || {}, }))) } // Règle de validation standard du NIR rules.push({ type: 'custom', options: { validate: (value: string) => { if (!value) return true // Ne valider que si tous les caractères sont saisis if (value.length < 13) { return customLocale.value.errorInvalidNumber || locales.errorInvalidNumber } const result = checkNIR(value, nirType.value) return result ? true : customLocale.value.errorInvalidNumber || locales.errorInvalidNumber }, message: customLocale.value.errorInvalidNumber || locales.errorInvalidNumber, successMessage: customLocale.value.successNumberValid || locales.successNumberValid, fieldIdentifier: numberLabel.value, }, }) // Ajout des règles personnalisées sans prévalence (comportement par défaut) if (!customRulesPrecedence.value && customNumberRules.value && customNumberRules.value.length > 0) { rules.push(...customNumberRules.value.map(rule => ({ ...rule, options: rule.options || {}, }))) } return rules }) const keyRules = computed(() => { if (useVuetifyValidation.value || !displayKey.value) { return [] } const rules: SyValidationRule[] = [] if (required.value) { rules.push({ type: 'required', options: { message: customLocale.value.errorRequiredKey || locales.errorRequiredKey, fieldIdentifier: keyLabel.value, }, }) } const validateKey = (value: string) => { if (!value) return true if (!unmaskedNumberValue.value) return true const fullNir = unmaskedNumberValue.value + value return isNIRKeyValid(fullNir) } // Ajout des règles personnalisées if (customKeyRules.value) { rules.push(...customKeyRules.value) } // Ajout de la règle de validation par défaut si pas de règle personnalisée avec validation de clé if (!customKeyRules.value?.some(rule => rule.options.validate)) { rules.push({ type: 'custom', options: { validate: validateKey, message: customLocale.value.errorInvalidKey || locales.errorInvalidKey, successMessage: customLocale.value.successKeyValid || locales.successKeyValid, fieldIdentifier: keyLabel.value, }, }) } return rules }) // État pour suivre si une validation est en cours const isValidating = ref(false) const shouldValidateOnBlur = ref(false) let validationPromise: Promise | null = null const numberFieldFocused = ref(false) const keyFieldFocused = ref(false) // update focus state on blur and focus const onNumberFocus = () => { numberFieldFocused.value = true } const onNumberBlur = () => { numberFieldFocused.value = false } const onKeyFocus = () => { keyFieldFocused.value = true } const onKeyBlur = () => { keyFieldFocused.value = false } let numberInput: HTMLInputElement | null = null let keyInput: HTMLInputElement | null = null onMounted(() => { numberInput = numberField.value?.$el?.querySelector('input') ?? null if (numberInput) { numberInput.addEventListener('focus', onNumberFocus) numberInput.addEventListener('blur', onNumberBlur) } keyInput = keyField.value?.$el?.querySelector('input') ?? null if (keyInput) { keyInput.addEventListener('focus', onKeyFocus) keyInput.addEventListener('blur', onKeyBlur) } }) onBeforeUnmount(() => { numberInput?.removeEventListener('focus', onNumberFocus) numberInput?.removeEventListener('blur', onNumberBlur) keyInput?.removeEventListener('focus', onKeyFocus) keyInput?.removeEventListener('blur', onKeyBlur) }) const numberValidation = useValidation({ modelValue: numberValue, readonly, disabled, required, isValidateOnBlur, showSuccessMessages, disableErrorHandling, useVuetifyValidation, label, customRules: numberRules, customWarningRules: computed(() => unmaskedNumberValue.value.length === 13 ? customNumberWarningRules.value : []), rules: vuetifyNumberRules, focused: numberFieldFocused, }) const keyValidation = useValidation({ modelValue: keyValue, readonly, disabled, required, isValidateOnBlur, showSuccessMessages, disableErrorHandling, useVuetifyValidation, label, customRules: keyRules, customWarningRules: computed(() => (displayKey.value && unmaskedKeyValue.value.length === 2) ? customKeyWarningRules.value : []), rules: vuetifyKeyRules, focused: keyFieldFocused, }) // Validation des champs const validateFields = (onBlur = false): Promise => { shouldValidateOnBlur.value = shouldValidateOnBlur.value || onBlur if (validationPromise) { return validationPromise } validationPromise = (async () => { isValidating.value = true await numberValidation.validate() if (displayKey.value) { await keyValidation.validate() } if (shouldValidateOnBlur.value) { await nextTick() if (numberValidation.hasError.value) { numberField.value?.$el?.querySelector?.('input')?.focus() } else if (keyValidation.hasError.value) { keyField.value?.$el?.querySelector?.('input')?.focus() } shouldValidateOnBlur.value = false } isValidating.value = false validationPromise = null return !numberValidation.hasError.value && (!displayKey.value || !keyValidation.hasError.value) })() return validationPromise } const hasFieldErrors = computed(() => numberValidation.hasError.value || (displayKey.value && keyValidation.hasError.value)) return { numberValidation, keyValidation, validateFields, hasFieldErrors, } }