import { Meta, Source } from '@storybook/addon-docs/blocks'
import '../../styles/shared.css';

<Meta title="Guide Du Dev/Utilitaires/Règles de validation" />


<div className="header">
  <h1>Règles de validation</h1>
  <p>Le Design System fournit un ensemble complet de règles de validation prêtes à l'emploi pour les formulaires Vuetify. Ces règles facilitent la validation des champs de formulaire tout en garantissant une expérience utilisateur cohérente. </p>
</div>

Ces règles ne sont pas liées à la validation interne du Design System&nbsp;: ce sont des fonctions réutilisables, utiles à tout projet. Elles peuvent être combinées entre elles, avec d'autres règles de `synapse` ou des règles personnalisées. Voir aussi la [documentation des règles `Vuetify`](https://vuetifyjs.com/en/components/forms/#rules).

> Les règles dont le nom se termine par `Fn` sont des **fonctions d'ordre supérieur**&nbsp;: elles prennent des données de configuration en paramètre et retournent une fonction de validation, utilisable comme règle d'un champ de formulaire.

## Importation

<Source dark code={`import {
  // Règles prêtes à l'emploi (sans paramètre) :
  isRequired,
  isValidEmail,
  isDateValid,
  isNotBeforeToday,
  isNotAfterToday,
  isHolidayDay,
  // Fonctions d'ordre supérieur (à appeler avec un paramètre) :
  isMinLengthFn,
  isMaxLengthFn,
  isExactLengthFn,
  doMatchPatternFn,
  isNotBeforeDateFn,
  isNotAfterDateFn,
} from '@cnamts/synapse'
`}/>

