feat(campaigns/detail): edit draft params + jump to template editor
Two new buttons on the campaign detail page header — both visible only when campaign.status === 'draft' to keep operators from accidentally mutating a campaign mid-send. "Éditer les paramètres" → q-dialog with: - name (internal) - subject (the email Subject: line) - from (sender) - amount displayed in the body (overrides per-recipient default) - commitment_months - expiry text - template_fr / template_en dropdowns (refresh on popup-show so newly created templates show up without a page reload) Saves via the existing PATCH /campaigns/:id, which merges into params. A live load() refresh updates the Confirmation recap and any visible counters. "Éditer le template" → opens the Unlayer editor in a new tab on the campaign's configured template_fr (most TARGO customers FR). For campaign-specific tweaks the dialog tells the operator to create a variant template (+ Nouveau) and select it here. Addresses the gap a user hit on a reminder draft — they wanted to add a condition to the body before launching but had no edit affordance on the detail page. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
f414975b00
commit
e64e1e6a1f
|
|
@ -17,6 +17,19 @@
|
|||
:loading="creatingReminder" @click="confirmCreateReminder">
|
||||
<q-tooltip>Cloner cette campagne pour les destinataires qui n'ont PAS cliqué le cadeau encore</q-tooltip>
|
||||
</q-btn>
|
||||
<!-- Draft-only edit affordances: params dialog + jump to template
|
||||
editor. Both invisible once status flips off draft to keep
|
||||
operators from accidentally mutating a campaign mid-send. -->
|
||||
<q-btn v-if="campaign?.status === 'draft'" flat dense icon="palette" color="primary"
|
||||
label="Éditer le template" class="q-mr-sm"
|
||||
:href="templateEditorHref" target="_blank">
|
||||
<q-tooltip>Modifier le contenu HTML du template dans un nouvel onglet (Unlayer)</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn v-if="campaign?.status === 'draft'" flat dense icon="tune" color="primary"
|
||||
label="Éditer les paramètres" class="q-mr-sm"
|
||||
@click="openEditParams">
|
||||
<q-tooltip>Sujet, expéditeur, montant affiché, choix de template (FR/EN), etc.</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn v-if="campaign?.status === 'draft'" unelevated color="primary" icon="send" label="Lancer l'envoi"
|
||||
:loading="resending" @click="relaunch" />
|
||||
</div>
|
||||
|
|
@ -125,6 +138,59 @@
|
|||
<q-icon name="error_outline" size="48px" />
|
||||
<div class="text-h6 q-mt-md">Campagne introuvable</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit-params dialog. Mirrors a subset of the new-campaign wizard
|
||||
step 1 fields — everything that's safe to change pre-launch on
|
||||
a draft. Saves via PATCH /campaigns/:id which merges into
|
||||
campaign.params (the existing values stay if not touched). -->
|
||||
<q-dialog v-model="editParamsOpen" persistent>
|
||||
<q-card style="min-width: 560px; max-width: 720px">
|
||||
<q-card-section class="row items-center q-pb-none">
|
||||
<div class="text-h6"><q-icon name="tune" class="q-mr-sm" />Éditer les paramètres</div>
|
||||
<q-space />
|
||||
<q-btn flat dense round icon="close" v-close-popup />
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form @submit="saveEditParams" class="q-gutter-sm">
|
||||
<q-input v-model="editParams.name" label="Nom interne (visible uniquement dans l'admin)" outlined dense />
|
||||
<q-input v-model="editParams.subject" label="Sujet du courriel" outlined dense
|
||||
:rules="[v => !!v || 'requis']" />
|
||||
<q-input v-model="editParams.from" label="Expéditeur (From)" outlined dense
|
||||
placeholder="TARGO <noreply@targo.ca>" />
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-input v-model="editParams.amount" label="Montant affiché" outlined dense
|
||||
class="col-12 col-sm-4" placeholder="60 $" />
|
||||
<q-input v-model.number="editParams.commitment_months" type="number" min="1"
|
||||
label="Engagement (mois)" outlined dense class="col-12 col-sm-4" />
|
||||
<q-input v-model="editParams.expiry" label="Expiration (texte affiché)" outlined dense
|
||||
class="col-12 col-sm-4" placeholder="31 décembre 2026" />
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-select v-model="editParams.template_fr" :options="frTemplateOptions" emit-value map-options
|
||||
label="🇫🇷 Template français" outlined dense class="col-12 col-sm-6"
|
||||
:loading="loadingTemplates" @popup-show="loadTemplateLists" />
|
||||
<q-select v-model="editParams.template_en" :options="enTemplateOptions" emit-value map-options
|
||||
label="🇺🇸 Template anglais" outlined dense class="col-12 col-sm-6"
|
||||
:loading="loadingTemplates" @popup-show="loadTemplateLists" />
|
||||
</div>
|
||||
<div class="text-caption text-grey-7 q-mt-sm">
|
||||
<q-icon name="info" size="14px" /> Pour modifier le <strong>contenu</strong> du courriel
|
||||
(texte, mise en page, conditions, etc.), clique <strong>"Éditer le template"</strong>
|
||||
à côté — ça ouvre l'éditeur Unlayer dans un nouvel onglet. Les modifications de template
|
||||
s'appliquent à <strong>toutes les campagnes</strong> qui utilisent ce template ; pour
|
||||
une version unique à cette campagne, crée d'abord un template variant
|
||||
(<code>+ Nouveau</code> dans l'éditeur) et sélectionne-le ici.
|
||||
</div>
|
||||
<div class="row q-mt-md">
|
||||
<q-space />
|
||||
<q-btn flat label="Annuler" v-close-popup class="q-mr-sm" />
|
||||
<q-btn unelevated color="primary" type="submit" icon="save" label="Enregistrer"
|
||||
:loading="savingParams" />
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
|
|
@ -132,7 +198,7 @@
|
|||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { getCampaign, sendCampaign, campaignSseUrl, campaignReportCsvUrl, retryRecipient, createReminderCampaign } from 'src/api/campaigns'
|
||||
import { getCampaign, sendCampaign, campaignSseUrl, campaignReportCsvUrl, retryRecipient, createReminderCampaign, updateCampaign, listTemplates } from 'src/api/campaigns'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -143,6 +209,53 @@ const loading = ref(true)
|
|||
const resending = ref(false)
|
||||
const creatingReminder = ref(false)
|
||||
|
||||
// Edit-params dialog state. editParams is a snapshot of campaign.params
|
||||
// that the form mutates; on save we PATCH only that subset back to the
|
||||
// hub, which merges over the existing params on disk.
|
||||
const editParamsOpen = ref(false)
|
||||
const savingParams = ref(false)
|
||||
const editParams = ref({
|
||||
name: '', subject: '', from: '', amount: '', commitment_months: 3,
|
||||
expiry: '', template_fr: 'gift-email-fr', template_en: 'gift-email-en',
|
||||
})
|
||||
|
||||
// Template lists for the FR/EN dropdowns in the dialog. Refreshed lazily
|
||||
// when the dropdown opens so newly-created templates show up without a
|
||||
// page reload.
|
||||
const loadingTemplates = ref(false)
|
||||
const frTemplateOptions = ref([{ label: 'gift-email-fr', value: 'gift-email-fr' }])
|
||||
const enTemplateOptions = ref([{ label: 'gift-email-en', value: 'gift-email-en' }])
|
||||
async function loadTemplateLists () {
|
||||
loadingTemplates.value = true
|
||||
try {
|
||||
const tpls = await listTemplates()
|
||||
const opt = (t, preferred) => ({
|
||||
label: preferred ? t.name : `${t.name} · sans suffixe de langue`,
|
||||
value: t.name,
|
||||
})
|
||||
const fr = [], en = []
|
||||
for (const t of tpls) {
|
||||
if (!t?.name) continue
|
||||
if (t.name.endsWith('-fr')) fr.push(opt(t, true))
|
||||
else if (t.name.endsWith('-en')) en.push(opt(t, true))
|
||||
else { fr.push(opt(t, false)); en.push(opt(t, false)) }
|
||||
}
|
||||
const sortFn = (a, b) => {
|
||||
const aPref = !a.label.includes('sans suffixe')
|
||||
const bPref = !b.label.includes('sans suffixe')
|
||||
if (aPref !== bPref) return aPref ? -1 : 1
|
||||
return a.value.localeCompare(b.value)
|
||||
}
|
||||
fr.sort(sortFn); en.sort(sortFn)
|
||||
if (fr.length) frTemplateOptions.value = fr
|
||||
if (en.length) enTemplateOptions.value = en
|
||||
} catch (e) {
|
||||
$q.notify({ type: 'warning', message: 'Templates non chargés : ' + e.message })
|
||||
} finally {
|
||||
loadingTemplates.value = false
|
||||
}
|
||||
}
|
||||
|
||||
let es = null
|
||||
|
||||
const columns = [
|
||||
|
|
@ -185,6 +298,14 @@ function giftbitAdminUrl (giftUrl) {
|
|||
|
||||
const reportCsvUrl = computed(() => campaignReportCsvUrl(id))
|
||||
|
||||
// Deep link to the template editor for the FR template configured on
|
||||
// this campaign (most TARGO customers are FR). The editor route also
|
||||
// accepts no name and lets the user pick from a dropdown.
|
||||
const templateEditorHref = computed(() => {
|
||||
const t = campaign.value?.params?.template_fr || 'gift-email-fr'
|
||||
return `/ops/#/campaigns/templates/${encodeURIComponent(t)}`
|
||||
})
|
||||
|
||||
// Eligibility for the "Créer une relance" button: campaign sent at least
|
||||
// once (not draft), it's not itself a reminder, and at least one recipient
|
||||
// is in a non-clicked / non-revoked / non-expired state. The hub re-checks
|
||||
|
|
@ -283,6 +404,53 @@ async function retryFailedRow (row) {
|
|||
}
|
||||
}
|
||||
|
||||
// Open the params dialog, snapshotting current campaign.params + name
|
||||
// into the editable form. Lazily load the template lists so the
|
||||
// dropdowns are populated when the dialog mounts.
|
||||
function openEditParams () {
|
||||
const p = campaign.value?.params || {}
|
||||
editParams.value = {
|
||||
name: campaign.value?.name || '',
|
||||
subject: p.subject || '',
|
||||
from: p.from || '',
|
||||
amount: p.amount || '',
|
||||
commitment_months: p.commitment_months || 3,
|
||||
expiry: p.expiry || '',
|
||||
template_fr: p.template_fr || 'gift-email-fr',
|
||||
template_en: p.template_en || 'gift-email-en',
|
||||
}
|
||||
editParamsOpen.value = true
|
||||
loadTemplateLists()
|
||||
}
|
||||
|
||||
// PATCH the campaign with the new name + params. Hub merges params over
|
||||
// the existing ones (spread) so unspecified fields stay put. Live UI
|
||||
// refresh via load() so the recap + counters reflect the new values.
|
||||
async function saveEditParams () {
|
||||
savingParams.value = true
|
||||
try {
|
||||
await updateCampaign(id, {
|
||||
name: editParams.value.name,
|
||||
params: {
|
||||
subject: editParams.value.subject,
|
||||
from: editParams.value.from,
|
||||
amount: editParams.value.amount,
|
||||
commitment_months: editParams.value.commitment_months,
|
||||
expiry: editParams.value.expiry,
|
||||
template_fr: editParams.value.template_fr,
|
||||
template_en: editParams.value.template_en,
|
||||
},
|
||||
})
|
||||
await load()
|
||||
editParamsOpen.value = false
|
||||
$q.notify({ type: 'positive', message: 'Paramètres enregistrés' })
|
||||
} catch (e) {
|
||||
$q.notify({ type: 'negative', message: 'Erreur : ' + e.message })
|
||||
} finally {
|
||||
savingParams.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Confirmation + creation of the reminder campaign. The hub returns the
|
||||
// new draft campaign; we navigate to its detail page so the operator can
|
||||
// review the recipients list and click "Lancer l'envoi" when ready.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user