planif (feuille job mobile) : commentaire interne par défaut — plus de courriel client accidentel

Incident : un employé a envoyé un courriel au client en croyant ajouter un commentaire
privé au fil du ticket. Le composeur de la feuille (jobSheet) s'ouvrait en mode « Réponse ·
courriel » (public) par défaut.

- openJobSheet : `noteMode` défaut = TRUE (commentaire INTERNE) au lieu de false (réponse client) ;
  ouverture en mode réponse seulement si `{ reply: true }` explicite (aucun appelant ne le fait)
- bascule réordonnée : « Note interne » (1re, défaut) · « Répondre au client » (2e, explicite) ;
  couleur d'alerte (deep-orange) quand la réponse client est sélectionnée
- bouton d'envoi : « Enregistrer la note » (interne, neutre) vs « Envoyer au client » (orange) ;
  indice/placeholder clarifiés (« ajoutée au fil, jamais envoyée au client » vs « ⚠ ENVOYÉE au client »)
- répondre au client reste possible mais = choix explicite (aligne le comportement sûr déjà
  présent dans le panneau détail complet : case « Envoyer aussi au client » jdReplyPublic par défaut off)
- Vérifié en aperçu sur un job AVEC courriel client : le composeur s'ouvre en « Note interne »

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-07 23:43:53 -04:00
parent d8325f7e55
commit 23e3c0d2e2

View File

