Update various configuration files, components, and assets; enhance notification system and API endpoints; improve documentation and styles across the application.
This commit is contained in:
329
pages/notification/log-audit/analytics.vue
Normal file
329
pages/notification/log-audit/analytics.vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<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-bar-chart"></Icon>
|
||||
<h1 class="text-xl font-bold text-primary">Analytics Dashboard</h1>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<p class="text-gray-600">
|
||||
View metrics for notification performance, delivery rates, and user engagement.
|
||||
</p>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Key Metrics Summary -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-6 mb-6">
|
||||
<rs-card
|
||||
v-for="(metric, index) in keyMetrics"
|
||||
:key="index"
|
||||
>
|
||||
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
|
||||
<div
|
||||
class="p-5 flex justify-center items-center bg-primary/20 rounded-2xl"
|
||||
>
|
||||
<Icon class="text-primary text-3xl" :name="metric.icon"></Icon>
|
||||
</div>
|
||||
<div class="flex-1 truncate">
|
||||
<span class="block font-bold text-2xl leading-tight text-primary">
|
||||
{{ metric.value }}
|
||||
</span>
|
||||
<span class="text-sm font-medium text-gray-600">
|
||||
{{ metric.title }}
|
||||
</span>
|
||||
<div class="flex items-center mt-1" v-if="metric.change">
|
||||
<Icon
|
||||
:name="metric.trend === 'up' ? 'ic:outline-trending-up' : 'ic:outline-trending-down'"
|
||||
:class="metric.trend === 'up' ? 'text-green-500' : 'text-red-500'"
|
||||
class="text-sm mr-1"
|
||||
/>
|
||||
<span
|
||||
:class="metric.trend === 'up' ? 'text-green-600' : 'text-red-600'"
|
||||
class="text-xs font-medium"
|
||||
>
|
||||
{{ metric.change }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Time Range Filter -->
|
||||
<rs-card class="mb-6">
|
||||
<template #body>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<h3 class="text-lg font-semibold text-primary">Analytics Period</h3>
|
||||
<FormKit
|
||||
type="select"
|
||||
v-model="selectedPeriod"
|
||||
:options="periodOptions"
|
||||
outer-class="mb-0"
|
||||
@input="updateAnalytics"
|
||||
/>
|
||||
</div>
|
||||
<rs-button variant="primary-outline" size="sm" @click="refreshAnalytics">
|
||||
<Icon name="ic:outline-refresh" class="mr-1"/> Refresh
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<div v-if="analyticsLoading" class="flex justify-center py-16">
|
||||
<Loading />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- Main Analytics Charts -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="ic:outline-bar-chart" class="mr-2 text-primary"/>
|
||||
<h3 class="text-lg font-semibold text-primary">Delivery Rate Analysis</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="h-64 bg-gray-100 dark:bg-gray-800 flex items-center justify-center rounded">
|
||||
<div class="text-center">
|
||||
<p class="text-gray-500">Delivery success over time</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-green-600">{{ channelPerformance[0]?.successRate || '0' }}%</div>
|
||||
<div class="text-sm text-gray-600">Success Rate</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-red-600">{{ channelPerformance[0]?.failureRate || '0' }}%</div>
|
||||
<div class="text-sm text-gray-600">Failed Rate</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-yellow-600">{{ channelPerformance[0]?.bounceRate || '0' }}%</div>
|
||||
<div class="text-sm text-gray-600">Bounce Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="ic:outline-device-hub" class="mr-2 text-primary"/>
|
||||
<h3 class="text-lg font-semibold text-primary">Channel Performance</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="channel in channelPerformance"
|
||||
:key="channel.name"
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center">
|
||||
<Icon :name="channel.icon" class="mr-2 text-primary"/>
|
||||
<span class="font-medium">{{ channel.name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-primary">{{ channel.successRate }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
class="bg-primary h-2 rounded-full"
|
||||
:style="{ width: channel.successRate + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-gray-500 mt-1">
|
||||
<span>{{ channel.sent }} sent</span>
|
||||
<span>{{ channel.failed }} failed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Recent Analytics Events -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<Icon name="ic:outline-insights" class="mr-2 text-primary"/>
|
||||
<h3 class="text-lg font-semibold text-primary">Recent Events</h3>
|
||||
</div>
|
||||
<rs-button variant="outline" size="sm" @click="navigateTo('/notification/log-audit/logs')">
|
||||
View All Logs
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="recentEvents.length > 0" class="space-y-3">
|
||||
<div
|
||||
v-for="(event, index) in recentEvents"
|
||||
:key="index"
|
||||
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-3 h-3 rounded-full mr-3"
|
||||
:class="{
|
||||
'bg-green-500': event.type === 'success',
|
||||
'bg-yellow-500': event.type === 'warning',
|
||||
'bg-red-500': event.type === 'error',
|
||||
'bg-blue-500': event.type === 'info',
|
||||
}"
|
||||
></div>
|
||||
<div>
|
||||
<p class="font-medium">{{ event.title }}</p>
|
||||
<p class="text-sm text-gray-600">{{ event.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm font-medium">{{ event.value }}</p>
|
||||
<p class="text-xs text-gray-500">{{ event.time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-8 text-gray-500">
|
||||
<Icon name="ic:outline-search-off" class="text-4xl mb-2 mx-auto" />
|
||||
<p>No recent events found.</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Analytics Dashboard",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/dashboard",
|
||||
},
|
||||
{
|
||||
name: "Notification",
|
||||
path: "/notification",
|
||||
},
|
||||
{
|
||||
name: "Logs & Audit Trail",
|
||||
path: "/notification/log-audit",
|
||||
},
|
||||
{
|
||||
name: "Analytics",
|
||||
path: "/notification/log-audit/analytics",
|
||||
type: "current"
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
// Use the notification logs composable
|
||||
const {
|
||||
analyticsData,
|
||||
analyticsLoading,
|
||||
analyticsError,
|
||||
fetchAnalytics,
|
||||
formatDate
|
||||
} = useNotificationLogs()
|
||||
|
||||
// Time period selection
|
||||
const selectedPeriod = ref('7d')
|
||||
const periodOptions = [
|
||||
{ label: 'Last 24 Hours', value: '1d' },
|
||||
{ label: 'Last 7 Days', value: '7d' },
|
||||
{ label: 'Last 30 Days', value: '30d' },
|
||||
{ label: 'Last 90 Days', value: '90d' },
|
||||
{ label: 'Last 12 Months', value: '12m' },
|
||||
]
|
||||
|
||||
// Key metrics data - will be updated from API
|
||||
const keyMetrics = computed(() => analyticsData.value?.keyMetrics || [
|
||||
{
|
||||
title: "Total Sent",
|
||||
value: "0",
|
||||
icon: "ic:outline-send",
|
||||
trend: "up",
|
||||
change: "+0.0%"
|
||||
},
|
||||
{
|
||||
title: "Success Rate",
|
||||
value: "0.0%",
|
||||
icon: "ic:outline-check-circle",
|
||||
trend: "up",
|
||||
change: "+0.0%"
|
||||
},
|
||||
{
|
||||
title: "Open Rate",
|
||||
value: "0.0%",
|
||||
icon: "ic:outline-open-in-new",
|
||||
trend: "up",
|
||||
change: "+0.0%"
|
||||
},
|
||||
{
|
||||
title: "Click Rate",
|
||||
value: "0.0%",
|
||||
icon: "ic:outline-touch-app",
|
||||
trend: "up",
|
||||
change: "+0.0%"
|
||||
},
|
||||
])
|
||||
|
||||
// Channel performance data - will be updated from API
|
||||
const channelPerformance = computed(() => analyticsData.value?.channelPerformance || [
|
||||
{
|
||||
name: "Email",
|
||||
icon: "ic:outline-email",
|
||||
successRate: "0",
|
||||
sent: "0",
|
||||
failed: "0",
|
||||
bounceRate: "0",
|
||||
failureRate: "0"
|
||||
},
|
||||
])
|
||||
|
||||
// Recent analytics events from API
|
||||
const recentEvents = computed(() => analyticsData.value?.recentEvents || [])
|
||||
|
||||
// Methods
|
||||
const updateAnalytics = async () => {
|
||||
await fetchAnalytics(selectedPeriod.value)
|
||||
}
|
||||
|
||||
const refreshAnalytics = async () => {
|
||||
await fetchAnalytics(selectedPeriod.value)
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
onMounted(async () => {
|
||||
await fetchAnalytics(selectedPeriod.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.formkit-outer) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.formkit-label) {
|
||||
font-weight: 500;
|
||||
color: rgb(107 114 128);
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
:deep(.formkit-input) {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user