diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json
index 4fa1c99..c09f4e9 100644
--- a/frontend/i18n/locales/fr.json
+++ b/frontend/i18n/locales/fr.json
@@ -10,7 +10,11 @@
"confirm": "Confirmer",
"yes": "Oui",
"no": "Non",
- "actions": "Actions"
+ "actions": "Actions",
+ "comingSoon": {
+ "title": "En cours de dev",
+ "subtitle": "Cette fonctionnalité arrive bientôt."
+ }
},
"sidebar": {
"administration": {
@@ -95,8 +99,6 @@
"back": "Retour au répertoire",
"loading": "Chargement du client…",
"notFound": "Client introuvable.",
- "emptyContacts": "Aucun contact enregistré.",
- "emptyAddresses": "Aucune adresse enregistrée.",
"confirmArchive": {
"title": "Archiver le client",
"message": "Ce client n'apparaîtra plus dans le répertoire actif. Confirmer l'archivage ?"
@@ -111,8 +113,6 @@
"back": "Retour au répertoire",
"loading": "Chargement du client…",
"notFound": "Client introuvable.",
- "emptyContacts": "Aucun contact enregistré.",
- "emptyAddresses": "Aucune adresse enregistrée.",
"save": "Valider"
},
"validation": {
diff --git a/frontend/modules/commercial/components/TabPlaceholderBlank.vue b/frontend/modules/commercial/components/TabPlaceholderBlank.vue
deleted file mode 100644
index 5375cb6..0000000
--- a/frontend/modules/commercial/components/TabPlaceholderBlank.vue
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
diff --git a/frontend/modules/commercial/pages/clients/[id]/edit.vue b/frontend/modules/commercial/pages/clients/[id]/edit.vue
index e1f64e5..d1614ec 100644
--- a/frontend/modules/commercial/pages/clients/[id]/edit.vue
+++ b/frontend/modules/commercial/pages/clients/[id]/edit.vue
@@ -179,9 +179,6 @@
@update:model-value="(v) => contacts[index] = v"
@remove="askRemoveContact(index)"
/>
-
- {{ t('commercial.clients.edit.emptyContacts') }}
-
-
- {{ t('commercial.clients.edit.emptyAddresses') }}
-
-
+
-
+
-
-
-
-
+
+
+
+
@@ -495,6 +489,11 @@ function hydrate(detail: ClientDetail): void {
contacts.value = (detail.contacts ?? []).map(mapContactToDraft)
addresses.value = (detail.addresses ?? []).map(mapAddressToDraft)
ribs.value = (detail.ribs ?? []).map(mapRibToDraft)
+ // Chaque bloc reste visible meme vide : si une collection est vide, on amorce
+ // un bloc vierge (non persiste tant qu'incomplet — cf. submit*/canValidate*).
+ if (contacts.value.length === 0) contacts.value.push(emptyContact())
+ if (addresses.value.length === 0) addresses.value.push(emptyAddress())
+ if (ribs.value.length === 0) ribs.value.push(emptyRib())
// Charge les listes distributeur / courtier si une relation est deja posee.
if (main.relationType === 'distributeur') referentials.loadDistributors().catch(() => {})
if (main.relationType === 'courtier') referentials.loadBrokers().catch(() => {})
@@ -694,6 +693,8 @@ function askRemoveContact(index: number): void {
const removed = contacts.value[index]
if (removed?.id != null) removedContactIds.value.push(removed.id)
contacts.value.splice(index, 1)
+ // Garde au moins un bloc visible (cf. amorce a l'hydratation).
+ if (contacts.value.length === 0) contacts.value.push(emptyContact())
})
}
@@ -755,6 +756,8 @@ function askRemoveAddress(index: number): void {
const removed = addresses.value[index]
if (removed?.id != null) removedAddressIds.value.push(removed.id)
addresses.value.splice(index, 1)
+ // Garde au moins un bloc visible (cf. amorce a l'hydratation).
+ if (addresses.value.length === 0) addresses.value.push(emptyAddress())
})
}
@@ -833,6 +836,8 @@ function askRemoveRib(index: number): void {
const removed = ribs.value[index]
if (removed?.id != null) removedRibIds.value.push(removed.id)
ribs.value.splice(index, 1)
+ // Garde au moins un bloc RIB visible (cf. amorce a l'hydratation).
+ if (ribs.value.length === 0) ribs.value.push(emptyRib())
})
}
diff --git a/frontend/modules/commercial/pages/clients/[id]/index.vue b/frontend/modules/commercial/pages/clients/[id]/index.vue
index 52ea3cd..a16e73c 100644
--- a/frontend/modules/commercial/pages/clients/[id]/index.vue
+++ b/frontend/modules/commercial/pages/clients/[id]/index.vue
@@ -159,9 +159,6 @@
:title="t('commercial.clients.form.contact.title', { n: index + 1 })"
readonly
/>
-
- {{ t('commercial.clients.consultation.emptyContacts') }}
-
@@ -179,9 +176,6 @@
:country-options="countryOptions"
readonly
/>
-
- {{ t('commercial.clients.consultation.emptyAddresses') }}
-
@@ -189,7 +183,7 @@
-
+
-
+
-
-
-
-
+
+
+
+
@@ -320,6 +314,7 @@ import {
type SelectOption,
} from '~/modules/commercial/utils/clientConsultation'
import { formatPhoneFR } from '~/shared/utils/phone'
+import { emptyAddress, emptyContact, emptyRib } from '~/modules/commercial/types/clientForm'
// Masques d'affichage (purement visuels, la donnee reste celle du serveur).
const PHONE_MASK = '## ## ## ## ##'
@@ -372,10 +367,21 @@ const information = computed(() => ({
directorName: client.value?.directorName ?? null,
}))
-const contacts = computed(() => (client.value?.contacts ?? []).map(mapContactToDraft))
+// Chaque bloc reste visible meme vide en consultation : si la collection est
+// vide, on affiche un bloc vierge en lecture seule (pas de message « Aucun … »).
+const contacts = computed(() => {
+ const list = (client.value?.contacts ?? []).map(mapContactToDraft)
+ return list.length ? list : [emptyContact()]
+})
// Vue par adresse : brouillon + options (sites/categories) propres a l'adresse.
-const addressViews = computed(() => (client.value?.addresses ?? []).map(mapAddressView))
-const ribs = computed(() => (client.value?.ribs ?? []).map(mapRibToDraft))
+const addressViews = computed(() => {
+ const views = (client.value?.addresses ?? []).map(mapAddressView)
+ return views.length ? views : [{ draft: emptyAddress(), siteOptions: [], categoryOptions: [] }]
+})
+const ribs = computed(() => {
+ const list = (client.value?.ribs ?? []).map(mapRibToDraft)
+ return list.length ? list : [emptyRib()]
+})
// Draft comptable (tout null si l'utilisateur n'a pas accounting.view).
const accounting = computed(() => mapAccountingDraft(client.value ?? ({} as ClientDetail)))
diff --git a/frontend/modules/commercial/pages/clients/new.vue b/frontend/modules/commercial/pages/clients/new.vue
index f651225..8502eec 100644
--- a/frontend/modules/commercial/pages/clients/new.vue
+++ b/frontend/modules/commercial/pages/clients/new.vue
@@ -233,7 +233,7 @@
-
+
-
+
-
+
@@ -870,6 +870,8 @@ function addRib(): void {
function askRemoveRib(index: number): void {
askConfirm(t('commercial.clients.form.confirmDelete.rib'), () => {
ribs.value.splice(index, 1)
+ // Garde au moins un bloc RIB visible (cf. amorce au montage).
+ if (ribs.value.length === 0) ribs.value.push(emptyRib())
})
}
@@ -956,5 +958,8 @@ interface ContactResponse {
onMounted(() => {
// Echec du chargement des referentiels non bloquant : les selects restent vides.
referentials.loadCommon().catch(() => {})
+ // Au moins un bloc RIB toujours visible en creation : on amorce un bloc vide
+ // (non persiste tant qu'incomplet — RG-1.13).
+ if (ribs.value.length === 0) ribs.value.push(emptyRib())
})
diff --git a/frontend/public/coming-soon.gif b/frontend/public/coming-soon.gif
new file mode 100644
index 0000000..ef5f597
Binary files /dev/null and b/frontend/public/coming-soon.gif differ
diff --git a/frontend/shared/components/ui/ComingSoonPlaceholder.vue b/frontend/shared/components/ui/ComingSoonPlaceholder.vue
new file mode 100644
index 0000000..6d89eac
--- /dev/null
+++ b/frontend/shared/components/ui/ComingSoonPlaceholder.vue
@@ -0,0 +1,51 @@
+
+
+
+
![]()
+
+
🚧 👨💻 🚧
+
+
+
{{ resolvedTitle }}
+
{{ resolvedSubtitle }}
+
+
+
+
+