482 lines
14 KiB
Vue
482 lines
14 KiB
Vue
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Page Info Card -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-send"></Icon>
|
|
<h1 class="text-xl font-bold text-primary">Notification Delivery</h1>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<p class="text-gray-600">
|
|
Configure and monitor email and push notification delivery.
|
|
</p>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div
|
|
v-if="isLoading"
|
|
class="fixed inset-0 blur-lg bg-opacity-50 z-50 flex items-center justify-center"
|
|
>
|
|
<div class="bg-white rounded-lg p-6 flex items-center space-x-4">
|
|
<Icon name="ic:outline-refresh" class="text-primary animate-spin" size="24" />
|
|
<span class="text-gray-700">Loading...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Alert -->
|
|
<rs-alert
|
|
v-if="error"
|
|
variant="danger"
|
|
class="mb-6"
|
|
dismissible
|
|
@dismiss="error = null"
|
|
>
|
|
<template #icon>
|
|
<Icon name="ic:outline-error" />
|
|
</template>
|
|
{{ error }}
|
|
</rs-alert>
|
|
|
|
<!-- Quick Stats -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
|
<rs-card v-for="(stat, index) in stats" :key="index" class="text-center relative">
|
|
<div
|
|
v-if="isLoading"
|
|
class="absolute inset-0 bg-gray-50 bg-opacity-75 flex items-center justify-center"
|
|
>
|
|
<Icon name="ic:outline-refresh" class="text-primary animate-spin" size="24" />
|
|
</div>
|
|
<div class="p-6">
|
|
<Icon class="text-primary text-4xl mb-3" :name="getIconForStat(index)"></Icon>
|
|
<div class="font-bold text-2xl text-primary mb-1">
|
|
{{ stat.value }}
|
|
</div>
|
|
<div class="text-sm text-gray-600">{{ getLabelForStat(index) }}</div>
|
|
</div>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Channel Configuration -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<!-- Email Configuration -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-email"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Email Configuration</h2>
|
|
</div>
|
|
<rs-badge :variant="emailConfig.enabled ? 'success' : 'secondary'">
|
|
{{ emailConfig.enabled ? "Active" : "Disabled" }}
|
|
</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div v-if="isLoading" class="flex items-center justify-center py-8">
|
|
<Icon name="ic:outline-refresh" class="text-primary animate-spin" size="24" />
|
|
</div>
|
|
<div v-else class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-medium">Enable Email Delivery</span>
|
|
<FormKit type="toggle" v-model="emailConfig.enabled" />
|
|
</div>
|
|
|
|
<div>
|
|
<FormKit
|
|
type="select"
|
|
label="Email Provider"
|
|
v-model="emailConfig.provider"
|
|
:options="emailProviders"
|
|
:disabled="!emailConfig.enabled"
|
|
/>
|
|
</div>
|
|
|
|
<div class="p-4 bg-gray-50 rounded-lg">
|
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-600">Status:</span>
|
|
<span class="ml-2 font-medium">{{ emailConfig.status }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600">Success Rate:</span>
|
|
<span class="ml-2 font-medium">{{ emailConfig.successRate }}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Push Notification Configuration -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-notifications"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">
|
|
Push Notification Configuration
|
|
</h2>
|
|
</div>
|
|
<rs-badge :variant="pushConfig.enabled ? 'success' : 'secondary'">
|
|
{{ pushConfig.enabled ? "Active" : "Disabled" }}
|
|
</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div v-if="isLoading" class="flex items-center justify-center py-8">
|
|
<Icon name="ic:outline-refresh" class="text-primary animate-spin" size="24" />
|
|
</div>
|
|
<div v-else class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-medium">Enable Push Notifications</span>
|
|
<FormKit type="toggle" v-model="pushConfig.enabled" />
|
|
</div>
|
|
|
|
<div>
|
|
<FormKit
|
|
type="select"
|
|
label="Push Provider"
|
|
v-model="pushConfig.provider"
|
|
:options="pushProviders"
|
|
:disabled="!pushConfig.enabled"
|
|
/>
|
|
</div>
|
|
|
|
<div class="p-4 bg-gray-50 rounded-lg">
|
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-600">Status:</span>
|
|
<span class="ml-2 font-medium">{{ pushConfig.status }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600">Success Rate:</span>
|
|
<span class="ml-2 font-medium">{{ pushConfig.successRate }}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Basic Settings -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-settings"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Delivery Settings</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary"
|
|
@click="saveSettings"
|
|
:disabled="isLoading"
|
|
>
|
|
<Icon
|
|
:name="isLoading ? 'ic:outline-refresh' : 'ic:outline-save'"
|
|
class="mr-1"
|
|
:class="{ 'animate-spin': isLoading }"
|
|
/>
|
|
{{ isLoading ? "Saving..." : "Save Settings" }}
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div v-if="isLoading" class="flex items-center justify-center py-8">
|
|
<Icon name="ic:outline-refresh" class="text-primary animate-spin" size="24" />
|
|
</div>
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-medium">Auto Retry Failed Deliveries</span>
|
|
<FormKit type="toggle" v-model="settings.autoRetry" />
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-medium">Enable Fallback Delivery</span>
|
|
<FormKit type="toggle" v-model="settings.enableFallback" />
|
|
</div>
|
|
|
|
<div>
|
|
<FormKit
|
|
type="number"
|
|
label="Max Retry Attempts"
|
|
v-model="settings.maxRetries"
|
|
min="0"
|
|
max="5"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<FormKit
|
|
type="number"
|
|
label="Retry Delay (seconds)"
|
|
v-model="settings.retryDelay"
|
|
min="1"
|
|
max="300"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<FormKit
|
|
type="select"
|
|
label="Delivery Priority"
|
|
v-model="settings.priority"
|
|
:options="priorityOptions"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-medium">Enable Delivery Reports</span>
|
|
<FormKit type="toggle" v-model="settings.enableReports" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from "vue";
|
|
import { useToast } from "@/composables/useToast";
|
|
import { useNotificationDelivery } from "@/composables/useNotificationDelivery";
|
|
|
|
definePageMeta({
|
|
title: "Notification Delivery",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{
|
|
name: "Dashboard",
|
|
path: "/dashboard",
|
|
},
|
|
{
|
|
name: "Notification",
|
|
path: "/notification",
|
|
},
|
|
{
|
|
name: "Delivery",
|
|
path: "/notification/delivery",
|
|
type: "current",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Stats
|
|
const stats = ref([
|
|
{
|
|
key: "emailsSent",
|
|
value: "12.5K",
|
|
label: "Emails Sent",
|
|
icon: "ic:outline-email",
|
|
},
|
|
{
|
|
key: "pushSent",
|
|
value: "8.3K",
|
|
label: "Push Sent",
|
|
icon: "ic:outline-notifications",
|
|
},
|
|
{
|
|
key: "successRate",
|
|
value: 98.5,
|
|
label: "Success Rate",
|
|
icon: "ic:outline-check-circle",
|
|
},
|
|
{ key: "failed", value: 156, label: "Failed", icon: "ic:outline-error" },
|
|
]);
|
|
|
|
// Email Configuration
|
|
const emailConfig = ref({
|
|
enabled: true,
|
|
provider: "nodemailer",
|
|
status: "Connected",
|
|
successRate: 99.2,
|
|
});
|
|
|
|
const emailProviders = [{ label: "Nodemailer", value: "nodemailer" }];
|
|
|
|
// Push Configuration
|
|
const pushConfig = ref({
|
|
enabled: true,
|
|
provider: "firebase",
|
|
status: "Connected",
|
|
successRate: 95.8,
|
|
});
|
|
|
|
const pushProviders = [{ label: "Firebase FCM", value: "firebase" }];
|
|
|
|
// Delivery Status
|
|
const emailStatus = ref({
|
|
delivered: "11.8K",
|
|
failed: "42",
|
|
});
|
|
|
|
const pushStatus = ref({
|
|
delivered: "7.9K",
|
|
failed: "114",
|
|
});
|
|
|
|
// Settings
|
|
const settings = ref({
|
|
autoRetry: true,
|
|
enableFallback: true,
|
|
maxRetries: 3,
|
|
retryDelay: 30,
|
|
priority: "normal",
|
|
enableReports: true,
|
|
});
|
|
|
|
const priorityOptions = [
|
|
{ label: "High Priority", value: "high" },
|
|
{ label: "Normal Priority", value: "normal" },
|
|
{ label: "Low Priority", value: "low" },
|
|
];
|
|
|
|
// Use composables
|
|
const toast = useToast();
|
|
const {
|
|
isLoading,
|
|
error,
|
|
fetchDeliveryStats,
|
|
fetchEmailConfig,
|
|
fetchPushConfig,
|
|
fetchDeliverySettings,
|
|
updateEmailConfig,
|
|
updatePushConfig,
|
|
updateDeliverySettings,
|
|
} = useNotificationDelivery();
|
|
|
|
// Methods
|
|
async function refreshData() {
|
|
try {
|
|
isLoading.value = true;
|
|
const [statsData, emailData, pushData, settingsData] = await Promise.all([
|
|
fetchDeliveryStats(),
|
|
fetchEmailConfig(),
|
|
fetchPushConfig(),
|
|
fetchDeliverySettings(),
|
|
]);
|
|
|
|
// Update stats
|
|
stats.value = [
|
|
{
|
|
key: "emailsSent",
|
|
value: formatNumber(statsData.emailsSent),
|
|
label: "Emails Sent",
|
|
icon: "ic:outline-email",
|
|
},
|
|
{
|
|
key: "pushSent",
|
|
value: formatNumber(statsData.pushSent),
|
|
label: "Push Sent",
|
|
icon: "ic:outline-notifications",
|
|
},
|
|
{
|
|
key: "successRate",
|
|
value: statsData.successRate.toFixed(1),
|
|
label: "Success Rate",
|
|
icon: "ic:outline-check-circle",
|
|
},
|
|
{
|
|
key: "failed",
|
|
value: formatNumber(statsData.failed),
|
|
label: "Failed",
|
|
icon: "ic:outline-error",
|
|
},
|
|
];
|
|
|
|
// Update email config
|
|
emailConfig.value = {
|
|
enabled: emailData.enabled,
|
|
provider: emailData.provider,
|
|
status: emailData.status,
|
|
successRate: emailData.successRate,
|
|
};
|
|
|
|
// Update push config
|
|
pushConfig.value = {
|
|
enabled: pushData.enabled,
|
|
provider: pushData.provider,
|
|
status: pushData.status,
|
|
successRate: pushData.successRate,
|
|
};
|
|
|
|
// Update settings
|
|
settings.value = {
|
|
autoRetry: settingsData.autoRetry,
|
|
enableFallback: settingsData.enableFallback,
|
|
maxRetries: settingsData.maxRetries,
|
|
retryDelay: settingsData.retryDelay,
|
|
priority: settingsData.priority,
|
|
enableReports: settingsData.enableReports,
|
|
};
|
|
|
|
// toast.success("Data refreshed successfully");
|
|
} catch (err) {
|
|
console.error("Error refreshing data:", err);
|
|
toast.error(err.message || "Failed to refresh data");
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
async function saveSettings() {
|
|
try {
|
|
isLoading.value = true;
|
|
|
|
// Update email configuration if changed
|
|
if (emailConfig.value.enabled !== undefined) {
|
|
await updateEmailConfig(emailConfig.value);
|
|
}
|
|
|
|
// Update push configuration if changed
|
|
if (pushConfig.value.enabled !== undefined) {
|
|
await updatePushConfig(pushConfig.value);
|
|
}
|
|
|
|
// Update delivery settings
|
|
await updateDeliverySettings(settings.value);
|
|
|
|
toast.success("Settings saved successfully");
|
|
} catch (err) {
|
|
console.error("Error saving settings:", err);
|
|
toast.error(err.message || "Failed to save settings");
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
// Format number helper
|
|
function formatNumber(num) {
|
|
if (num >= 1000) {
|
|
return (num / 1000).toFixed(1) + "K";
|
|
}
|
|
return num.toString();
|
|
}
|
|
|
|
// Helper functions for stats
|
|
function getIconForStat(index) {
|
|
return stats.value[index]?.icon || "ic:outline-email";
|
|
}
|
|
|
|
function getLabelForStat(index) {
|
|
return stats.value[index]?.label || "";
|
|
}
|
|
|
|
// Load initial data
|
|
onMounted(() => {
|
|
refreshData();
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped></style>
|