generated from fahricansecer/boilerplate-be
404 lines
14 KiB
TypeScript
404 lines
14 KiB
TypeScript
// Performance Dashboard Service - Comprehensive analytics dashboard
|
|
// Path: src/modules/analytics/services/performance-dashboard.service.ts
|
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
|
|
export interface DashboardOverview {
|
|
period: string;
|
|
totalPosts: number;
|
|
totalEngagements: number;
|
|
avgEngagementRate: number;
|
|
totalReach: number;
|
|
totalImpressions: number;
|
|
followerGrowth: number;
|
|
topPlatform: string;
|
|
goldPostsCount: number;
|
|
abTestsActive: number;
|
|
}
|
|
|
|
export interface PlatformBreakdown {
|
|
platform: string;
|
|
posts: number;
|
|
engagements: number;
|
|
avgEngagementRate: number;
|
|
reach: number;
|
|
impressions: number;
|
|
bestContentType: string;
|
|
growthTrend: 'up' | 'down' | 'stable';
|
|
comparedToLastPeriod: number;
|
|
}
|
|
|
|
export interface ContentTypeAnalysis {
|
|
type: string;
|
|
count: number;
|
|
avgEngagement: number;
|
|
avgReach: number;
|
|
performanceScore: number;
|
|
trend: 'improving' | 'declining' | 'stable';
|
|
}
|
|
|
|
export interface TimeAnalysis {
|
|
dayOfWeek: string;
|
|
hourSlot: string;
|
|
avgEngagement: number;
|
|
postCount: number;
|
|
recommendation: string;
|
|
}
|
|
|
|
export interface DashboardWidget {
|
|
id: string;
|
|
type: 'chart' | 'metric' | 'table' | 'heatmap' | 'comparison';
|
|
title: string;
|
|
data: any;
|
|
config: Record<string, any>;
|
|
}
|
|
|
|
export interface DashboardLayout {
|
|
userId: string;
|
|
widgets: DashboardWidget[];
|
|
customization: {
|
|
theme: 'light' | 'dark';
|
|
refreshInterval: number;
|
|
dateRange: string;
|
|
};
|
|
}
|
|
|
|
@Injectable()
|
|
export class PerformanceDashboardService {
|
|
private readonly logger = new Logger(PerformanceDashboardService.name);
|
|
private dashboardData: Map<string, any> = new Map();
|
|
private userLayouts: Map<string, DashboardLayout> = new Map();
|
|
|
|
/**
|
|
* Get dashboard overview
|
|
*/
|
|
getDashboardOverview(userId: string, period: 'day' | 'week' | 'month' | 'quarter' | 'year'): DashboardOverview {
|
|
// Generate realistic dashboard data
|
|
const multipliers = { day: 1, week: 7, month: 30, quarter: 90, year: 365 };
|
|
const m = multipliers[period];
|
|
|
|
return {
|
|
period,
|
|
totalPosts: Math.floor(3 * m + Math.random() * 2 * m),
|
|
totalEngagements: Math.floor(1500 * m + Math.random() * 1000 * m),
|
|
avgEngagementRate: 2.5 + Math.random() * 3,
|
|
totalReach: Math.floor(15000 * m + Math.random() * 10000 * m),
|
|
totalImpressions: Math.floor(25000 * m + Math.random() * 15000 * m),
|
|
followerGrowth: Math.floor(50 * m + Math.random() * 30 * m),
|
|
topPlatform: ['instagram', 'twitter', 'linkedin', 'tiktok'][Math.floor(Math.random() * 4)],
|
|
goldPostsCount: Math.floor(m / 10) + 1,
|
|
abTestsActive: Math.floor(Math.random() * 5) + 1,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get platform breakdown
|
|
*/
|
|
getPlatformBreakdown(userId: string, period: string): PlatformBreakdown[] {
|
|
const platforms = ['twitter', 'instagram', 'linkedin', 'facebook', 'tiktok', 'youtube'];
|
|
const contentTypes = ['video', 'carousel', 'single_image', 'text', 'story', 'reel'];
|
|
|
|
return platforms.map(platform => ({
|
|
platform,
|
|
posts: Math.floor(10 + Math.random() * 40),
|
|
engagements: Math.floor(500 + Math.random() * 2000),
|
|
avgEngagementRate: 1.5 + Math.random() * 4,
|
|
reach: Math.floor(5000 + Math.random() * 20000),
|
|
impressions: Math.floor(10000 + Math.random() * 40000),
|
|
bestContentType: contentTypes[Math.floor(Math.random() * contentTypes.length)],
|
|
growthTrend: ['up', 'down', 'stable'][Math.floor(Math.random() * 3)] as 'up' | 'down' | 'stable',
|
|
comparedToLastPeriod: -15 + Math.random() * 40,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Get content type analysis
|
|
*/
|
|
getContentTypeAnalysis(userId: string): ContentTypeAnalysis[] {
|
|
const types = [
|
|
{ type: 'video', base: 4.5 },
|
|
{ type: 'carousel', base: 3.8 },
|
|
{ type: 'single_image', base: 2.5 },
|
|
{ type: 'text', base: 1.8 },
|
|
{ type: 'story', base: 3.2 },
|
|
{ type: 'reel', base: 5.5 },
|
|
{ type: 'live', base: 6.0 },
|
|
{ type: 'poll', base: 4.0 },
|
|
];
|
|
|
|
return types.map(t => ({
|
|
type: t.type,
|
|
count: Math.floor(5 + Math.random() * 30),
|
|
avgEngagement: t.base + Math.random() * 2,
|
|
avgReach: Math.floor(1000 + Math.random() * 5000),
|
|
performanceScore: Math.floor(50 + Math.random() * 50),
|
|
trend: ['improving', 'declining', 'stable'][Math.floor(Math.random() * 3)] as 'improving' | 'declining' | 'stable',
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Get time-based analysis (best times to post)
|
|
*/
|
|
getTimeAnalysis(userId: string, platform?: string): TimeAnalysis[] {
|
|
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
const hours = ['6-9 AM', '9-12 PM', '12-3 PM', '3-6 PM', '6-9 PM', '9-12 AM'];
|
|
|
|
const analysis: TimeAnalysis[] = [];
|
|
|
|
for (const day of days) {
|
|
for (const hour of hours) {
|
|
const engagement = 1 + Math.random() * 6;
|
|
const postCount = Math.floor(Math.random() * 10);
|
|
|
|
let recommendation = 'Good time to post';
|
|
if (engagement > 4) recommendation = 'Excellent time - high engagement expected';
|
|
else if (engagement < 2) recommendation = 'Avoid posting - low engagement expected';
|
|
|
|
analysis.push({
|
|
dayOfWeek: day,
|
|
hourSlot: hour,
|
|
avgEngagement: engagement,
|
|
postCount,
|
|
recommendation,
|
|
});
|
|
}
|
|
}
|
|
|
|
return analysis;
|
|
}
|
|
|
|
/**
|
|
* Get engagement heatmap data
|
|
*/
|
|
getEngagementHeatmap(userId: string): number[][] {
|
|
// 7 days x 24 hours matrix of engagement scores (0-100)
|
|
return Array.from({ length: 7 }, () =>
|
|
Array.from({ length: 24 }, () => Math.floor(Math.random() * 100))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get comparison data (vs previous period)
|
|
*/
|
|
getComparisonData(userId: string, currentPeriod: string): {
|
|
current: DashboardOverview;
|
|
previous: DashboardOverview;
|
|
changes: Record<string, { value: number; percentage: number; trend: 'up' | 'down' | 'stable' }>;
|
|
} {
|
|
const current = this.getDashboardOverview(userId, currentPeriod as any);
|
|
const previous = this.getDashboardOverview(userId, currentPeriod as any);
|
|
|
|
const calculate = (curr: number, prev: number) => {
|
|
const change = curr - prev;
|
|
const percentage = prev > 0 ? (change / prev) * 100 : 0;
|
|
const trend: 'up' | 'down' | 'stable' =
|
|
percentage > 5 ? 'up' : percentage < -5 ? 'down' : 'stable';
|
|
return { value: change, percentage, trend };
|
|
};
|
|
|
|
return {
|
|
current,
|
|
previous,
|
|
changes: {
|
|
posts: calculate(current.totalPosts, previous.totalPosts),
|
|
engagements: calculate(current.totalEngagements, previous.totalEngagements),
|
|
engagementRate: calculate(current.avgEngagementRate, previous.avgEngagementRate),
|
|
reach: calculate(current.totalReach, previous.totalReach),
|
|
followers: calculate(current.followerGrowth, previous.followerGrowth),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get dashboard widgets
|
|
*/
|
|
getDefaultWidgets(): DashboardWidget[] {
|
|
return [
|
|
{
|
|
id: 'overview',
|
|
type: 'metric',
|
|
title: 'Performance Overview',
|
|
data: null,
|
|
config: { size: 'large', position: { row: 0, col: 0 } },
|
|
},
|
|
{
|
|
id: 'engagement-trend',
|
|
type: 'chart',
|
|
title: 'Engagement Trend',
|
|
data: null,
|
|
config: { chartType: 'line', size: 'medium', position: { row: 0, col: 1 } },
|
|
},
|
|
{
|
|
id: 'platform-breakdown',
|
|
type: 'chart',
|
|
title: 'Platform Performance',
|
|
data: null,
|
|
config: { chartType: 'bar', size: 'medium', position: { row: 1, col: 0 } },
|
|
},
|
|
{
|
|
id: 'content-types',
|
|
type: 'chart',
|
|
title: 'Content Type Analysis',
|
|
data: null,
|
|
config: { chartType: 'pie', size: 'small', position: { row: 1, col: 1 } },
|
|
},
|
|
{
|
|
id: 'best-times',
|
|
type: 'heatmap',
|
|
title: 'Best Times to Post',
|
|
data: null,
|
|
config: { size: 'large', position: { row: 2, col: 0 } },
|
|
},
|
|
{
|
|
id: 'top-posts',
|
|
type: 'table',
|
|
title: 'Top Performing Posts',
|
|
data: null,
|
|
config: { columns: ['title', 'platform', 'engagement', 'reach'], position: { row: 2, col: 1 } },
|
|
},
|
|
{
|
|
id: 'growth-metrics',
|
|
type: 'comparison',
|
|
title: 'Growth vs Last Period',
|
|
data: null,
|
|
config: { size: 'medium', position: { row: 3, col: 0 } },
|
|
},
|
|
{
|
|
id: 'gold-posts',
|
|
type: 'table',
|
|
title: 'Gold Posts',
|
|
data: null,
|
|
config: { highlight: true, position: { row: 3, col: 1 } },
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get/create user dashboard layout
|
|
*/
|
|
getDashboardLayout(userId: string): DashboardLayout {
|
|
let layout = this.userLayouts.get(userId);
|
|
|
|
if (!layout) {
|
|
layout = {
|
|
userId,
|
|
widgets: this.getDefaultWidgets(),
|
|
customization: {
|
|
theme: 'dark',
|
|
refreshInterval: 60000,
|
|
dateRange: 'week',
|
|
},
|
|
};
|
|
this.userLayouts.set(userId, layout);
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
/**
|
|
* Update dashboard layout
|
|
*/
|
|
updateDashboardLayout(userId: string, updates: Partial<DashboardLayout>): DashboardLayout {
|
|
const current = this.getDashboardLayout(userId);
|
|
const updated = { ...current, ...updates };
|
|
this.userLayouts.set(userId, updated);
|
|
return updated;
|
|
}
|
|
|
|
/**
|
|
* Add custom widget
|
|
*/
|
|
addWidget(userId: string, widget: Omit<DashboardWidget, 'id'>): DashboardWidget {
|
|
const layout = this.getDashboardLayout(userId);
|
|
const newWidget: DashboardWidget = {
|
|
...widget,
|
|
id: `widget-${Date.now()}`,
|
|
};
|
|
layout.widgets.push(newWidget);
|
|
this.userLayouts.set(userId, layout);
|
|
return newWidget;
|
|
}
|
|
|
|
/**
|
|
* Remove widget
|
|
*/
|
|
removeWidget(userId: string, widgetId: string): boolean {
|
|
const layout = this.getDashboardLayout(userId);
|
|
const index = layout.widgets.findIndex(w => w.id === widgetId);
|
|
if (index === -1) return false;
|
|
|
|
layout.widgets.splice(index, 1);
|
|
this.userLayouts.set(userId, layout);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Export dashboard data
|
|
*/
|
|
exportDashboardData(userId: string, format: 'json' | 'csv'): string {
|
|
const overview = this.getDashboardOverview(userId, 'month');
|
|
const platforms = this.getPlatformBreakdown(userId, 'month');
|
|
const contentTypes = this.getContentTypeAnalysis(userId);
|
|
|
|
if (format === 'json') {
|
|
return JSON.stringify({ overview, platforms, contentTypes }, null, 2);
|
|
}
|
|
|
|
// CSV format
|
|
let csv = 'Metric,Value\n';
|
|
csv += `Total Posts,${overview.totalPosts}\n`;
|
|
csv += `Total Engagements,${overview.totalEngagements}\n`;
|
|
csv += `Avg Engagement Rate,${overview.avgEngagementRate.toFixed(2)}%\n`;
|
|
csv += `Total Reach,${overview.totalReach}\n`;
|
|
csv += `Follower Growth,${overview.followerGrowth}\n`;
|
|
csv += '\nPlatform,Posts,Engagements,Avg Rate\n';
|
|
for (const p of platforms) {
|
|
csv += `${p.platform},${p.posts},${p.engagements},${p.avgEngagementRate.toFixed(2)}%\n`;
|
|
}
|
|
return csv;
|
|
}
|
|
|
|
/**
|
|
* Get insights and recommendations
|
|
*/
|
|
getInsights(userId: string): Array<{
|
|
type: 'success' | 'warning' | 'info' | 'action';
|
|
title: string;
|
|
description: string;
|
|
priority: 'high' | 'medium' | 'low';
|
|
}> {
|
|
return [
|
|
{
|
|
type: 'success',
|
|
title: 'Strong Video Performance',
|
|
description: 'Your video content is outperforming other formats by 45%. Consider increasing video production.',
|
|
priority: 'high',
|
|
},
|
|
{
|
|
type: 'warning',
|
|
title: 'Engagement Drop on Weekends',
|
|
description: 'Weekend posts show 30% lower engagement. Consider rescheduling to weekday evenings.',
|
|
priority: 'medium',
|
|
},
|
|
{
|
|
type: 'action',
|
|
title: 'Untapped LinkedIn Potential',
|
|
description: 'LinkedIn shows high engagement but low posting frequency. Increase LinkedIn content.',
|
|
priority: 'high',
|
|
},
|
|
{
|
|
type: 'info',
|
|
title: 'Optimal Posting Time Detected',
|
|
description: 'Best engagement window: Tuesday-Thursday, 6-9 PM in your timezone.',
|
|
priority: 'medium',
|
|
},
|
|
{
|
|
type: 'success',
|
|
title: 'Gold Post Streak',
|
|
description: 'You\'ve had 3 Gold Posts this week! Your viral content formula is working.',
|
|
priority: 'low',
|
|
},
|
|
];
|
|
}
|
|
}
|