@dev.smartpricing/message-composer-layer 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/Composer/BrandList.vue +190 -0
- package/app/components/Composer/EmailComposer.vue +501 -0
- package/app/components/Composer/EmailContent.vue +95 -0
- package/app/components/Composer/EmailTranslations.vue +70 -0
- package/app/components/Composer/ImageGallery.vue +141 -0
- package/app/components/Composer/MessageLibrary.vue +155 -0
- package/app/components/Composer/WhatsappComposer.vue +424 -0
- package/app/components/Composer/WhatsappContent.vue +88 -0
- package/app/components/Composer/WhatsappTranslations.vue +68 -0
- package/app/pages/brands.vue +7 -176
- package/app/pages/images.vue +1 -126
- package/app/pages/index.vue +6 -138
- package/app/pages/template/email/[id].vue +16 -545
- package/app/pages/template/email/create.vue +13 -514
- package/app/pages/template/whatsapp/[id].vue +21 -509
- package/app/pages/template/whatsapp/create.vue +13 -426
- package/app/stores/emailComposer.ts +188 -0
- package/app/stores/whatsappComposer.ts +173 -0
- package/package.json +2 -2
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import IncompleteTranslationsDialog from '@/components/Common/IncompleteTranslationsDialog.vue'
|
|
3
|
+
import { computed, ref, watch, onUnmounted } from 'vue'
|
|
4
|
+
import type { WhatsappMessage, PostEntity } from '@dev.smartpricing/message-composer-utils/types'
|
|
5
|
+
import { useAppContextStore } from '@/stores/appContext'
|
|
6
|
+
import { useMetaSubmissionStore } from '@/stores/metaSubmission'
|
|
7
|
+
import { useMessageGroupQuery, useLanguagesQuery } from '~/queries'
|
|
8
|
+
import type { WaHeaderType } from '~/types/tracking'
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
mode: 'create' | 'edit'
|
|
12
|
+
messageGroupId?: string
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
saved: [data: { id: string | number; name: string; status?: string }]
|
|
17
|
+
cancelled: [data?: { id?: string | number }]
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const store = useWhatsappComposerStore()
|
|
21
|
+
const { mainLanguage } = storeToRefs(useAppContextStore())
|
|
22
|
+
const { trackEvent } = useTracking()
|
|
23
|
+
const { goBackOrFallback } = useGoBack()
|
|
24
|
+
const { submitToMeta } = useMetaSubmissionStore()
|
|
25
|
+
|
|
26
|
+
// ── Init ──────────────────────────────────────────────────────────
|
|
27
|
+
if (props.mode === 'create') {
|
|
28
|
+
store.initCreate(mainLanguage.value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Edit mode: fetch message group ────────────────────────────────
|
|
32
|
+
const { data: messageGroupData, isPending: isFetching } =
|
|
33
|
+
props.mode === 'edit' && props.messageGroupId
|
|
34
|
+
? useMessageGroupQuery(props.messageGroupId)
|
|
35
|
+
: { data: ref(null), isPending: ref(false) }
|
|
36
|
+
|
|
37
|
+
// ── Change tracking ───────────────────────────────────────────────
|
|
38
|
+
const changeTracking = useChangeTracking(computed(() => store.changeTrackingId))
|
|
39
|
+
|
|
40
|
+
// Record initial state for create mode
|
|
41
|
+
if (props.mode === 'create' && store.currentMessage) {
|
|
42
|
+
changeTracking.recordOriginalState(mainLanguage.value, store.currentMessage as WhatsappMessage)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Watch messageGroupData to init edit mode
|
|
46
|
+
if (props.mode === 'edit') {
|
|
47
|
+
watch(
|
|
48
|
+
messageGroupData,
|
|
49
|
+
(data) => {
|
|
50
|
+
if (!data?.messages) return
|
|
51
|
+
|
|
52
|
+
store.initEdit(data, mainLanguage.value)
|
|
53
|
+
|
|
54
|
+
// Record original states for change tracking
|
|
55
|
+
for (const message of data.messages) {
|
|
56
|
+
if (message.render_type === 'whatsapp_v1') {
|
|
57
|
+
changeTracking.recordOriginalState(message.language_id, message as WhatsappMessage)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{ immediate: true },
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── WhatsApp template behaviors ───────────────────────────────────
|
|
66
|
+
const { ensureUnsubscribeButton } = useWhatsappTemplateBehaviors({
|
|
67
|
+
category: computed(() => store.category),
|
|
68
|
+
messages: computed(() => store.messages),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
if (props.mode === 'create') {
|
|
72
|
+
ensureUnsubscribeButton()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Validation ────────────────────────────────────────────────────
|
|
76
|
+
const messagesRef = computed(() => store.messages)
|
|
77
|
+
const { formState, draftSchema, validationErrors, validateDraft, validateFull, clearErrors } =
|
|
78
|
+
useTemplateFormValidation({
|
|
79
|
+
type: 'whatsapp',
|
|
80
|
+
name: computed(() => store.name),
|
|
81
|
+
category: computed(() => store.category),
|
|
82
|
+
messages: messagesRef,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// ── Template operations ───────────────────────────────────────────
|
|
86
|
+
const successFlag = ref(false)
|
|
87
|
+
|
|
88
|
+
const { saving, createTemplate, updateTemplate } = useTemplateOperations({
|
|
89
|
+
onSuccess: () => {
|
|
90
|
+
successFlag.value = true
|
|
91
|
+
changeTracking.clearAllChanges()
|
|
92
|
+
if (props.mode === 'create') {
|
|
93
|
+
goBackOrFallback()
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// ── Change detection ──────────────────────────────────────────────
|
|
99
|
+
const hasAnyChanges = computed(() => {
|
|
100
|
+
if (store.name !== store.originalName) return true
|
|
101
|
+
if (store.category !== store.originalCategory) return true
|
|
102
|
+
|
|
103
|
+
for (const languageId in store.messages) {
|
|
104
|
+
const message = store.messages[languageId]
|
|
105
|
+
if (message && changeTracking.hasChanges(languageId, message as WhatsappMessage)) {
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
useExitConfirmation(computed(() => hasAnyChanges.value && !successFlag.value))
|
|
113
|
+
|
|
114
|
+
// ── Incomplete translations ───────────────────────────────────────
|
|
115
|
+
const { data: languages } = useLanguagesQuery()
|
|
116
|
+
|
|
117
|
+
const { incompleteLanguages, hasIncompleteTranslations } = useIncompleteTranslations({
|
|
118
|
+
messages: computed(() => store.messages as Record<string, WhatsappMessage>),
|
|
119
|
+
languages,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const showIncompleteDialog = ref(false)
|
|
123
|
+
const pendingAction = ref<'save_draft' | 'save_as_new' | 'request_approval' | null>(null)
|
|
124
|
+
|
|
125
|
+
// ── Auto-translate ────────────────────────────────────────────────
|
|
126
|
+
const { translateMessage, isTranslating } = useAutoTranslate()
|
|
127
|
+
|
|
128
|
+
// ── Cleanup ───────────────────────────────────────────────────────
|
|
129
|
+
onUnmounted(() => {
|
|
130
|
+
store.$reset()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// ── Tracking helpers ──────────────────────────────────────────────
|
|
134
|
+
const trackPrefix = computed(() => (props.mode === 'create' ? 'wa_create' : 'wa_edit'))
|
|
135
|
+
|
|
136
|
+
function getWaBaseProps() {
|
|
137
|
+
return { name: store.name, category: store.category }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getWaDraftProps() {
|
|
141
|
+
const resolved = resolveDefaultLanguage(store.messages, mainLanguage.value)
|
|
142
|
+
const msg = resolved ? (store.messages[resolved] as WhatsappMessage) : undefined
|
|
143
|
+
return {
|
|
144
|
+
...getWaBaseProps(),
|
|
145
|
+
languages: Object.keys(store.messages),
|
|
146
|
+
header: (msg?.body?.header?.type ?? 'none') as WaHeaderType,
|
|
147
|
+
footer: !!msg?.body?.footer?.enabled,
|
|
148
|
+
buttons: msg?.body?.buttons?.buttons?.length ?? 0,
|
|
149
|
+
is_internal: store.isInternal,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
watch(
|
|
154
|
+
() => store.activeTab,
|
|
155
|
+
(tab) => {
|
|
156
|
+
trackEvent(`${trackPrefix.value}.change_tab`, { ...getWaBaseProps(), tab })
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
// Edit mode: watch for mainLanguage resolution
|
|
161
|
+
if (props.mode === 'edit') {
|
|
162
|
+
watch(
|
|
163
|
+
() => store.messages[mainLanguage.value],
|
|
164
|
+
() => {
|
|
165
|
+
const resolved = resolveDefaultLanguage(store.messages, mainLanguage.value)
|
|
166
|
+
if (resolved && resolved !== mainLanguage.value) {
|
|
167
|
+
store.language = resolved
|
|
168
|
+
store.targetLanguage = resolved
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Save logic ────────────────────────────────────────────────────
|
|
175
|
+
async function saveDraft() {
|
|
176
|
+
trackEvent(`${trackPrefix.value}.save_draft`, getWaDraftProps())
|
|
177
|
+
|
|
178
|
+
if (props.mode === 'create') {
|
|
179
|
+
const result = await createTemplate(
|
|
180
|
+
{ messageGroup: store.messageGroupData, messages: store.messages },
|
|
181
|
+
'draft',
|
|
182
|
+
)
|
|
183
|
+
if (result) {
|
|
184
|
+
emit('saved', { id: result.id, name: result.name })
|
|
185
|
+
notifyParent('TEMPLATE_SAVED_DRAFT', { id: result.id, name: result.name })
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
if (!store.messageGroupId) return
|
|
189
|
+
await updateTemplate(store.messageGroupId, {
|
|
190
|
+
messageGroup: store.messageGroupUpdateData,
|
|
191
|
+
messages: store.messages,
|
|
192
|
+
})
|
|
193
|
+
emit('saved', { id: store.messageGroupId, name: store.name })
|
|
194
|
+
notifyParent('TEMPLATE_SAVED_DRAFT', { id: store.messageGroupId, name: store.name })
|
|
195
|
+
goBackOrFallback()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function saveAsNew() {
|
|
200
|
+
if (!store.messageGroupId) return
|
|
201
|
+
trackEvent(`${trackPrefix.value}.save_as_new`, getWaDraftProps())
|
|
202
|
+
|
|
203
|
+
const cleanedMessages: Record<string, PostEntity<WhatsappMessage>> = {}
|
|
204
|
+
for (const [languageId, message] of Object.entries(store.messages)) {
|
|
205
|
+
const { id, message_group_id, created_at, updated_at, ...templateMessage } =
|
|
206
|
+
message as WhatsappMessage
|
|
207
|
+
cleanedMessages[languageId] = {
|
|
208
|
+
...templateMessage,
|
|
209
|
+
message_group_id: '',
|
|
210
|
+
metadata: {},
|
|
211
|
+
} as PostEntity<WhatsappMessage>
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const result = await createTemplate(
|
|
215
|
+
{ messageGroup: store.messageGroupUpdateData, messages: cleanedMessages },
|
|
216
|
+
'draft',
|
|
217
|
+
)
|
|
218
|
+
if (result) {
|
|
219
|
+
emit('saved', { id: result.id, name: result.name })
|
|
220
|
+
notifyParent('TEMPLATE_SAVED_AS_NEW', { id: result.id, name: result.name })
|
|
221
|
+
navigateTo({ name: 'whatsapp-message-edit', params: { id: result.id } }, { replace: true })
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function requestApproval() {
|
|
226
|
+
trackEvent(`${trackPrefix.value}.request_approval`, getWaDraftProps())
|
|
227
|
+
|
|
228
|
+
if (props.mode === 'create') {
|
|
229
|
+
const result = await createTemplate(
|
|
230
|
+
{ messageGroup: store.messageGroupData, messages: store.messages },
|
|
231
|
+
'draft',
|
|
232
|
+
)
|
|
233
|
+
if (result) {
|
|
234
|
+
await submitToMeta(result.id.toString())
|
|
235
|
+
emit('saved', { id: result.id, name: result.name })
|
|
236
|
+
notifyParent('TEMPLATE_APPROVAL_REQUESTED', { id: result.id, name: result.name })
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
if (!store.messageGroupId) return
|
|
240
|
+
await updateTemplate(store.messageGroupId, {
|
|
241
|
+
messageGroup: store.messageGroupUpdateData,
|
|
242
|
+
messages: store.messages,
|
|
243
|
+
})
|
|
244
|
+
await submitToMeta(store.messageGroupId)
|
|
245
|
+
emit('saved', { id: store.messageGroupId, name: store.name })
|
|
246
|
+
notifyParent('TEMPLATE_APPROVAL_REQUESTED', { id: store.messageGroupId, name: store.name })
|
|
247
|
+
goBackOrFallback()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Public action handlers ────────────────────────────────────────
|
|
252
|
+
async function handleSaveDraft() {
|
|
253
|
+
if (!(await validateDraft())) return
|
|
254
|
+
if (hasIncompleteTranslations.value) {
|
|
255
|
+
pendingAction.value = 'save_draft'
|
|
256
|
+
showIncompleteDialog.value = true
|
|
257
|
+
} else {
|
|
258
|
+
saveDraft()
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function handleSaveAsNew() {
|
|
263
|
+
if (!(await validateDraft())) return
|
|
264
|
+
if (hasIncompleteTranslations.value) {
|
|
265
|
+
pendingAction.value = 'save_as_new'
|
|
266
|
+
showIncompleteDialog.value = true
|
|
267
|
+
} else {
|
|
268
|
+
saveAsNew()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function handleRequestApproval() {
|
|
273
|
+
if (!(await validateFull())) return
|
|
274
|
+
if (hasIncompleteTranslations.value) {
|
|
275
|
+
pendingAction.value = 'request_approval'
|
|
276
|
+
showIncompleteDialog.value = true
|
|
277
|
+
} else {
|
|
278
|
+
requestApproval()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function handleCancel() {
|
|
283
|
+
const data =
|
|
284
|
+
props.mode === 'edit' && store.messageGroupId ? { id: store.messageGroupId } : undefined
|
|
285
|
+
emit('cancelled', data)
|
|
286
|
+
notifyParent('TEMPLATE_CANCELLED', data ? { id: data.id } : undefined)
|
|
287
|
+
goBackOrFallback()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function proceedWithSave() {
|
|
291
|
+
if (pendingAction.value === 'save_draft') {
|
|
292
|
+
saveDraft()
|
|
293
|
+
} else if (pendingAction.value === 'save_as_new') {
|
|
294
|
+
saveAsNew()
|
|
295
|
+
} else if (pendingAction.value === 'request_approval') {
|
|
296
|
+
requestApproval()
|
|
297
|
+
}
|
|
298
|
+
pendingAction.value = null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function goToTranslations() {
|
|
302
|
+
store.activeTab = 'translations'
|
|
303
|
+
pendingAction.value = null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function translateAndSave() {
|
|
307
|
+
if (!incompleteLanguages.value.length) {
|
|
308
|
+
proceedWithSave()
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const baseMessage = store.messages[store.language]
|
|
313
|
+
if (!baseMessage) {
|
|
314
|
+
proceedWithSave()
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const { language: lang } of incompleteLanguages.value) {
|
|
319
|
+
const targetMsg = store.messages[lang.id]
|
|
320
|
+
if (!targetMsg) continue
|
|
321
|
+
|
|
322
|
+
await translateMessage(
|
|
323
|
+
baseMessage as WhatsappMessage,
|
|
324
|
+
targetMsg as WhatsappMessage,
|
|
325
|
+
store.language,
|
|
326
|
+
lang.id,
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
proceedWithSave()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Tracking event forwarders ─────────────────────────────────────
|
|
334
|
+
function onAddLanguage(lang: string) {
|
|
335
|
+
trackEvent(`${trackPrefix.value}.add_language`, { ...getWaBaseProps(), language: lang })
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function onRemoveLanguage(lang: string) {
|
|
339
|
+
trackEvent(`${trackPrefix.value}.remove_language`, { ...getWaBaseProps(), language: lang })
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function onAutoTranslateTracked(lang: string) {
|
|
343
|
+
trackEvent(`${trackPrefix.value}.auto_translate`, { ...getWaBaseProps(), language: lang })
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function onPreview(lang: string) {
|
|
347
|
+
trackEvent(`${trackPrefix.value}.preview`, { ...getWaBaseProps(), language: lang })
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function onFiltersChanged(payload: {
|
|
351
|
+
language: string
|
|
352
|
+
search_term: string
|
|
353
|
+
show_only_missing: boolean
|
|
354
|
+
show_only_changes: boolean
|
|
355
|
+
}) {
|
|
356
|
+
trackEvent(`${trackPrefix.value}.translations_filters`, { ...getWaBaseProps(), ...payload })
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── Expose ────────────────────────────────────────────────────────
|
|
360
|
+
defineExpose({
|
|
361
|
+
handleSaveDraft,
|
|
362
|
+
handleSaveAsNew,
|
|
363
|
+
handleRequestApproval,
|
|
364
|
+
handleCancel,
|
|
365
|
+
saving,
|
|
366
|
+
isTranslating,
|
|
367
|
+
validationErrors,
|
|
368
|
+
isPending: store.isPending,
|
|
369
|
+
isApproved: store.isApproved,
|
|
370
|
+
})
|
|
371
|
+
</script>
|
|
372
|
+
|
|
373
|
+
<template>
|
|
374
|
+
<UForm
|
|
375
|
+
ref="templateForm"
|
|
376
|
+
:schema="draftSchema"
|
|
377
|
+
:state="formState"
|
|
378
|
+
:validate-on="[]"
|
|
379
|
+
class="flex-1 relative min-h-0 flex flex-col"
|
|
380
|
+
>
|
|
381
|
+
<div v-if="validationErrors.length">
|
|
382
|
+
<UAlert
|
|
383
|
+
color="error"
|
|
384
|
+
variant="subtle"
|
|
385
|
+
icon="i-lucide-alert-triangle"
|
|
386
|
+
:title="$t('editor.validation.error.title')"
|
|
387
|
+
:close="true"
|
|
388
|
+
@update:open="clearErrors()"
|
|
389
|
+
>
|
|
390
|
+
<template #description>
|
|
391
|
+
<ul class="list-disc pl-4 space-y-1">
|
|
392
|
+
<li v-for="err in validationErrors" :key="err">
|
|
393
|
+
{{ err }}
|
|
394
|
+
</li>
|
|
395
|
+
</ul>
|
|
396
|
+
</template>
|
|
397
|
+
</UAlert>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<ComposerWhatsappContent
|
|
401
|
+
v-if="store.activeTab === 'content'"
|
|
402
|
+
:mode="mode"
|
|
403
|
+
@category-changed="ensureUnsubscribeButton"
|
|
404
|
+
/>
|
|
405
|
+
|
|
406
|
+
<ComposerWhatsappTranslations
|
|
407
|
+
v-if="store.activeTab === 'translations'"
|
|
408
|
+
:mode="mode"
|
|
409
|
+
@add-language="onAddLanguage"
|
|
410
|
+
@remove-language="onRemoveLanguage"
|
|
411
|
+
@auto-translate-tracked="onAutoTranslateTracked"
|
|
412
|
+
@preview="onPreview"
|
|
413
|
+
@filters-changed="onFiltersChanged"
|
|
414
|
+
/>
|
|
415
|
+
</UForm>
|
|
416
|
+
|
|
417
|
+
<IncompleteTranslationsDialog
|
|
418
|
+
v-model="showIncompleteDialog"
|
|
419
|
+
:incomplete-languages="incompleteLanguages"
|
|
420
|
+
@save-without-translating="proceedWithSave"
|
|
421
|
+
@translate-and-save="translateAndSave"
|
|
422
|
+
@go-to-translations="goToTranslations"
|
|
423
|
+
/>
|
|
424
|
+
</template>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import WhatsappEditorSyncWrapper from '@/components/Whatsapp/Editor/SyncWrapper.vue'
|
|
3
|
+
import WhatsappPreview from '@/components/Whatsapp/PreviewPanel.vue'
|
|
4
|
+
import CategoryChanger from '@/components/Whatsapp/CategoryChanger.vue'
|
|
5
|
+
import type { WhatsappMessage, PostEntity } from '@dev.smartpricing/message-composer-utils/types'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
mode: 'create' | 'edit'
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{
|
|
12
|
+
'category-changed': []
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const store = useWhatsappComposerStore()
|
|
16
|
+
const adminStore = useAdminStore()
|
|
17
|
+
|
|
18
|
+
const testIds = computed(() =>
|
|
19
|
+
props.mode === 'create' ? templateWhatsappCreateTestIds : templateWhatsappEditTestIds,
|
|
20
|
+
)
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<UCard
|
|
25
|
+
v-if="store.currentMessage"
|
|
26
|
+
:ui="{
|
|
27
|
+
root: 'flex flex-col',
|
|
28
|
+
body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0',
|
|
29
|
+
header: 'sm:px-4 px-4 py-4 sm:py-4',
|
|
30
|
+
}"
|
|
31
|
+
:data-testid="testIds.contentCard"
|
|
32
|
+
>
|
|
33
|
+
<template #header>
|
|
34
|
+
<div class="grid grid-cols-3 gap-2">
|
|
35
|
+
<UFieldGroup>
|
|
36
|
+
<UBadge variant="outline" color="neutral" size="lg">{{
|
|
37
|
+
$t(`pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.name`)
|
|
38
|
+
}}</UBadge>
|
|
39
|
+
<UInput
|
|
40
|
+
v-model="store.name"
|
|
41
|
+
:placeholder="
|
|
42
|
+
$t(
|
|
43
|
+
`pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.name_placeholder`,
|
|
44
|
+
)
|
|
45
|
+
"
|
|
46
|
+
:data-testid="testIds.nameInput"
|
|
47
|
+
/>
|
|
48
|
+
</UFieldGroup>
|
|
49
|
+
|
|
50
|
+
<UFieldGroup>
|
|
51
|
+
<UBadge variant="outline" color="neutral" size="lg">{{
|
|
52
|
+
$t(`pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.category`)
|
|
53
|
+
}}</UBadge>
|
|
54
|
+
<CategoryChanger v-model:category="store.category" @changed="emit('category-changed')" />
|
|
55
|
+
</UFieldGroup>
|
|
56
|
+
|
|
57
|
+
<USwitch
|
|
58
|
+
v-if="mode === 'create' && adminStore.isAdminModeActive"
|
|
59
|
+
v-model="store.isInternal"
|
|
60
|
+
:label="$t('common.internal')"
|
|
61
|
+
color="info"
|
|
62
|
+
:data-testid="testIds.internalSwitch"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<div class="grid grid-cols-3 divide-x divide-muted overflow-auto max-h-full h-full">
|
|
68
|
+
<div class="col-span-2 overflow-y-auto pt-4 pb-4" v-if="store.currentMessage">
|
|
69
|
+
<h2 class="px-4 pb-4 label-lg">
|
|
70
|
+
{{
|
|
71
|
+
$t(
|
|
72
|
+
`pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.message_content`,
|
|
73
|
+
)
|
|
74
|
+
}}
|
|
75
|
+
</h2>
|
|
76
|
+
<WhatsappEditorSyncWrapper
|
|
77
|
+
v-model="store.messages[store.language]"
|
|
78
|
+
:messages="store.messages"
|
|
79
|
+
:main-language-id="store.language"
|
|
80
|
+
:key="store.language"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="col-span-1 overflow-y-auto" v-if="store.currentMessage">
|
|
84
|
+
<WhatsappPreview :message="store.currentMessage" />
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</UCard>
|
|
88
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import TranslationEditor from '@/components/Whatsapp/TranslationEditor.vue'
|
|
3
|
+
import LanguageList from '@/components/Common/LanguageList.vue'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
mode: 'create' | 'edit'
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const emit = defineEmits<{
|
|
10
|
+
'add-language': [lang: string]
|
|
11
|
+
'remove-language': [lang: string]
|
|
12
|
+
'auto-translate-tracked': [lang: string]
|
|
13
|
+
preview: [lang: string]
|
|
14
|
+
'filters-changed': [
|
|
15
|
+
payload: {
|
|
16
|
+
language: string
|
|
17
|
+
search_term: string
|
|
18
|
+
show_only_missing: boolean
|
|
19
|
+
show_only_changes: boolean
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const store = useWhatsappComposerStore()
|
|
25
|
+
|
|
26
|
+
const testIds = computed(() =>
|
|
27
|
+
props.mode === 'create' ? templateWhatsappCreateTestIds : templateWhatsappEditTestIds,
|
|
28
|
+
)
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<UCard
|
|
33
|
+
v-if="store.currentMessage"
|
|
34
|
+
:ui="{
|
|
35
|
+
root: 'flex flex-col h-full',
|
|
36
|
+
body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0',
|
|
37
|
+
header: 'sm:px-4 px-4 py-4 sm:py-4',
|
|
38
|
+
}"
|
|
39
|
+
:data-testid="testIds.translationsCard"
|
|
40
|
+
>
|
|
41
|
+
<div class="grid grid-cols-[auto_1fr] divide-x divide-muted overflow-auto max-h-full h-full">
|
|
42
|
+
<div class="overflow-y-auto">
|
|
43
|
+
<LanguageList
|
|
44
|
+
v-model:messages="store.messages"
|
|
45
|
+
v-model:language="store.targetLanguage"
|
|
46
|
+
render-type="whatsapp_v1"
|
|
47
|
+
:message-group-id="store.changeTrackingId"
|
|
48
|
+
:main-language-id="store.language"
|
|
49
|
+
@add-language="emit('add-language', $event)"
|
|
50
|
+
@remove-language="emit('remove-language', $event)"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="overflow-y-auto" v-if="store.currentMessage && store.targetMessage">
|
|
54
|
+
<TranslationEditor
|
|
55
|
+
:base-message="store.messages[store.language] || store.messages['en']"
|
|
56
|
+
v-model:target-message="store.messages[store.targetLanguage]"
|
|
57
|
+
:messages="store.messages"
|
|
58
|
+
:current-language="store.targetLanguage"
|
|
59
|
+
:base-language="store.language"
|
|
60
|
+
:message-group-id="store.changeTrackingId"
|
|
61
|
+
@auto-translate-tracked="emit('auto-translate-tracked', $event)"
|
|
62
|
+
@preview="emit('preview', $event)"
|
|
63
|
+
@filters-changed="emit('filters-changed', $event)"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</UCard>
|
|
68
|
+
</template>
|