fix: Update sentCount on campaign sent for correct overview stats
This commit is contained in:
@@ -204,6 +204,7 @@ export async function createEmailWorker() {
|
|||||||
where: {id: email.campaignId},
|
where: {id: email.campaignId},
|
||||||
data: {
|
data: {
|
||||||
status: CampaignStatus.SENT,
|
status: CampaignStatus.SENT,
|
||||||
|
sentCount,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -564,6 +564,7 @@ export class CampaignService {
|
|||||||
await prisma.campaign.update({
|
await prisma.campaign.update({
|
||||||
where: {id: campaignId},
|
where: {id: campaignId},
|
||||||
data: {
|
data: {
|
||||||
|
sentCount: sentEmails,
|
||||||
deliveredCount: deliveredEmails,
|
deliveredCount: deliveredEmails,
|
||||||
openedCount: openedEmails,
|
openedCount: openedEmails,
|
||||||
clickedCount: clickedEmails,
|
clickedCount: clickedEmails,
|
||||||
|
|||||||
@@ -503,6 +503,42 @@ export class EmailService {
|
|||||||
data: updateData,
|
data: updateData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update campaign stats if applicable
|
||||||
|
if (email.campaignId) {
|
||||||
|
const campaignUpdate: Prisma.CampaignUpdateInput = {};
|
||||||
|
|
||||||
|
switch (eventType) {
|
||||||
|
case 'delivered':
|
||||||
|
campaignUpdate.deliveredCount = {increment: 1};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'opened':
|
||||||
|
// Only increment unique opens to match getStats logic
|
||||||
|
if (!email.openedAt) {
|
||||||
|
campaignUpdate.openedCount = {increment: 1};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'clicked':
|
||||||
|
// Only increment unique clicks to match getStats logic
|
||||||
|
if (!email.clickedAt) {
|
||||||
|
campaignUpdate.clickedCount = {increment: 1};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bounced':
|
||||||
|
campaignUpdate.bouncedCount = {increment: 1};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(campaignUpdate).length > 0) {
|
||||||
|
await prisma.campaign.update({
|
||||||
|
where: {id: email.campaignId},
|
||||||
|
data: campaignUpdate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Track event
|
// Track event
|
||||||
await prisma.event.create({
|
await prisma.event.create({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -25,7 +25,20 @@ import {TemplateSelectionDialog} from '../../components/TemplateSelectionDialog'
|
|||||||
import {CampaignSelectionDialog} from '../../components/CampaignSelectionDialog';
|
import {CampaignSelectionDialog} from '../../components/CampaignSelectionDialog';
|
||||||
import {network} from '../../lib/network';
|
import {network} from '../../lib/network';
|
||||||
import {formatRelativeTime} from '../../lib/dateUtils';
|
import {formatRelativeTime} from '../../lib/dateUtils';
|
||||||
import {Calendar, ChevronDown, Copy, FileText, Info, Mail, Plus, RefreshCw, Trash2, Users} from 'lucide-react';
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
Calendar,
|
||||||
|
ChevronDown,
|
||||||
|
Copy,
|
||||||
|
FileText,
|
||||||
|
Info,
|
||||||
|
Mail,
|
||||||
|
MousePointerClick,
|
||||||
|
Plus,
|
||||||
|
RefreshCw,
|
||||||
|
Trash2,
|
||||||
|
Users,
|
||||||
|
} from 'lucide-react';
|
||||||
import {NextSeo} from 'next-seo';
|
import {NextSeo} from 'next-seo';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {useRouter} from 'next/router';
|
import {useRouter} from 'next/router';
|
||||||
@@ -426,7 +439,7 @@ export default function CampaignsPage() {
|
|||||||
{campaign.clickedCount > 0 && (
|
{campaign.clickedCount > 0 && (
|
||||||
<div className="bg-orange-50 border border-orange-100 rounded-lg p-3">
|
<div className="bg-orange-50 border border-orange-100 rounded-lg p-3">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<Mail className="h-3.5 w-3.5 text-orange-600" />
|
<MousePointerClick className="h-3.5 w-3.5 text-orange-600" />
|
||||||
<span className="text-xs font-medium text-orange-900">Clicks</span>
|
<span className="text-xs font-medium text-orange-900">Clicks</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg font-bold text-orange-900">{clickRate.toFixed(1)}%</p>
|
<p className="text-lg font-bold text-orange-900">{clickRate.toFixed(1)}%</p>
|
||||||
@@ -436,6 +449,20 @@ export default function CampaignsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Bounced Rate */}
|
||||||
|
{campaign.bouncedCount > 0 && campaign.sentCount > 0 && (
|
||||||
|
<div className="bg-red-50 border border-red-100 rounded-lg p-3">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<AlertCircle className="h-3.5 w-3.5 text-red-600" />
|
||||||
|
<span className="text-xs font-medium text-red-900">Bounced</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-lg font-bold text-red-900">
|
||||||
|
{((campaign.bouncedCount / campaign.sentCount) * 100).toFixed(1)}%
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-red-700 mt-1">{campaign.bouncedCount.toLocaleString()} bounced</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Scheduled For */}
|
{/* Scheduled For */}
|
||||||
{campaign.scheduledFor && (
|
{campaign.scheduledFor && (
|
||||||
<div className="bg-green-50 border border-green-100 rounded-lg p-3 md:col-span-2">
|
<div className="bg-green-50 border border-green-100 rounded-lg p-3 md:col-span-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user