Files
Content-Hunter_BE/src/modules/analytics/services/performance-dashboard.service.ts
Harun CAN fc88faddb9
All checks were successful
Backend Deploy 🚀 / build-and-deploy (push) Successful in 2m1s
main
2026-02-10 12:27:14 +03:00

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',
},
];
}
}