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:
@@ -0,0 +1,76 @@
|
||||
import prisma from "~/server/utils/prisma";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const now = new Date();
|
||||
const last30Days = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// Get all channel types
|
||||
const channelTypes = ['email', 'push', 'sms'];
|
||||
const channelPerformance = [];
|
||||
|
||||
for (const channelType of channelTypes) {
|
||||
// Get stats for this channel
|
||||
const [totalSent, successful, failed, pending] = await Promise.all([
|
||||
prisma.notification_recipients.count({
|
||||
where: {
|
||||
channel_type: channelType,
|
||||
created_at: { gte: last30Days }
|
||||
}
|
||||
}),
|
||||
prisma.notification_recipients.count({
|
||||
where: {
|
||||
channel_type: channelType,
|
||||
status: 'sent',
|
||||
created_at: { gte: last30Days }
|
||||
}
|
||||
}),
|
||||
prisma.notification_recipients.count({
|
||||
where: {
|
||||
channel_type: channelType,
|
||||
status: 'failed',
|
||||
created_at: { gte: last30Days }
|
||||
}
|
||||
}),
|
||||
prisma.notification_recipients.count({
|
||||
where: {
|
||||
channel_type: channelType,
|
||||
status: 'pending',
|
||||
created_at: { gte: last30Days }
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
const successRate = totalSent > 0
|
||||
? ((successful / totalSent) * 100).toFixed(1)
|
||||
: 0;
|
||||
|
||||
const failureRate = totalSent > 0
|
||||
? ((failed / totalSent) * 100).toFixed(1)
|
||||
: 0;
|
||||
|
||||
channelPerformance.push({
|
||||
channel: channelType,
|
||||
totalSent,
|
||||
successful,
|
||||
failed,
|
||||
pending,
|
||||
successRate: parseFloat(successRate),
|
||||
failureRate: parseFloat(failureRate),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: channelPerformance
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching channel performance:", error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to fetch channel performance",
|
||||
data: { error: error.message },
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
});
|
||||
170
server/api/notifications/dashboard/overview.get.js
Normal file
170
server/api/notifications/dashboard/overview.get.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import prisma from "~/server/utils/prisma";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const now = new Date();
|
||||
const last24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
const last7Days = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
const last30Days = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// Fetch comprehensive stats
|
||||
const [
|
||||
totalNotifications,
|
||||
totalSent,
|
||||
totalScheduled,
|
||||
totalDraft,
|
||||
sentLast24h,
|
||||
sentLast7Days,
|
||||
totalRecipients,
|
||||
successfulDeliveries,
|
||||
failedDeliveries,
|
||||
queuedJobs,
|
||||
channelStats,
|
||||
categoryStats,
|
||||
] = await Promise.all([
|
||||
// Total notifications
|
||||
prisma.notifications.count(),
|
||||
|
||||
// Total sent notifications
|
||||
prisma.notifications.count({
|
||||
where: { status: "sending" }
|
||||
}),
|
||||
|
||||
// Total scheduled
|
||||
prisma.notifications.count({
|
||||
where: { status: "scheduled" }
|
||||
}),
|
||||
|
||||
// Total drafts
|
||||
prisma.notifications.count({
|
||||
where: { status: "draft" }
|
||||
}),
|
||||
|
||||
// Sent in last 24 hours
|
||||
prisma.notifications.count({
|
||||
where: {
|
||||
status: "sending",
|
||||
sent_at: { gte: last24Hours }
|
||||
}
|
||||
}),
|
||||
|
||||
// Sent in last 7 days
|
||||
prisma.notifications.count({
|
||||
where: {
|
||||
status: "sending",
|
||||
sent_at: { gte: last7Days }
|
||||
}
|
||||
}),
|
||||
|
||||
// Total recipients
|
||||
prisma.notification_recipients.count(),
|
||||
|
||||
// Successful deliveries
|
||||
prisma.notification_recipients.count({
|
||||
where: { status: "sent" }
|
||||
}),
|
||||
|
||||
// Failed deliveries
|
||||
prisma.notification_recipients.count({
|
||||
where: { status: "failed" }
|
||||
}),
|
||||
|
||||
// Queued jobs
|
||||
prisma.notification_queue.count({
|
||||
where: { status: "queued" }
|
||||
}),
|
||||
|
||||
// Channel distribution
|
||||
prisma.notification_channels.groupBy({
|
||||
by: ['channel_type'],
|
||||
_count: {
|
||||
channel_type: true
|
||||
}
|
||||
}),
|
||||
|
||||
// Category distribution
|
||||
prisma.notifications.groupBy({
|
||||
by: ['category_id'],
|
||||
_count: {
|
||||
category_id: true
|
||||
},
|
||||
take: 5,
|
||||
orderBy: {
|
||||
_count: {
|
||||
category_id: 'desc'
|
||||
}
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
// Calculate delivery rate
|
||||
const totalDeliveryAttempts = successfulDeliveries + failedDeliveries;
|
||||
const deliveryRate = totalDeliveryAttempts > 0
|
||||
? ((successfulDeliveries / totalDeliveryAttempts) * 100).toFixed(1)
|
||||
: 100;
|
||||
|
||||
// Calculate growth rate (last 7 days vs previous 7 days)
|
||||
const previous7Days = new Date(last7Days.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
const sentPrevious7Days = await prisma.notifications.count({
|
||||
where: {
|
||||
status: "sending",
|
||||
sent_at: {
|
||||
gte: previous7Days,
|
||||
lt: last7Days
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const growthRate = sentPrevious7Days > 0
|
||||
? (((sentLast7Days - sentPrevious7Days) / sentPrevious7Days) * 100).toFixed(1)
|
||||
: 0;
|
||||
|
||||
// Get category names
|
||||
const categoryIds = categoryStats.map(c => c.category_id).filter(Boolean);
|
||||
const categories = await prisma.notification_categories.findMany({
|
||||
where: { id: { in: categoryIds } },
|
||||
select: { id: true, name: true }
|
||||
});
|
||||
|
||||
const categoryMap = Object.fromEntries(categories.map(c => [c.id, c.name]));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
overview: {
|
||||
total: totalNotifications,
|
||||
sent: totalSent,
|
||||
scheduled: totalScheduled,
|
||||
draft: totalDraft,
|
||||
sentLast24h,
|
||||
sentLast7Days,
|
||||
growthRate: parseFloat(growthRate),
|
||||
},
|
||||
delivery: {
|
||||
totalRecipients,
|
||||
successful: successfulDeliveries,
|
||||
failed: failedDeliveries,
|
||||
deliveryRate: parseFloat(deliveryRate),
|
||||
queued: queuedJobs,
|
||||
},
|
||||
channels: channelStats.map(ch => ({
|
||||
channel: ch.channel_type,
|
||||
count: ch._count.channel_type,
|
||||
})),
|
||||
topCategories: categoryStats.map(cat => ({
|
||||
categoryId: cat.category_id,
|
||||
categoryName: categoryMap[cat.category_id] || 'Unknown',
|
||||
count: cat._count.category_id,
|
||||
})),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching dashboard overview:", error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to fetch dashboard data",
|
||||
data: { error: error.message },
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
});
|
||||
65
server/api/notifications/dashboard/recent.get.js
Normal file
65
server/api/notifications/dashboard/recent.get.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import prisma from "~/server/utils/prisma";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const query = getQuery(event);
|
||||
const limit = parseInt(query.limit) || 10;
|
||||
|
||||
// Fetch recent notifications with related data
|
||||
const recentNotifications = await prisma.notifications.findMany({
|
||||
take: limit,
|
||||
orderBy: {
|
||||
created_at: 'desc'
|
||||
},
|
||||
include: {
|
||||
notification_categories: {
|
||||
select: {
|
||||
name: true,
|
||||
value: true
|
||||
}
|
||||
},
|
||||
notification_channels: {
|
||||
select: {
|
||||
channel_type: true
|
||||
}
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
notification_recipients: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Format the response
|
||||
const formatted = recentNotifications.map(notif => ({
|
||||
id: notif.id,
|
||||
title: notif.title,
|
||||
type: notif.type,
|
||||
priority: notif.priority,
|
||||
status: notif.status,
|
||||
category: notif.notification_categories?.name || 'Unknown',
|
||||
channels: notif.notification_channels.map(ch => ch.channel_type),
|
||||
recipientCount: notif._count.notification_recipients,
|
||||
estimatedReach: notif.estimated_reach,
|
||||
actualSent: notif.actual_sent,
|
||||
createdAt: notif.created_at,
|
||||
sentAt: notif.sent_at,
|
||||
scheduledAt: notif.scheduled_at,
|
||||
createdBy: notif.created_by,
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: formatted
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent notifications:", error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to fetch recent notifications",
|
||||
data: { error: error.message },
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user