266 lines
7.2 KiB
Vue
266 lines
7.2 KiB
Vue
<template>
|
|
<div class="flex h-full flex-col gap-6 p-8 text-ink-gray-8">
|
|
<!-- Header -->
|
|
<div class="flex justify-between">
|
|
<div class="flex flex-col gap-1 w-9/12">
|
|
<h2 class="flex gap-2 text-xl font-semibold leading-none h-5">
|
|
{{ __('Email templates') }}
|
|
</h2>
|
|
<p class="text-p-base text-ink-gray-6">
|
|
{{
|
|
__(
|
|
'Add, edit, and manage email templates for various CRM communications',
|
|
)
|
|
}}
|
|
</p>
|
|
</div>
|
|
<div class="flex item-center space-x-2 w-3/12 justify-end">
|
|
<Button
|
|
:label="__('New')"
|
|
icon-left="plus"
|
|
variant="solid"
|
|
@click="emit('updateStep', 'new-template')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- loading state -->
|
|
<div
|
|
v-if="templates.loading"
|
|
class="flex mt-28 justify-between w-full h-full"
|
|
>
|
|
<Button
|
|
:loading="templates.loading"
|
|
variant="ghost"
|
|
class="w-full"
|
|
size="2xl"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div
|
|
v-if="!templates.loading && !templates.data?.length"
|
|
class="flex justify-between w-full h-full"
|
|
>
|
|
<div
|
|
class="text-ink-gray-4 border border-dashed rounded w-full flex items-center justify-center"
|
|
>
|
|
{{ __('No email templates found') }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Email template list -->
|
|
<div
|
|
class="flex flex-col overflow-hidden"
|
|
v-if="!templates.loading && templates.data?.length"
|
|
>
|
|
<div
|
|
v-if="templates.data?.length > 10"
|
|
class="flex items-center justify-between mb-4"
|
|
>
|
|
<TextInput
|
|
ref="searchRef"
|
|
v-model="search"
|
|
:placeholder="__('Search template')"
|
|
class="w-1/3"
|
|
:debounce="300"
|
|
>
|
|
<template #prefix>
|
|
<FeatherIcon name="search" class="h-4 w-4 text-ink-gray-6" />
|
|
</template>
|
|
</TextInput>
|
|
<FormControl
|
|
type="select"
|
|
v-model="currentDoctype"
|
|
:options="[
|
|
{ label: __('All'), value: 'All' },
|
|
{ label: __('Lead'), value: 'CRM Lead' },
|
|
{ label: __('Deal'), value: 'CRM Deal' },
|
|
]"
|
|
/>
|
|
</div>
|
|
<div class="flex items-center p-2 text-sm border-b text-ink-gray-5">
|
|
<div class="w-4/6">{{ __('Template name') }}</div>
|
|
<div class="w-1/6">{{ __('For') }}</div>
|
|
<div class="w-1/6">{{ __('Enabled') }}</div>
|
|
</div>
|
|
<ul class="divide-y divide-outline-gray-modals overflow-y-auto">
|
|
<template v-for="template in templatesList" :key="template.name">
|
|
<li class="flex items-center justify-between px-2 py-3">
|
|
<div class="flex flex-col w-4/6 pr-5">
|
|
<div class="text-base font-medium text-ink-gray-7">
|
|
{{ template.name }}
|
|
</div>
|
|
<div class="text-p-base text-ink-gray-5 truncate">
|
|
{{ template.subject }}
|
|
</div>
|
|
</div>
|
|
<div class="text-base text-ink-gray-6 w-1/6">
|
|
{{ template.reference_doctype.replace('CRM ', '') }}
|
|
</div>
|
|
<div class="flex items-center justify-between w-1/6">
|
|
<Switch
|
|
size="sm"
|
|
v-model="template.enabled"
|
|
@update:model-value="toggleEmailTemplate(template)"
|
|
/>
|
|
<Dropdown
|
|
:options="getDropdownOptions(template)"
|
|
placement="right"
|
|
:button="{
|
|
icon: 'more-horizontal',
|
|
variant: 'ghost',
|
|
onblur: (e) => {
|
|
e.stopPropagation()
|
|
confirmDelete = false
|
|
},
|
|
}"
|
|
/>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
<!-- Load More Button -->
|
|
<div
|
|
v-if="!templates.loading && templates.hasNextPage"
|
|
class="flex justify-center"
|
|
>
|
|
<Button
|
|
class="mt-3.5 p-2"
|
|
@click="() => templates.next()"
|
|
:loading="templates.loading"
|
|
:label="__('Load More')"
|
|
icon-left="refresh-cw"
|
|
/>
|
|
</div>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script setup>
|
|
import {
|
|
TextInput,
|
|
FormControl,
|
|
Switch,
|
|
Dropdown,
|
|
FeatherIcon,
|
|
} from 'frappe-ui'
|
|
import { ref, computed, inject, h } from 'vue'
|
|
|
|
const emit = defineEmits(['updateStep'])
|
|
|
|
const templates = inject('templates')
|
|
|
|
const search = ref('')
|
|
const currentDoctype = ref('All')
|
|
const confirmDelete = ref(false)
|
|
|
|
const templatesList = computed(() => {
|
|
let list = templates.data || []
|
|
if (search.value) {
|
|
list = list.filter(
|
|
(template) =>
|
|
template.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
|
template.subject.toLowerCase().includes(search.value.toLowerCase()),
|
|
)
|
|
}
|
|
if (currentDoctype.value !== 'All') {
|
|
list = list.filter(
|
|
(template) => template.reference_doctype === currentDoctype.value,
|
|
)
|
|
}
|
|
return list
|
|
})
|
|
|
|
function toggleEmailTemplate(template) {
|
|
templates.setValue.submit({
|
|
name: template.name,
|
|
enabled: template.enabled ? 1 : 0,
|
|
})
|
|
}
|
|
|
|
function deleteTemplate(template) {
|
|
confirmDelete.value = false
|
|
templates.delete.submit(template.name)
|
|
}
|
|
|
|
function getDropdownOptions(template) {
|
|
let options = [
|
|
{
|
|
label: __('Edit'),
|
|
component: (props) =>
|
|
TemplateOption({
|
|
option: __('Edit'),
|
|
icon: 'edit-2',
|
|
active: props.active,
|
|
onClick: () => {
|
|
emit('updateStep', 'edit-template', { ...template })
|
|
},
|
|
}),
|
|
},
|
|
{
|
|
label: __('Duplicate'),
|
|
component: (props) =>
|
|
TemplateOption({
|
|
option: __('Duplicate'),
|
|
icon: 'copy',
|
|
active: props.active,
|
|
onClick: () => {},
|
|
}),
|
|
},
|
|
{
|
|
label: __('Delete'),
|
|
component: (props) =>
|
|
TemplateOption({
|
|
option: __('Delete'),
|
|
icon: 'trash-2',
|
|
active: props.active,
|
|
onClick: (e) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
confirmDelete.value = true
|
|
},
|
|
}),
|
|
condition: () => !confirmDelete.value,
|
|
},
|
|
{
|
|
label: __('Confirm Delete'),
|
|
component: (props) =>
|
|
TemplateOption({
|
|
option: __('Confirm Delete'),
|
|
icon: 'trash-2',
|
|
active: props.active,
|
|
variant: 'danger',
|
|
onClick: () => deleteTemplate(template),
|
|
}),
|
|
condition: () => confirmDelete.value,
|
|
},
|
|
]
|
|
|
|
return options.filter((option) => option.condition?.() || true)
|
|
}
|
|
|
|
function TemplateOption({ active, option, variant, icon, onClick }) {
|
|
return h(
|
|
'button',
|
|
{
|
|
class: [
|
|
active ? 'bg-surface-gray-2' : 'text-ink-gray-8',
|
|
'group flex w-full gap-2 items-center rounded-md px-2 py-2 text-sm',
|
|
variant == 'danger' ? 'text-ink-red-3 hover:bg-ink-red-1' : '',
|
|
],
|
|
onClick: onClick,
|
|
},
|
|
[
|
|
icon
|
|
? h(FeatherIcon, {
|
|
name: icon,
|
|
class: ['h-4 w-4 shrink-0'],
|
|
'aria-hidden': true,
|
|
})
|
|
: null,
|
|
h('span', { class: 'whitespace-nowrap' }, option),
|
|
],
|
|
)
|
|
}
|
|
</script>
|