Files
malio-layer-ui/.playground/pages/composant/form/client.vue
tristan b2e3a83bb9 [#MUI-32] Création d'un composant saisie assistée (autocomplete) (#46)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #46
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-13 06:59:13 +00:00

318 lines
10 KiB
Vue

<template>
<div class="flex justify-center">
<div class="w-[1348px]">
<div class="flex gap-3 mt-[46px]">
<MalioButtonIcon
icon="mdi:arrow-left-bold"
icon-size="24"
aria-label="Précédent"
variant="ghost"
/>
<h1 class="text-[32px] text-m-primary font-bold">Ajouter un client</h1>
</div>
<div class="mt-[48px] grid grid-cols-3 gap-x-[80px] gap-y-8">
<MalioInputText
label="Nom du client (Entreprise)"
/>
<MalioInputText
label="Nom du contact principal"
/>
<MalioInputText
label="Prénom du contact principal"
/>
<MalioSelectCheckbox
v-model="multiselectValue"
label="Catégorie"
:options="[
{label: 'Catégorie 1', value: 'Catégorie 1'},
{label: 'Catégorie 2', value: 'Catégorie 2'}
]"
/>
<MalioInputPhone
v-for="(_, index) in phones"
:key="index"
v-model="phones[index]"
label="Téléphone"
add-icon-name="mdi:plus"
:addable="phones.length === 1"
@add="addPhoneInput"
/>
<MalioInputEmail
label="Email"
/>
<MalioSelect
v-model="distributeur"
value=""
label="Distributeur / Courtier"
:options="[
{label: 'Dépend du distributeur', value: 'Dépend du distributeur'},
{label: 'Distributeur', value: 'Distributeur'},
{label: 'Courtier', value: 'Courtier'},
]"
/>
<MalioSelect
v-model="nomCourtier"
value=""
label="Nom du courtier"
:options="[
{label: 'Nom 1', value: 'Nom 1'}
]"
/>
<MalioSelect
v-model="nomDistributeur"
value=""
label="Nom du distributeur"
:options="[
{label: 'Nom 1', value: 'Nom 1'}
]"
/>
<MalioCheckbox label="Prestation de triage" groupClass="self-center"/>
</div>
<div class="mt-12 flex justify-center">
<MalioButton label="Valider" variant="primary"/>
</div>
<div class="mt-[60px]">
<MalioTabList :tabs="tabs" v-model="tabsValue">
<template #information>
<div class="grid grid-cols-3 gap-x-[80px] gap-y-8 mt-12 shadow-[0_4px_4px_0_rgba(0,0,0,0.25)] py-4 pl-[28px] pr-[60px]">
<MalioInputTextArea label="Descritpion" resize="none" groupClass="row-span-2" textInput="h-full"/>
<MalioInputText v-model="concurrent" label="Concurrent"/>
<MalioInputText label="Date création"/>
<MalioInputText label="Nombre de salariés" />
<MalioInputAmount label="CA"/>
<MalioInputText label="Dirigeant" />
<MalioInputText label="Résultat" />
</div>
<div class="mt-12 flex justify-center">
<MalioButton label="Valider" variant="primary"/>
</div>
</template>
<template #adresses>
<div class="relative grid grid-cols-3 gap-x-[80px] gap-y-8 mt-12 bg-white shadow-[0_4px_4px_0_rgba(0,0,0,0.25)] py-4 pl-[28px] pr-[60px]">
<MalioButtonIcon
icon="mdi:delete-outline"
aria-label="Supprimer l'adresse"
variant="ghost"
button-class="absolute top-3 right-3"
@click="onDeleteAdresse"
/>
<MalioCheckbox label="Prospect" groupClass="self-center"/>
<MalioCheckbox label="Adresse de livraison" groupClass="self-center"/>
<MalioCheckbox label="Facturation" groupClass="self-center"/>
<MalioSelectCheckbox
v-model="multiselectValue"
label="Catégorie"
:options="[
{label: 'Catégorie 1', value: 'Catégorie 1'},
{label: 'Catégorie 2', value: 'Catégorie 2'}
]"
/>
<MalioSelect
label="Pays"
v-model="pays"
:options="[
{label: 'France', value: 'France'},
{label: 'Espagne', value: 'Espagne'}
]"/>
<MalioInputText v-model="codePostal" label="Code postal" />
<MalioSelect
v-model="ville"
label="Ville"
:options="villeOptions"
:no-options-text="villeNoOptionsText"
/>
<MalioInputAutocomplete
v-model="adresse"
label="Adresse"
:options="adresseOptions"
:loading="adresseLoading"
:min-search-length="2"
:no-results-text="adresseNoResultsText"
:min-search-text="adresseMinSearchText"
@search="onSearchAdresse"
/>
<MalioInputText label="Adresse complémentaire"/>
<div class="flex justify-between">
<MalioCheckbox
v-for="dep in departements"
:key="dep"
v-model="departementsSelected[dep]"
:label="dep"
group-class="w-auto self-center"
/>
</div>
<MalioSelect label="Contact" :options="[]"/>
<MalioCheckbox label="Prestation de triage" groupClass="self-center"/>
</div>
<div class="mt-12 flex justify-center gap-6">
<MalioButton label="Nouvelle Adresse" variant="secondary" icon-name="mdi:add-bold" icon-position="left"/>
<MalioButton label="Valider" variant="primary"/>
</div>
</template>
</MalioTabList>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref, computed, watch} from 'vue'
type Commune = {
nom: string
code: string
codesPostaux: string[]
}
type BanFeature = {
properties: {
label: string
id: string
name: string
housenumber?: string
street?: string
postcode: string
citycode: string
city: string
}
}
const multiselectValue = ref<Array<string | number>>([])
const distributeur = ref<string>('')
const phones = ref<string[]>([''])
const nomDistributeur = ref<string>('')
const nomCourtier = ref<string>('')
function addPhoneInput() {
phones.value.push('')
}
function onDeleteAdresse() {
console.log('Supprimer cette adresse')
}
const departements = ['86', '17', '82']
const departementsSelected = ref<Record<string, boolean>>({86: false, 17: false, 82: false})
const pays = ref<string>('France')
const codePostal = ref<string>('')
const ville = ref<string | number | null>(null)
const villeOptions = ref<Array<{label: string; value: string}>>([])
const villeLoading = ref(false)
const villeNoOptionsText = computed(() => {
if (villeLoading.value) return 'Chargement…'
if (!/^\d{5}$/.test(codePostal.value)) return 'Saisir un code postal (5 chiffres)'
return 'Aucune ville pour ce code postal'
})
let villeFetchId = 0
watch(codePostal, async (cp) => {
ville.value = null
villeOptions.value = []
adresse.value = null
adresseOptions.value = []
if (!/^\d{5}$/.test(cp)) {
villeLoading.value = false
return
}
const requestId = ++villeFetchId
villeLoading.value = true
try {
const response = await fetch(`https://geo.api.gouv.fr/communes?codePostal=${cp}`)
const data = await response.json() as Commune[]
if (requestId !== villeFetchId) return
villeOptions.value = data.map(c => ({label: c.nom, value: c.code}))
} catch (err) {
if (requestId !== villeFetchId) return
villeOptions.value = []
console.error('Erreur lors du chargement des villes', err)
} finally {
if (requestId === villeFetchId) villeLoading.value = false
}
})
const adresse = ref<string | number | null>(null)
const adresseOptions = ref<Array<{label: string; value: string}>>([])
const adresseLoading = ref(false)
const adresseMinSearchText = computed(() => {
if (!/^\d{5}$/.test(codePostal.value)) return 'Saisir d\'abord un code postal'
return 'Tapez au moins 3 caractères'
})
const adresseNoResultsText = computed(() => {
if (!/^\d{5}$/.test(codePostal.value)) return 'Saisir d\'abord un code postal'
return 'Aucune adresse trouvée'
})
let adresseFetchId = 0
const onSearchAdresse = async (query: string) => {
if (!/^\d{5}$/.test(codePostal.value) || query.length < 3) {
adresseOptions.value = []
adresseLoading.value = false
return
}
const requestId = ++adresseFetchId
adresseLoading.value = true
try {
const params = new URLSearchParams({
q: query,
postcode: codePostal.value,
type: 'housenumber',
})
const response = await fetch(`https://api-adresse.data.gouv.fr/search/?${params.toString()}`)
const data = await response.json() as {features: BanFeature[]}
if (requestId !== adresseFetchId) return
adresseOptions.value = data.features.map(f => ({
label: f.properties.name,
value: f.properties.name,
}))
} catch (err) {
if (requestId !== adresseFetchId) return
adresseOptions.value = []
console.error('Erreur lors du chargement des adresses', err)
} finally {
if (requestId === adresseFetchId) adresseLoading.value = false
}
}
const tabsValue = ref('information')
const concurrent = ref('')
const informationValid = computed(() => concurrent.value.trim().length > 0)
const adressesValid = computed(() => /^\d{5}$/.test(codePostal.value))
const tabs = computed(() => [
{
key: 'information',
label: 'Information',
icon: 'mdi:account-outline',
},
{
key: 'contacts',
label: 'Contacts',
icon: 'mdi:account-box-plus-outline',
disabled: !informationValid.value,
},
{
key: 'adresses',
label: 'Adresses',
icon: 'mdi:map-marker-outline',
disabled: !informationValid.value,
},
{
key: 'transport',
label: 'Transport',
icon: 'mdi:truck-delivery-outline',
disabled: !informationValid.value || !adressesValid.value,
},
{
key: 'comptabilité',
label: 'Comptabilité',
icon: 'mdi:bank-circle-outline',
disabled: !informationValid.value || !adressesValid.value,
},
])
</script>