Chaque règle existe aussi sous sa forme \`…Fn\` (ex. \`isRequiredFn\`) pour **personnaliser le message** via un objet \`errorMessages\`. La clé \`default\` accepte&nbsp;:
- une **chaîne** pour un message fixe&nbsp;;
- une **fonction** uniquement quand on veut injecter un paramètre de la règle (longueur, date…).

<Source dark code={`isRequiredFn({ default: 'Ce champ est obligatoire' })                       // message fixe
isMinLengthFn(8, false, { default: (min) => \`Au moins \${min} caractères\` })  // message avec paramètre
`}/>

## Règles disponibles

### isRequired

Vérifie qu'un champ n'est pas vide.

<Source dark code={`isRequired                                            // règle prête à l'emploi
isRequiredFn({ default: 'Ce champ est obligatoire' })  // message personnalisé
`}/>

### isValidEmail

Vérifie qu'un champ contient une adresse email valide.

<Source dark code={`isValidEmail                                       // règle prête à l'emploi
isValidEmailFn({ default: 'Format d\\'email invalide' })  // message personnalisé
`}/>

### isMinLength

Vérifie qu'un champ contient au moins un nombre minimum de caractères.

<Source dark code={`isMinLengthFn(8)                  // minimum 8 caractères
isMinLengthFn(8, true)            // en ignorant les espaces (2e arg)

// message personnalisé (3e arg : objet errorMessages)
isMinLengthFn(8, false, { default: (min) => \`Doit contenir au moins \${min} caractères\` })
`}/>

### isMaxLength

Vérifie qu'un champ ne dépasse pas un nombre maximum de caractères.

<Source dark code={`isMaxLengthFn(100)               // maximum 100 caractères
isMaxLengthFn(100, true)         // en ignorant les espaces

// message personnalisé
isMaxLengthFn(100, false, { default: (max) => \`Ne doit pas dépasser \${max} caractères\` })
`}/>

### isExactLength

Vérifie qu'un champ contient exactement un nombre spécifique de caractères.

<Source dark code={`isExactLengthFn(10)              // exactement 10 caractères
isExactLengthFn(10, true)        // en ignorant les espaces

// message personnalisé
isExactLengthFn(10, false, { default: (len) => \`Doit contenir exactement \${len} caractères\` })
`}/>

### doMatchPattern

Vérifie qu'un champ correspond à un motif d'expression régulière.

<Source dark code={`doMatchPatternFn(/^[a-zA-Z]+$/)   // motif (lettres uniquement)

// message personnalisé (2e arg : objet errorMessages)
doMatchPatternFn(/^[a-zA-Z]+$/, { default: 'Doit contenir uniquement des lettres' })
`}/>

### isDateValid

Vérifie qu'un champ contient une date valide.

<Source dark code={`isDateValid                                  // règle prête à l'emploi
isDateValidFn({ default: 'Date invalide' })  // message personnalisé
`}/>

### isNotBeforeDate

Vérifie qu'une date n'est pas antérieure à une date spécifiée.

<Source dark code={`isNotBeforeDateFn('01/01/2023')   // date de référence (format JJ/MM/AAAA)

// message personnalisé (la date de référence est passée à la fonction)
isNotBeforeDateFn('01/01/2023', { default: (date) => \`La date doit être postérieure ou égale au \${date}\` })
`}/>

### isNotAfterDate

Vérifie qu'une date n'est pas postérieure à une date spécifiée.

<Source dark code={`isNotAfterDateFn('31/12/2023')    // date de référence (format JJ/MM/AAAA)

// message personnalisé (la date de référence est passée à la fonction)
isNotAfterDateFn('31/12/2023', { default: (date) => \`La date doit être antérieure ou égale au \${date}\` })
`}/>

### isNotBeforeToday

Vérifie qu'une date n'est pas antérieure à aujourd'hui.

<Source dark code={`isNotBeforeToday                                          // règle prête à l'emploi
isNotBeforeTodayFn({ default: 'La date ne peut pas être dans le passé' })  // message personnalisé
`}/>

### isNotAfterToday

Vérifie qu'une date n'est pas postérieure à aujourd'hui.

<Source dark code={`isNotAfterToday                                          // règle prête à l'emploi
isNotAfterTodayFn({ default: 'La date ne peut pas être dans le futur' })  // message personnalisé
`}/>

### isHolidayDay

Règle qui **échoue si la date est un jour férié français** : elle renvoie `true` quand la date n'est PAS un férié, sinon le message d'erreur. C'est déjà une règle complète&nbsp;: passez-la directement dans `:rules`, inutile de l'inverser avec `v => !isHolidayDay(v)`.

<Source dark code={`isHolidayDay                                              // règle prête à l'emploi
isHolidayDayFn({ default: 'La date ne peut pas être un jour férié' })  // message personnalisé
`}/>

## Exemples d'utilisation

### Formulaire d'inscription simple

<Source dark code={`<template>
  <v-form ref="form" v-model="isFormValid">
    <v-text-field
      v-model="formData.email"
      label="Email"
      :rules="rules.email"
    />
    
    <v-text-field
      v-model="formData.password"
      label="Mot de passe"
      type="password"
      :rules="rules.password"
    />
    
    <v-text-field
      v-model="formData.confirmPassword"
      label="Confirmer le mot de passe"
      type="password"
      :rules="rules.confirmPassword"
    />
    
    <v-btn
      color="primary"
      :disabled="!isFormValid"
      @click="submitForm"
    >
      S'inscrire
    </v-btn>
  </v-form>
</template>

<script>
import { ref, reactive, computed } from 'vue'
import {
  isRequired,
  isValidEmail,
  isMinLengthFn
} from '@cnamts/synapse'

export default {
  setup() {
    const form = ref(null)
    const isFormValid = ref(false)
    
    const formData = reactive({
      email: '',
      password: '',
      confirmPassword: ''
    })
    
    const rules = {
      email: [
        isRequired,
        isValidEmail
      ],
      password: [
        isRequired,
        isMinLengthFn(8),
        // Règle de validation pour mot de passe fort (simplifiée)
        v => v && v.match(/[A-Z]/) && v.match(/[a-z]/) && v.match(/[0-9]/) && v.match(/[^A-Za-z0-9]/) || 
            'Doit contenir au moins une majuscule, une minuscule, un chiffre et un caractère spécial'
      ],
      confirmPassword: [
        isRequired,
        v => v === formData.password || 'Les mots de passe ne correspondent pas'
      ]
    }
    
    const submitForm = () => {
      if (form.value.validate()) {
        // Traitement du formulaire
        console.log('Formulaire valide', formData)
      }
    }
    
    return {
      form,
      isFormValid,
      formData,
      rules,
      submitForm
    }
  }
}
</script>
`}/>

### Formulaire de réservation avec validation de dates

<Source dark code={`<template>
  <v-form ref="form" v-model="isFormValid">
    <v-text-field
      v-model="formData.name"
      label="Nom complet"
      :rules="rules.name"
    />
    
    <v-text-field
      v-model="formData.startDate"
      label="Date de début"
      placeholder="JJ/MM/AAAA"
      :rules="rules.startDate"
    />
    
    <v-text-field
      v-model="formData.endDate"
      label="Date de fin"
      placeholder="JJ/MM/AAAA"
      :rules="rules.endDate"
    />
    
    <v-btn
      color="primary"
      :disabled="!isFormValid"
      @click="submitForm"
    >
      Réserver
    </v-btn>
  </v-form>
</template>

<script>
import { ref, reactive, computed } from 'vue'
import {
  isRequired,
  isDateValid,
  isNotBeforeToday,
  isNotAfterDateFn,
  isHolidayDay
} from '@cnamts/synapse'

export default {
  setup() {
    const form = ref(null)
    const isFormValid = ref(false)
    
    const formData = reactive({
      name: '',
      startDate: '',
      endDate: ''
    })
    
    // Date limite (6 mois à partir d'aujourd'hui)
    const maxDate = new Date()
    maxDate.setMonth(maxDate.getMonth() + 6)
    const maxDateFormatted = maxDate.getDate().toString().padStart(2, '0') + '/' + 
                            (maxDate.getMonth() + 1).toString().padStart(2, '0') + '/' + 
                            maxDate.getFullYear()
    
    // Fonction utilitaire pour vérifier si une date est après une autre
    const isDateAfter = (date1, date2) => {
      // Implémentation simplifiée
      return true || 'La date de fin doit être après la date de début'
    }
    
    const rules = {
      name: [
        isRequired
      ],
      startDate: [
        isRequired,
        isDateValid,
        isNotBeforeToday,
        isNotAfterDateFn(maxDateFormatted),
        isHolidayDay
      ],
      endDate: computed(() => [
        isRequired,
        isDateValid,
        v => !formData.startDate || isDateValid(formData.startDate) !== true || 
             isDateValid(v) !== true || 
             isDateAfter(v, formData.startDate),
        isNotAfterDateFn(maxDateFormatted),
        isHolidayDay
      ])
    }
    
    const submitForm = () => {
      if (form.value.validate()) {
        // Traitement du formulaire
        console.log('Formulaire valide', formData)
      }
    }
    
    return {
      form,
      isFormValid,
      formData,
      rules,
      submitForm
    }
  }
}
</script>
`}/>

## Combinaison de règles personnalisées et prédéfinies

Vous pouvez facilement combiner les règles prédéfinies avec vos propres règles personnalisées :

<Source dark code={`const rules = {
  username: [
    isRequired,
    isMinLengthFn(3),
    isMaxLengthFn(20),
    // Règle personnalisée pour valider le format du nom d'utilisateur
    v => v && /^[a-z0-9_-]+$/.test(v) || 
        'Le nom d\\'utilisateur ne peut contenir que des lettres minuscules, chiffres, tirets et underscores'
  ],
  phoneNumber: [
    // Règle conditionnelle - validation uniquement si une valeur est fournie
    v => !v || /^(0|\\+33)[1-9](\\d{2}){4}$/.test(v) || 
        'Format de numéro de téléphone invalide'
  ]
}
`}/>

## Bonnes pratiques

- Utilisez des noms de champs dans vos règles pour des messages d'erreur plus clairs
- Combinez plusieurs règles pour une validation complète
- Utilisez des règles calculées (computed) lorsque la validation dépend d'autres champs
- Personnalisez les messages d'erreur pour qu'ils soient compréhensibles par les utilisateurs
- Validez toujours les données côté serveur, même si vous avez une validation côté client
- Pour les formulaires complexes, envisagez d'utiliser une bibliothèque de validation comme VeeValidate en complément
