qrmaster.net
This commit is contained in:
@@ -5,6 +5,20 @@ import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
// Helper function to calculate trend
|
||||
function calculateTrend(current: number, previous: number): { trend: 'up' | 'down' | 'flat'; percentage: number } {
|
||||
if (previous === 0) {
|
||||
return current > 0 ? { trend: 'up', percentage: 100 } : { trend: 'flat', percentage: 0 };
|
||||
}
|
||||
|
||||
const change = ((current - previous) / previous) * 100;
|
||||
const percentage = Math.round(Math.abs(change));
|
||||
|
||||
if (change > 5) return { trend: 'up', percentage };
|
||||
if (change < -5) return { trend: 'down', percentage };
|
||||
return { trend: 'flat', percentage };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
@@ -33,17 +47,58 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user's QR codes
|
||||
// Get date range from query params (default: last 30 days)
|
||||
const { searchParams } = request.nextUrl;
|
||||
const range = searchParams.get('range') || '30';
|
||||
const daysInRange = parseInt(range, 10);
|
||||
|
||||
// Calculate current and previous period dates
|
||||
const now = new Date();
|
||||
const currentPeriodStart = new Date();
|
||||
currentPeriodStart.setDate(now.getDate() - daysInRange);
|
||||
|
||||
const previousPeriodEnd = new Date(currentPeriodStart);
|
||||
const previousPeriodStart = new Date(previousPeriodEnd);
|
||||
previousPeriodStart.setDate(previousPeriodEnd.getDate() - daysInRange);
|
||||
|
||||
// Get user's QR codes with scans filtered by period
|
||||
const qrCodes = await db.qRCode.findMany({
|
||||
where: { userId },
|
||||
include: {
|
||||
scans: true,
|
||||
scans: {
|
||||
where: {
|
||||
ts: {
|
||||
gte: currentPeriodStart,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Calculate stats
|
||||
// Get previous period scans for comparison
|
||||
const qrCodesWithPreviousScans = await db.qRCode.findMany({
|
||||
where: { userId },
|
||||
include: {
|
||||
scans: {
|
||||
where: {
|
||||
ts: {
|
||||
gte: previousPeriodStart,
|
||||
lt: previousPeriodEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Calculate current period stats
|
||||
const totalScans = qrCodes.reduce((sum, qr) => sum + qr.scans.length, 0);
|
||||
const uniqueScans = qrCodes.reduce((sum, qr) =>
|
||||
const uniqueScans = qrCodes.reduce((sum, qr) =>
|
||||
sum + qr.scans.filter(s => s.isUnique).length, 0
|
||||
);
|
||||
|
||||
// Calculate previous period stats for comparison
|
||||
const previousTotalScans = qrCodesWithPreviousScans.reduce((sum, qr) => sum + qr.scans.length, 0);
|
||||
const previousUniqueScans = qrCodesWithPreviousScans.reduce((sum, qr) =>
|
||||
sum + qr.scans.filter(s => s.isUnique).length, 0
|
||||
);
|
||||
|
||||
@@ -60,44 +115,59 @@ export async function GET(request: NextRequest) {
|
||||
? Math.round((mobileScans / totalScans) * 100)
|
||||
: 0;
|
||||
|
||||
// Country stats
|
||||
// Country stats (current period)
|
||||
const countryStats = qrCodes.flatMap(qr => qr.scans)
|
||||
.reduce((acc, scan) => {
|
||||
const country = scan.country || 'Unknown';
|
||||
acc[country] = (acc[country] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
|
||||
// Country stats (previous period)
|
||||
const previousCountryStats = qrCodesWithPreviousScans.flatMap(qr => qr.scans)
|
||||
.reduce((acc, scan) => {
|
||||
const country = scan.country || 'Unknown';
|
||||
acc[country] = (acc[country] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
const topCountry = Object.entries(countryStats)
|
||||
.sort(([,a], [,b]) => b - a)[0];
|
||||
|
||||
// Time-based stats (last 30 days)
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
const recentScans = qrCodes.flatMap(qr => qr.scans)
|
||||
.filter(scan => new Date(scan.ts) > thirtyDaysAgo);
|
||||
|
||||
// Daily scan counts for chart
|
||||
const dailyScans = recentScans.reduce((acc, scan) => {
|
||||
// Daily scan counts for chart (current period)
|
||||
const dailyScans = qrCodes.flatMap(qr => qr.scans).reduce((acc, scan) => {
|
||||
const date = new Date(scan.ts).toISOString().split('T')[0];
|
||||
acc[date] = (acc[date] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
|
||||
// QR performance (only show DYNAMIC QR codes since STATIC don't track scans)
|
||||
const qrPerformance = qrCodes
|
||||
.filter(qr => qr.type === 'DYNAMIC')
|
||||
.map(qr => ({
|
||||
id: qr.id,
|
||||
title: qr.title,
|
||||
type: qr.type,
|
||||
totalScans: qr.scans.length,
|
||||
uniqueScans: qr.scans.filter(s => s.isUnique).length,
|
||||
conversion: qr.scans.length > 0
|
||||
? Math.round((qr.scans.filter(s => s.isUnique).length / qr.scans.length) * 100)
|
||||
: 0,
|
||||
}))
|
||||
.map(qr => {
|
||||
const currentTotal = qr.scans.length;
|
||||
const currentUnique = qr.scans.filter(s => s.isUnique).length;
|
||||
|
||||
// Find previous period data for this QR code
|
||||
const previousQR = qrCodesWithPreviousScans.find(prev => prev.id === qr.id);
|
||||
const previousTotal = previousQR ? previousQR.scans.length : 0;
|
||||
|
||||
// Calculate trend
|
||||
const trendData = calculateTrend(currentTotal, previousTotal);
|
||||
|
||||
return {
|
||||
id: qr.id,
|
||||
title: qr.title,
|
||||
type: qr.type,
|
||||
totalScans: currentTotal,
|
||||
uniqueScans: currentUnique,
|
||||
conversion: currentTotal > 0
|
||||
? Math.round((currentUnique / currentTotal) * 100)
|
||||
: 0,
|
||||
trend: trendData.trend,
|
||||
trendPercentage: trendData.percentage,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.totalScans - a.totalScans);
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -117,13 +187,20 @@ export async function GET(request: NextRequest) {
|
||||
countryStats: Object.entries(countryStats)
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([country, count]) => ({
|
||||
country,
|
||||
count,
|
||||
percentage: totalScans > 0
|
||||
? Math.round((count / totalScans) * 100)
|
||||
: 0,
|
||||
})),
|
||||
.map(([country, count]) => {
|
||||
const previousCount = previousCountryStats[country] || 0;
|
||||
const trendData = calculateTrend(count, previousCount);
|
||||
|
||||
return {
|
||||
country,
|
||||
count,
|
||||
percentage: totalScans > 0
|
||||
? Math.round((count / totalScans) * 100)
|
||||
: 0,
|
||||
trend: trendData.trend,
|
||||
trendPercentage: trendData.percentage,
|
||||
};
|
||||
}),
|
||||
dailyScans,
|
||||
qrPerformance: qrPerformance.slice(0, 10),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user