fix(front) : masque les onglets vides en consultation des 4 repertoires (ERP-193)
This commit is contained in:
@@ -113,7 +113,8 @@
|
||||
</div>
|
||||
|
||||
<!-- ── Onglets (Adresses · Contacts · Prix) — ouvre sur Adresses ──── -->
|
||||
<MalioTabList v-model="activeTab" :tabs="tabs" class="mt-[60px]">
|
||||
<!-- ERP-193 : barre masquee s'il ne reste aucun onglet non vide. -->
|
||||
<MalioTabList v-if="visibleTabKeys.length" v-model="activeTab" :tabs="tabs" class="mt-[60px]">
|
||||
<template #addresses>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<!-- Adresse UNIQUE (ERP-172). -->
|
||||
@@ -241,12 +242,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import CarrierAddressBlock from '~/modules/transport/components/CarrierAddressBlock.vue'
|
||||
import CarrierContactBlock from '~/modules/transport/components/CarrierContactBlock.vue'
|
||||
import { useCarrier } from '~/modules/transport/composables/useCarrier'
|
||||
import {
|
||||
canEditCarrier,
|
||||
carrierConsultationVisibleTabs,
|
||||
labelOfRelation,
|
||||
mapAddressToDraft,
|
||||
mapContactToDraft,
|
||||
@@ -300,18 +302,32 @@ const dischargeLabel = computed(() => {
|
||||
})
|
||||
|
||||
// ── Onglets : Adresses · Contacts · Prix (ouvre sur Adresses, pas de Qualimat) ──
|
||||
const activeTab = ref('addresses')
|
||||
// ERP-193 (retour metier) : on masque tout onglet de donnees vide.
|
||||
const TAB_ICONS: Record<string, string> = {
|
||||
addresses: 'mdi:map-marker-outline',
|
||||
contacts: 'mdi:account-box-plus-outline',
|
||||
prices: 'mdi:payment',
|
||||
}
|
||||
const tabs = computed(() => ['addresses', 'contacts', 'prices'].map(key => ({
|
||||
const visibleTabKeys = computed(() => carrierConsultationVisibleTabs(carrier.value))
|
||||
const tabs = computed(() => visibleTabKeys.value.map(key => ({
|
||||
key,
|
||||
label: t(`transport.carriers.tab.${key}`),
|
||||
icon: TAB_ICONS[key],
|
||||
})))
|
||||
|
||||
// Onglet initial : vide tant que le transporteur n'est pas charge, puis premier
|
||||
// onglet visible. Un watcher recale si l'onglet courant disparait.
|
||||
const activeTab = ref('')
|
||||
watch(visibleTabKeys, (keys) => {
|
||||
if (keys.length === 0) {
|
||||
activeTab.value = ''
|
||||
return
|
||||
}
|
||||
if (!keys.includes(activeTab.value)) {
|
||||
activeTab.value = keys[0]
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Adresse UNIQUE (ERP-172) : un seul bloc en lecture seule (vide si pas d'adresse).
|
||||
const address = computed(() => carrier.value?.address
|
||||
? mapAddressToDraft(carrier.value.address)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
canEditCarrier,
|
||||
carrierConsultationVisibleTabs,
|
||||
hasAddressData,
|
||||
iriOf,
|
||||
labelOfRelation,
|
||||
mapAddressToDraft,
|
||||
@@ -118,3 +120,47 @@ describe('carrierMappers', () => {
|
||||
expect(showRestoreAction(noArchive, true)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasAddressData', () => {
|
||||
it('faux pour une adresse absente ou entièrement vide', () => {
|
||||
expect(hasAddressData(null)).toBe(false)
|
||||
expect(hasAddressData(undefined)).toBe(false)
|
||||
expect(hasAddressData({ '@id': '/api/carrier_addresses/1', id: 1 })).toBe(false)
|
||||
})
|
||||
|
||||
it('vrai dès qu\'un champ adresse est rempli', () => {
|
||||
expect(hasAddressData({ '@id': '/api/carrier_addresses/1', id: 1, city: 'Poitiers' })).toBe(true)
|
||||
expect(hasAddressData({ '@id': '/api/carrier_addresses/1', id: 1, country: 'France' })).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('carrierConsultationVisibleTabs', () => {
|
||||
it('retourne [] tant que le transporteur n\'est pas chargé', () => {
|
||||
expect(carrierConsultationVisibleTabs(null)).toEqual([])
|
||||
expect(carrierConsultationVisibleTabs(undefined)).toEqual([])
|
||||
})
|
||||
|
||||
it('masque les onglets vides (transporteur minimal)', () => {
|
||||
expect(carrierConsultationVisibleTabs({ '@id': '/api/carriers/1', id: 1, name: 'LIOT' })).toEqual([])
|
||||
})
|
||||
|
||||
it('affiche addresses/contacts/prices dans l\'ordre quand renseignés', () => {
|
||||
const carrier: CarrierDetail = {
|
||||
'@id': '/api/carriers/1', id: 1,
|
||||
address: { '@id': '/api/carrier_addresses/1', id: 1, city: 'Poitiers' },
|
||||
contacts: [{ '@id': '/api/carrier_contacts/1', id: 1 }],
|
||||
prices: [{ '@id': '/api/carrier_prices/1', id: 1 }],
|
||||
}
|
||||
expect(carrierConsultationVisibleTabs(carrier)).toEqual(['addresses', 'contacts', 'prices'])
|
||||
})
|
||||
|
||||
it('ne garde que les onglets non vides (contacts seulement)', () => {
|
||||
const carrier: CarrierDetail = {
|
||||
'@id': '/api/carriers/1', id: 1,
|
||||
address: { '@id': '/api/carrier_addresses/1', id: 1 },
|
||||
contacts: [{ '@id': '/api/carrier_contacts/1', id: 1 }],
|
||||
prices: [],
|
||||
}
|
||||
expect(carrierConsultationVisibleTabs(carrier)).toEqual(['contacts'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -175,6 +175,62 @@ export function mapPriceToDraft(price: CarrierPriceRead): CarrierPriceFormDraft
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrai si une valeur scalaire porte une donnée affichable (non null/undefined,
|
||||
* et non chaîne vide après trim). Sert aux prédicats « onglet vide » ci-dessous.
|
||||
*/
|
||||
function hasValue(value: unknown): boolean {
|
||||
if (value === null || value === undefined) {
|
||||
return false
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value.trim() !== ''
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrai si l'adresse unique (OneToOne, ERP-172) porte au moins un champ rempli.
|
||||
* Un objet adresse vide (toutes clés nulles) est considéré comme « pas d'adresse ».
|
||||
*/
|
||||
export function hasAddressData(address: CarrierAddressRead | null | undefined): boolean {
|
||||
if (!address) {
|
||||
return false
|
||||
}
|
||||
return [
|
||||
address.postalCode,
|
||||
address.city,
|
||||
address.street,
|
||||
address.streetComplement,
|
||||
address.country,
|
||||
].some(hasValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Onglets visibles en consultation (ERP-193, retour métier) : on masque tout
|
||||
* onglet de données vide. Le transporteur n'a pas de coquille « à venir ».
|
||||
* Ordre : Adresses · Contacts · Prix. Retourne `[]` tant que le transporteur
|
||||
* n'est pas chargé.
|
||||
*/
|
||||
export function carrierConsultationVisibleTabs(
|
||||
carrier: CarrierDetail | null | undefined,
|
||||
): string[] {
|
||||
if (!carrier) {
|
||||
return []
|
||||
}
|
||||
const visible: string[] = []
|
||||
if (hasAddressData(carrier.address)) {
|
||||
visible.push('addresses')
|
||||
}
|
||||
if ((carrier.contacts ?? []).length > 0) {
|
||||
visible.push('contacts')
|
||||
}
|
||||
if ((carrier.prices ?? []).length > 0) {
|
||||
visible.push('prices')
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
/** Bouton « Modifier » : visible avec la permission `manage` (Admin / Bureau). */
|
||||
export function canEditCarrier(can: (code: string) => boolean): boolean {
|
||||
return can('transport.carriers.manage')
|
||||
|
||||
Reference in New Issue
Block a user