494 lines
17 KiB
Vue

<template>
<div>
<LayoutsBreadcrumb />
<rs-card class="mb-5">
<template #header>
<div class="flex">
<span title="Info"><Icon class="mr-2 flex justify-center" name="ic:outline-info"></Icon></span>
Info
</div>
</template>
<template #body>
<p class="mb-4">
Manage your notification templates here. You can create, edit, preview, and manage versions of templates for various channels like Email, SMS, Push, and In-App messages.
</p>
</template>
</rs-card>
<rs-card>
<template #header>
<h2 class="text-xl font-semibold">Notification Templates</h2>
</template>
<template #body>
<div class="pt-2">
<rs-tab fill>
<rs-tab-item title="All Templates">
<div class="flex justify-between items-center mb-4">
<div class="flex items-center gap-4 flex-wrap">
<FormKit
type="select"
name="category"
placeholder="Filter by Category"
:options="categories"
@input="filterByCategory"
input-class="formkit-input appearance-none bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md py-2 px-3 text-base leading-normal focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-400 dark:focus:border-primary-400"
outer-class="flex-grow sm:flex-grow-0 min-w-[180px] mb-0"
/>
<FormKit
type="select"
name="language"
placeholder="Filter by Language"
:options="languages"
@input="filterByLanguage"
input-class="formkit-input appearance-none bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md py-2 px-3 text-base leading-normal focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-400 dark:focus:border-primary-400"
outer-class="flex-grow sm:flex-grow-0 min-w-[180px] mb-0"
/>
<FormKit
type="select"
name="channel"
placeholder="Filter by Channel"
:options="channels"
@input="filterByChannel"
input-class="formkit-input appearance-none bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md py-2 px-3 text-base leading-normal focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-400 dark:focus:border-primary-400"
outer-class="flex-grow sm:flex-grow-0 min-w-[180px] mb-0"
/>
</div>
<rs-button @click="$router.push('/notification/templates/create_template')" class="ml-auto" >
<Icon name="material-symbols:add" class="mr-1"></Icon>
Create Template
</rs-button>
</div>
<rs-table
v-if="templateList && templateList.length > 0"
:data="templateList"
:columns="columns"
:options="{
variant: 'default',
striped: true,
borderless: true,
class: 'align-middle'
}"
advanced
>
<template v-slot:channel="{ value }">
<div class="flex items-center justify-start gap-1 flex-wrap" style="max-width: 100px;">
<template v-if="value.channel && value.channel.length">
<template v-for="channel_item in value.channel" :key="channel_item">
<span :title="channel_item">
<Icon
:name="getChannelIcon(channel_item)"
class="text-gray-700 dark:text-gray-300"
size="18"
/>
</span>
</template>
</template>
<span v-else>-</span>
</div>
</template>
<template v-slot:version="{ text }">
<span class="text-sm text-gray-600 dark:text-gray-400">v{{ text }}</span>
</template>
<template v-slot:status="{ text }">
<span
class="px-2 py-1 rounded-full text-xs font-medium"
:class="getStatusClass(text)"
>
{{ text }}
</span>
</template>
<template v-slot:action="data">
<div class="flex justify-center items-center gap-2">
<span title="Edit">
<Icon
name="material-symbols:edit-outline-rounded"
class="text-primary hover:text-primary/90 cursor-pointer"
size="22"
@click="editTemplate(data.value)"
/>
</span>
<span title="Duplicate">
<Icon
name="material-symbols:content-copy"
class="text-primary hover:text-primary/90 cursor-pointer"
size="22"
@click="duplicateTemplate(data.value)"
/>
</span>
<span title="Version History">
<Icon
name="material-symbols:history"
class="text-primary hover:text-primary/90 cursor-pointer"
size="22"
@click="showVersionHistory(data.value)"
/>
</span>
<span title="Preview">
<Icon
name="material-symbols:preview-outline"
class="text-primary hover:text-primary/90 cursor-pointer"
size="22"
@click="previewTemplate(data.value)"
/>
</span>
<span title="Delete">
<Icon
name="material-symbols:close-rounded"
class="text-red-500 hover:text-red-600 cursor-pointer"
size="22"
@click="openModalDelete(data.value)"
/>
</span>
</div>
</template>
</rs-table>
</rs-tab-item>
</rs-tab>
</div>
</template>
</rs-card>
<!-- Preview Modal -->
<rs-modal v-model="showPreview" title="Template Preview" size="lg">
<div class="space-y-4 p-1">
<div class="flex gap-4 mb-4">
<FormKit
type="select"
name="previewChannel"
label="Preview Channel"
:options="channels"
v-model="previewChannel"
/>
<FormKit
type="select"
name="previewLanguage"
label="Preview Language"
:options="languages"
v-model="previewLanguage"
/>
</div>
<div class="border rounded-lg p-4 bg-gray-50 dark:bg-gray-800">
<h3 class="font-semibold mb-2 text-gray-800 dark:text-gray-200">{{ selectedTemplate?.notificationTitle }}</h3>
<div class="prose prose-sm max-w-none dark:prose-invert" v-html="selectedTemplate?.content"></div>
</div>
</div>
<template #footer>
<rs-button @click="showPreview = false">Close</rs-button>
</template>
</rs-modal>
<!-- Version History Modal -->
<rs-modal v-model="showVersions" title="Version History" size="lg">
<div class="space-y-4 p-1">
<div v-if="versionHistory.length">
<div v-for="version in versionHistory" :key="version.id" class="border rounded-lg p-4 mb-3 last:mb-0 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150 ease-in-out">
<div class="flex justify-between items-center mb-2">
<span class="font-semibold text-gray-700 dark:text-gray-300">Version {{ version.version }}</span>
<div class="flex items-center gap-2">
<span class="text-sm text-gray-500 dark:text-gray-400">{{ version.updatedAt }}</span>
<rs-button size="sm" @click="restoreVersion(version)" variant="outline">Restore</rs-button>
<rs-button size="sm" @click="deleteVersion(version)" variant="danger-outline">Delete</rs-button>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400">{{ version.changeDescription }}</p>
</div>
</div>
<div v-else class="text-center text-gray-500 dark:text-gray-400 py-4">
No version history available for this template.
</div>
</div>
<template #footer>
<rs-button @click="showVersions = false">Close</rs-button>
</template>
</rs-modal>
</div>
</template>
<script setup>
definePageMeta({
title: "Notification Templates",
middleware: ["auth"],
requiresAuth: true,
});
const { $swal } = useNuxtApp();
const router = useRouter();
const categories = ref([
{ label: 'All Categories', value: '' },
{ label: 'User Management', value: 'user' },
{ label: 'Orders', value: 'order' },
{ label: 'Security', value: 'security' },
{ label: 'Marketing', value: 'marketing' },
]);
const languages = ref([
{ label: 'All Languages', value: '' },
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
]);
const channels = ref([
{ label: 'All Channels', value: '' },
{ label: 'Email', value: 'email' },
{ label: 'SMS', value: 'sms' },
{ label: 'Push', value: 'push' },
{ label: 'In-App', value: 'in-app' },
]);
const columns = [
{
label: 'Title',
key: 'title',
sortable: true,
},
{
label: 'Category',
key: 'category',
sortable: true,
},
{
label: 'Channels',
sortable: false,
},
{
label: 'Language',
key: 'language',
sortable: true,
},
{
label: 'Version',
key: 'version',
sortable: true,
},
{
label: 'Status',
key: 'status',
sortable: true,
},
{
label: 'Last Updated',
key: 'updatedAt',
sortable: true,
},
{
label: 'Action',
key: 'action',
align: 'center',
},
];
const templateList = ref([
{
id: 1,
title: 'Welcome Email',
category: 'User Management',
description: 'Template for new user welcome email',
notificationTitle: 'Welcome {{first_name}}',
content: 'Hello {{first_name}}, welcome to our platform!',
channel: ['email', 'in-app'],
language: 'en',
version: '1.2',
status: 'Active',
createdAt: '2024-03-20',
updatedAt: '2024-03-20',
action: null,
},
{
id: 2,
title: 'Order Confirmation',
category: 'Orders',
description: 'Template for order confirmation',
notificationTitle: 'Order #{{order_id}} Confirmed',
content: 'Your order #{{order_id}} has been confirmed.',
channel: ['email', 'sms', 'push'],
language: 'en',
version: '2.0',
status: 'Active',
createdAt: '2024-03-19',
updatedAt: '2024-03-19',
action: null,
},
{
id: 3,
title: 'Password Reset',
category: 'Security',
description: 'Template for password reset requests',
notificationTitle: 'Password Reset Request',
content: 'Click the link to reset your password: {{reset_link}}',
channel: ['email'],
language: 'en',
version: '1.0',
status: 'Draft',
createdAt: '2024-03-18',
updatedAt: '2024-03-18',
action: null,
},
]);
const showPreview = ref(false);
const selectedTemplate = ref(null);
const previewChannel = ref('email');
const previewLanguage = ref('en');
const showVersions = ref(false);
const versionHistory = ref([]);
const getChannelIcon = (channel_item) => {
const icons = {
email: 'material-symbols:mail-outline-rounded',
sms: 'material-symbols:sms-outline-rounded',
push: 'material-symbols:notifications-active-outline-rounded',
'in-app': 'material-symbols:chat-bubble-outline-rounded',
};
return icons[channel_item] || 'material-symbols:help-outline-rounded';
};
const getStatusClass = (status) => {
const classes = {
Active: 'bg-green-100 text-green-800 dark:bg-green-700 dark:text-green-100',
Draft: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200',
Archived: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-600 dark:text-yellow-100',
};
return classes[status];
};
const filterByCategory = (event) => {
console.log('Filter by category:', event.target.value);
};
const filterByLanguage = (event) => {
console.log('Filter by language:', event.target.value);
};
const filterByChannel = (event) => {
console.log('Filter by channel:', event.target.value);
};
const editTemplate = (template) => {
router.push(`/notification/templates/create_template?id=${template.id}`);
};
const duplicateTemplate = (template) => {
$swal.fire({
title: 'Duplicate Template?',
text: `Are you sure you want to duplicate "${template.title}"?`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Duplicate',
cancelButtonText: 'Cancel',
}).then((result) => {
if (result.isConfirmed) {
console.log('Duplicate template:', template.title);
$swal.fire('Duplicated!', 'Template has been duplicated. (Mock)', 'success');
}
});
};
const previewTemplate = (template) => {
selectedTemplate.value = template;
showPreview.value = true;
};
const showVersionHistory = async (template) => {
versionHistory.value = [
{
id: 1,
version: '1.2',
changeDescription: 'Updated welcome message and added new variables for personalization.',
updatedAt: '2024-03-20 14:30',
},
{
id: 2,
version: '1.1',
changeDescription: 'Fixed formatting issues and typos in the content body.',
updatedAt: '2024-03-19 11:20',
},
{
id: 3,
version: '1.0',
changeDescription: 'Initial version of the template created for welcome emails.',
updatedAt: '2024-03-18 09:15',
},
];
selectedTemplate.value = template;
showVersions.value = true;
};
const restoreVersion = async (version) => {
$swal.fire({
title: 'Restore Version?',
text: `Are you sure you want to restore version ${version.version} for "${selectedTemplate.value?.title}"? Current content will be overwritten.`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Restore',
cancelButtonText: 'Cancel',
dangerMode: true,
}).then((result) => {
if (result.isConfirmed) {
console.log('Restore version:', version);
$swal.fire('Restored!', `Version ${version.version} has been restored. (Mock)`, 'success');
showVersions.value = false;
}
});
};
const deleteVersion = async (version) => {
const result = await $swal.fire({
title: 'Delete Version?',
text: `Are you sure you want to delete version ${version.version} for "${selectedTemplate.value?.title}"? This action cannot be undone.`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it!',
cancelButtonText: 'Cancel',
confirmButtonColor: '#d33', // Standard red for delete
cancelButtonColor: '#3085d6',
});
if (result.isConfirmed) {
// Mock deletion: In a real app, you'd call an API here
versionHistory.value = versionHistory.value.filter(v => v.id !== version.id);
$swal.fire(
'Deleted!',
`Version ${version.version} has been deleted. (Mock)`,
'success'
);
// If no versions left, maybe close modal or show a message?
// if (versionHistory.value.length === 0) {
// showVersions.value = false;
// }
}
};
const openModalDelete = async (templateToDelete) => {
const result = await $swal.fire({
title: 'Delete Template',
text: `Are you sure you want to delete "${templateToDelete.title}"? This action cannot be undone.`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it!',
cancelButtonText: 'Cancel',
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
});
if (result.isConfirmed) {
deleteTemplate(templateToDelete);
}
};
const deleteTemplate = async (templateToDelete) => {
templateList.value = templateList.value.filter(t => t.id !== templateToDelete.id);
$swal.fire({
position: "center",
icon: "success",
title: "Deleted!",
text: `Template "${templateToDelete.title}" has been deleted.`,
timer: 2000,
showConfirmButton: false,
});
};
</script>