202 lines
7.5 KiB
Vue
202 lines
7.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { debounce } from '~/shared/utils/debounce'
|
|
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
|
|
|
/**
|
|
* Onglet « Qualimat » — saisie assistée par recherche dans le référentiel QUALIMAT
|
|
* (RG-4.01 / RG-4.04). Datatable paginé filtré par le NOM (`searchName`), sélection
|
|
* d'une ligne → modal de confirmation → `integrate`. Mutualisé entre l'écran
|
|
* d'AJOUT (ERP-166) et l'écran de MODIFICATION (ERP-172, « actualiser le
|
|
* transporteur »). La persistance (copie nom / certification / FK) est portée par
|
|
* le parent via `useCarrierForm.applyQualimatSelection`.
|
|
*/
|
|
const props = defineProps<{
|
|
/** Terme de recherche (nom du transporteur saisi dans le formulaire principal). */
|
|
searchName: string
|
|
/** IRI QUALIMAT actuellement lié (coche le radio de la ligne correspondante). */
|
|
selectedIri: string | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(event: 'integrate', row: QualimatCarrierRow): void
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
|
|
const {
|
|
items: qualimatItems,
|
|
totalItems: qualimatTotal,
|
|
currentPage: qualimatPage,
|
|
itemsPerPage: qualimatPerPage,
|
|
itemsPerPageOptions: qualimatPerPageOptions,
|
|
goToPage: qualimatGoToPage,
|
|
setItemsPerPage: qualimatSetPerPage,
|
|
setFilters: qualimatSetFilters,
|
|
} = useQualimatSearch()
|
|
|
|
// Colonnes du datatable de sélection QUALIMAT (radio / Nom / Adresse / Validité).
|
|
const qualimatColumns = [
|
|
{ key: 'select', label: '' },
|
|
{ key: 'name', label: t('transport.carriers.form.qualimat.columns.name') },
|
|
{ key: 'address', label: t('transport.carriers.form.qualimat.columns.address') },
|
|
{ key: 'validityDate', label: t('transport.carriers.form.qualimat.columns.validityDate') },
|
|
]
|
|
|
|
// Le datatable n'affiche QUE des résultats de recherche : vide tant que le Nom n'est
|
|
// pas saisi (pas de liste complète par défaut).
|
|
const hasQualimatSearch = computed(() => props.searchName.trim() !== '')
|
|
|
|
const qualimatRows = computed(() => {
|
|
if (!hasQualimatSearch.value) {
|
|
return []
|
|
}
|
|
return qualimatItems.value.map(row => ({
|
|
id: row.id,
|
|
iri: row['@id'],
|
|
name: row.name,
|
|
address: formatQualimatAddress(row),
|
|
validityDate: row.validityDate,
|
|
}))
|
|
})
|
|
|
|
const qualimatTotalDisplay = computed(() => (hasQualimatSearch.value ? qualimatTotal.value : 0))
|
|
const qualimatEmptyMessage = computed(() => hasQualimatSearch.value
|
|
? t('transport.carriers.form.qualimat.empty')
|
|
: t('transport.carriers.form.qualimat.searchHint'))
|
|
|
|
// Re-filtrage debouncé sur le nom ; aucune recherche tant que le Nom est vide.
|
|
const filterQualimatByName = debounce((term: string) => {
|
|
if (term.trim() === '') {
|
|
return
|
|
}
|
|
void qualimatSetFilters({ search: term })
|
|
}, 300)
|
|
|
|
watch(() => props.searchName, term => filterQualimatByName(term), { immediate: true })
|
|
|
|
/** Adresse QUALIMAT condensée pour la colonne « Adresse » (voie · CP · ville). */
|
|
function formatQualimatAddress(row: QualimatCarrierRow): string {
|
|
return [row.address, row.postalCode, row.city].filter(Boolean).join(' · ')
|
|
}
|
|
|
|
/** RG-4.04 : un agrément est périmé si sa date de validité est < aujourd'hui. */
|
|
function isExpired(value: string): boolean {
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return false
|
|
}
|
|
const today = new Date()
|
|
today.setHours(0, 0, 0, 0)
|
|
date.setHours(0, 0, 0, 0)
|
|
return date.getTime() < today.getTime()
|
|
}
|
|
|
|
/** Format court français JJ-MM-AAAA (chaîne vide si date absente / invalide). */
|
|
function formatDateFr(value: string | null | undefined): string {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return ''
|
|
}
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
return `${day}-${month}-${date.getFullYear()}`
|
|
}
|
|
|
|
// ── Confirmation d'intégration ───────────────────────────────────────────────
|
|
const confirmOpen = ref(false)
|
|
const pendingRow = ref<QualimatCarrierRow | null>(null)
|
|
|
|
/** Clic sur une ligne → retrouve la ligne QUALIMAT source + ouvre la modal. */
|
|
function onQualimatRowClick(item: Record<string, unknown>): void {
|
|
const row = qualimatItems.value.find(r => r.id === item.id)
|
|
if (row) {
|
|
pendingRow.value = row
|
|
confirmOpen.value = true
|
|
}
|
|
}
|
|
|
|
/** Confirme l'intégration : délègue la persistance au parent via `integrate`. */
|
|
function confirmIntegrate(): void {
|
|
const row = pendingRow.value
|
|
confirmOpen.value = false
|
|
if (row !== null) {
|
|
emit('integrate', row)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="mt-12 flex flex-col gap-6">
|
|
<!-- table-fixed : 1re colonne (radio) étroite, les 3 autres à parts égales. -->
|
|
<MalioDataTable
|
|
class="qualimat-table"
|
|
table-class="table-fixed"
|
|
:columns="qualimatColumns"
|
|
:items="qualimatRows"
|
|
:total-items="qualimatTotalDisplay"
|
|
:page="qualimatPage"
|
|
:per-page="qualimatPerPage"
|
|
:per-page-options="qualimatPerPageOptions"
|
|
row-clickable
|
|
:empty-message="qualimatEmptyMessage"
|
|
@row-click="onQualimatRowClick"
|
|
@update:page="qualimatGoToPage"
|
|
@update:per-page="qualimatSetPerPage"
|
|
>
|
|
<!-- Radio reflétant la ligne QUALIMAT intégrée (lecture). -->
|
|
<template #cell-select="{ item }">
|
|
<MalioRadioButton
|
|
:model-value="selectedIri"
|
|
name="qualimat-row"
|
|
:value="item.iri"
|
|
group-class="mt-0"
|
|
/>
|
|
</template>
|
|
<!-- Date de validité : fond rouge si périmée (RG-4.04). -->
|
|
<template #cell-validityDate="{ item }">
|
|
<span
|
|
v-if="item.validityDate"
|
|
:class="isExpired(item.validityDate as string) ? 'inline-block rounded px-2 py-0.5 bg-m-danger text-white' : ''"
|
|
>
|
|
{{ formatDateFr(item.validityDate as string) }}
|
|
</span>
|
|
</template>
|
|
</MalioDataTable>
|
|
|
|
<!-- Modal de confirmation d'intégration QUALIMAT. -->
|
|
<MalioModal v-model="confirmOpen" modal-class="max-w-md">
|
|
<template #header>
|
|
<h2 class="text-[24px] font-bold">{{ t('transport.carriers.form.qualimat.confirm.title') }}</h2>
|
|
</template>
|
|
<p>{{ t('transport.carriers.form.qualimat.confirm.message') }}</p>
|
|
<template #footer>
|
|
<MalioButton
|
|
variant="secondary"
|
|
button-class="flex-1"
|
|
:label="t('transport.carriers.form.qualimat.confirm.cancel')"
|
|
@click="confirmOpen = false"
|
|
/>
|
|
<MalioButton
|
|
variant="primary"
|
|
button-class="flex-1"
|
|
:label="t('transport.carriers.form.qualimat.confirm.confirm')"
|
|
@click="confirmIntegrate"
|
|
/>
|
|
</template>
|
|
</MalioModal>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Datatable QUALIMAT en table-fixed : la colonne radio (1re) reste étroite,
|
|
les 3 autres (nom / adresse / validité) se partagent l'espace à parts égales. */
|
|
.qualimat-table :deep(th:first-child),
|
|
.qualimat-table :deep(td:first-child) {
|
|
width: 56px;
|
|
}
|
|
</style>
|