@ -359,15 +359,15 @@
<!-- Conversation client UNIFIÉE UN seul champ : Réponse au client (Outbox courriel/SMS) OU 📌 Note importante (épinglée au job). Fil le plus récent en premier. --> <!-- Conversation client UNIFIÉE UN seul champ : Réponse au client (Outbox courriel/SMS) OU 📌 Note importante (épinglée au job). Fil le plus récent en premier. -->
<div class="pm-opt-label">Conversation client <span v-if="sheetThread.messages.length" class="text-grey-5">({{ sheetThread.messages.length }})</span></div> <div class="pm-opt-label">Conversation client <span v-if="sheetThread.messages.length" class="text-grey-5">({{ sheetThread.messages.length }})</span></div>
<div class="pm-postbox"> <div class="pm-postbox">
<q-input dense outlined v-model="composerText" type="textarea" rows="2" :placeholder="noteMode ? 'Note importante — épinglée au job (ex. appeler 30 min avant · préfère AM)…' : (replyChannel.ok ? ('Réponse au client par ' + replyChannel.label + '…') : 'Réponse au client…')" /> <q-input dense outlined v-model="composerText" type="textarea" rows="2" :placeholder="noteMode ? 'Note interne — ajoutée au fil du ticket, jamais envoyée au client…' : (replyChannel.ok ? ('Réponse ENVOYÉE au client par ' + replyChannel.label + '…') : 'Réponse au client…')" />
<div class="row items-center q-mt-xs q-gutter-xs"> <div class="row items-center q-mt-xs q-gutter-xs">
<q-btn-toggle v-model="noteMode" dense unelevated no-caps size="sm" :options="[{ label: replyChannel.ok ? ('Réponse · ' + replyChannel.label) : 'Réponse', value: false, icon: 'reply', disable: !replyChannel.ok }, { label: 'Note importante', value: true, icon: 'push_pin' }]" toggle-color="primary" color="grey-3" text-color="grey-8" /> <q-btn-toggle v-model="noteMode" dense unelevated no-caps size="sm" :options="[{ label: 'Note interne', value: true, icon: 'sticky_note_2' }, { label: replyChannel.ok ? ('Répondre au client · ' + replyChannel.label) : 'Répondre au client', value: false, icon: 'reply', disable: !replyChannel.ok }]" :toggle-color="noteMode ? 'blue-grey-7' : 'deep-orange'" color="grey-3" text-color="grey-8" />
<q-space /> <q-space />
<q-btn dense unelevated no-caps size="sm" :color="noteMode ? 'deep-orange-6' : 'teal-7'" :icon="noteMode ? 'push_pin' : 'send'" :label="noteMode ? 'Enregistrer' : 'Envoyer'" :loading="postBusy" :disable="noteMode ? false : !postDraft.trim()" @mousedown.prevent="submitComposer()" /> <q-btn dense unelevated no-caps size="sm" :color="noteMode ? 'blue-grey-7' : 'deep-orange'" :icon="noteMode ? 'sticky_note_2' : 'send'" :label="noteMode ? 'Enregistrer la note' : 'Envoyer au client'" :loading="postBusy" :disable="noteMode ? false : !postDraft.trim()" @mousedown.prevent="submitComposer()" />
</div> </div>
<div class="row items-center q-mt-xs pm-postbox-foot"> <div class="row items-center q-mt-xs pm-postbox-foot">
<q-icon :name="noteMode ? 'push_pin' : (replyChannel.ok ? 'public' : 'block')" size="12px" :color="noteMode ? 'deep-orange-6' : (replyChannel.ok ? 'teal-7' : 'grey-5')" /> <q-icon :name="noteMode ? 'push_pin' : (replyChannel.ok ? 'public' : 'block')" size="12px" :color="noteMode ? 'deep-orange-6' : (replyChannel.ok ? 'teal-7' : 'grey-5')" />
<span class="pm-postbox-hint">{{ noteMode ? 'Note interne épinglée au job — jamais envoyée au client' : (replyChannel.ok ? ('Envoyée au client par ' + replyChannel.label + (jobSheet.job.legacy_ticket_id ? ' · consignée au ticket' : '')) : 'Aucun courriel/téléphone au dossier client') }}</span> <span class="pm-postbox-hint">{{ noteMode ? 'Note interne — ajoutée au fil du ticket, jamais envoyée au client' : (replyChannel.ok ? ('⚠ ENVOYÉE au client par ' + replyChannel.label + (jobSheet.job.legacy_ticket_id ? ' · consignée au ticket' : '')) : 'Aucun courriel/téléphone au dossier client') }}</span>
<q-space /> <q-space />
<q-btn flat dense no-caps size="sm" color="primary" icon="open_in_new" label="Fil complet" @click="openFullThread()" /> <q-btn flat dense no-caps size="sm" color="primary" icon="open_in_new" label="Fil complet" @click="openFullThread()" />
</div> </div>
@ -5499,7 +5499,7 @@ async function loadSheetThread (j) {
} catch (e) { /* fil indisponible */ } } catch (e) { /* fil indisponible */ }
if (jobSheet.job && jobSheet.job.name === tok) sheetThread.loading = false if (jobSheet.job && jobSheet.job.name === tok) sheetThread.loading = false
} }
function openJobSheet (j, opts) { jobSheet.job = j; snoozeDate.value = addDaysISO(todayISO(), 7); sheetNoteText.value = j.notes || ''; postDraft.value = ''; noteMode.value = !!(opts && opts.note); serviceCheck.done = false; serviceCheck.loading = false; jobSheet.open = true; loadSheetThread(j) } function openJobSheet (j, opts) { jobSheet.job = j; snoozeDate.value = addDaysISO(todayISO(), 7); sheetNoteText.value = j.notes || ''; postDraft.value = ''; noteMode.value = !(opts && opts.reply); /* défaut SÛR : commentaire INTERNE (jamais un courriel accidentel) — « Répondre au client » = bascule explicite */ serviceCheck.done = false; serviceCheck.loading = false; jobSheet.open = true; loadSheetThread(j) }
// Auto-save de la note à la FERMETURE de la feuille (X · clic extérieur · bouton Fermer · Échap) uniquement si modifiée. Plus de bouton Enregistrer. // Auto-save de la note à la FERMETURE de la feuille (X · clic extérieur · bouton Fermer · Échap) uniquement si modifiée. Plus de bouton Enregistrer.
function onJobSheetHide () { const j = jobSheet.job; if (j && (sheetNoteText.value || '') !== (j.notes || '')) patchJob(j, { notes: sheetNoteText.value || '' }, 'Note enregistrée') } function onJobSheetHide () { const j = jobSheet.job; if (j && (sheetNoteText.value || '') !== (j.notes || '')) patchJob(j, { notes: sheetNoteText.value || '' }, 'Note enregistrée') }
function openNote (j) { noteDialog.job = j; noteDialog.text = j.notes || ''; noteDialog.open = true } function openNote (j) { noteDialog.job = j; noteDialog.text = j.notes || ''; noteDialog.open = true }