feat: add header channel selector & adding channel modal menu

This commit is contained in:
ivannoskov
2025-12-01 05:57:28 +03:00
parent d4672ea32b
commit d2cc1c39b3
22 changed files with 3473 additions and 892 deletions

View File

@@ -4,40 +4,41 @@
import { api } from "./client";
import type {
AnalyticsOverview,
CostsReport,
AnalyticsChannelData,
AnalyticsCreativeData,
SpendingAnalytics,
PlacementsAnalytics,
CreativesAnalytics,
ExternalChannelsAnalytics,
DateGrouping,
} from "@/lib/types/api";
export const analyticsApi = {
/**
* Get overview analytics
* Get spending analytics
*/
overview: (params?: { target_channel_id?: string }) =>
api.get<AnalyticsOverview>("/analytics/overview", { params }),
/**
* Get costs report
*/
costs: (params: {
period?: "day" | "week" | "month";
spending: (params?: {
target_channel_id?: string;
}) => api.get<CostsReport>("/analytics/costs", { params }),
date_from?: string;
date_to?: string;
grouping?: DateGrouping;
}) => api.get<SpendingAnalytics>("/analytics/spending", { params }),
/**
* Get channels analytics
* Get placements analytics
*/
channels: (params?: { target_channel_id?: string }) =>
api.get<{ data: AnalyticsChannelData[] }>("/analytics/channels", {
params,
}),
placements: (params?: { target_channel_id?: string }) =>
api.get<PlacementsAnalytics>("/analytics/placements", { params }),
/**
* Get creatives analytics
*/
creatives: (params?: { target_channel_id?: string }) =>
api.get<{ data: AnalyticsCreativeData[] }>("/analytics/creatives", {
api.get<CreativesAnalytics>("/analytics/creatives", { params }),
/**
* Get external channels analytics
*/
externalChannels: (params?: { target_channel_id?: string }) =>
api.get<ExternalChannelsAnalytics>("/analytics/external-channels", {
params,
}),
};

View File

@@ -205,17 +205,17 @@ const routeMockRequest = async (
}
// Analytics
if (endpoint === "/analytics/overview") {
return mockApiHandlers.getAnalyticsOverview(params);
if (endpoint === "/analytics/spending") {
return mockApiHandlers.getSpendingAnalytics(params);
}
if (endpoint === "/analytics/costs") {
return mockApiHandlers.getAnalyticsCosts(params);
}
if (endpoint === "/analytics/channels") {
return mockApiHandlers.getAnalyticsChannels(params);
if (endpoint === "/analytics/placements") {
return mockApiHandlers.getPlacementsAnalytics(params);
}
if (endpoint === "/analytics/creatives") {
return mockApiHandlers.getAnalyticsCreatives(params);
return mockApiHandlers.getCreativesAnalytics(params);
}
if (endpoint === "/analytics/external-channels") {
return mockApiHandlers.getExternalChannelsAnalytics(params);
}
throw {

View File

@@ -1,277 +0,0 @@
import type {
AnalyticsOverview,
CostsReport,
AnalyticsChannelData,
AnalyticsCreativeData,
} from "@/lib/types/api";
// ============================================================================
// Mock Analytics Data
// ============================================================================
export const mockAnalyticsOverview: AnalyticsOverview = {
totalPurchases: 25,
purchasesChange: 5,
totalFollowers: 2500,
followersChange: 320,
averageCpf: 50.0,
cpfChange: -5.2,
averageCpm: 450.0,
cpmChange: -3.8,
topChannelsByCpf: [
{
channel_id: "ec3",
channel_name: "IT и Технологии",
total_purchases: 3,
avg_cpf: 38.2,
},
{
channel_id: "ec1",
channel_name: "Канал о Маркетинге",
total_purchases: 5,
avg_cpf: 40.0,
},
{
channel_id: "ec5",
channel_name: "Продажи и Переговоры",
total_purchases: 4,
avg_cpf: 45.0,
},
{
channel_id: "ec2",
channel_name: "Бизнес и Стартапы",
total_purchases: 8,
avg_cpf: 55.8,
},
{
channel_id: "ec4",
channel_name: "Крипто Инвестиции",
total_purchases: 2,
avg_cpf: 68.5,
},
],
topCreativesByCpf: [
{
creative_id: "cr1",
creative_name: 'Креатив "Присоединяйся"',
total_subscriptions: 1180,
avg_cpf: 42.4,
},
{
creative_id: "cr2",
creative_name: 'Креатив "Эксклюзив"',
total_subscriptions: 577,
avg_cpf: 48.5,
},
{
creative_id: "cr3",
creative_name: 'Креатив "Обучение"',
total_subscriptions: 803,
avg_cpf: 52.3,
},
],
};
// Mock data for costs report
export const mockCostsReportByDay: CostsReport = {
totalCost: 76500,
averageCost: 7650,
periods: [
{
date: "2024-02-20",
total_cost: 5000,
purchases_count: 1,
avg_cost: 5000,
},
{
date: "2024-02-22",
total_cost: 3500,
purchases_count: 1,
avg_cost: 3500,
},
{
date: "2024-02-25",
total_cost: 7000,
purchases_count: 2,
avg_cost: 3500,
},
{
date: "2024-02-28",
total_cost: 4500,
purchases_count: 1,
avg_cost: 4500,
},
{
date: "2024-03-02",
total_cost: 8500,
purchases_count: 2,
avg_cost: 4250,
},
{
date: "2024-03-05",
total_cost: 6000,
purchases_count: 1,
avg_cost: 6000,
},
{
date: "2024-03-08",
total_cost: 12000,
purchases_count: 3,
avg_cost: 4000,
},
{
date: "2024-03-12",
total_cost: 5500,
purchases_count: 1,
avg_cost: 5500,
},
{
date: "2024-03-15",
total_cost: 15000,
purchases_count: 3,
avg_cost: 5000,
},
{
date: "2024-03-18",
total_cost: 9500,
purchases_count: 2,
avg_cost: 4750,
},
],
};
export const mockCostsReportByWeek: CostsReport = {
totalCost: 125000,
averageCost: 15625,
periods: [
{
date: "2024-02-19",
total_cost: 15500,
purchases_count: 4,
avg_cost: 3875,
},
{
date: "2024-02-26",
total_cost: 13000,
purchases_count: 3,
avg_cost: 4333,
},
{
date: "2024-03-04",
total_cost: 26500,
purchases_count: 6,
avg_cost: 4417,
},
{
date: "2024-03-11",
total_cost: 33000,
purchases_count: 7,
avg_cost: 4714,
},
{
date: "2024-03-18",
total_cost: 24500,
purchases_count: 5,
avg_cost: 4900,
},
],
};
export const mockCostsReportByMonth: CostsReport = {
totalCost: 125000,
averageCost: 62500,
periods: [
{
date: "2024-02-01",
total_cost: 20000,
purchases_count: 5,
avg_cost: 4000,
},
{
date: "2024-03-01",
total_cost: 105000,
purchases_count: 20,
avg_cost: 5250,
},
],
};
// Mock data for channels analytics
export const mockAnalyticsChannelsData: AnalyticsChannelData[] = [
{
channel_id: "ec1",
channel_name: "Канал о Маркетинге",
purchases_count: 5,
total_cost: 25000,
total_subscriptions: 625,
avg_cpf: 40.0,
avg_cpm: 380.0,
},
{
channel_id: "ec2",
channel_name: "Бизнес и Стартапы",
purchases_count: 8,
total_cost: 48000,
total_subscriptions: 860,
avg_cpf: 55.8,
avg_cpm: 450.0,
},
{
channel_id: "ec3",
channel_name: "IT и Технологии",
purchases_count: 3,
total_cost: 12000,
total_subscriptions: 314,
avg_cpf: 38.2,
avg_cpm: 360.0,
},
{
channel_id: "ec4",
channel_name: "Крипто Инвестиции",
purchases_count: 2,
total_cost: 18000,
total_subscriptions: 263,
avg_cpf: 68.5,
avg_cpm: 520.0,
},
{
channel_id: "ec5",
channel_name: "Продажи и Переговоры",
purchases_count: 4,
total_cost: 16000,
total_subscriptions: 356,
avg_cpf: 45.0,
avg_cpm: 410.0,
},
];
// Mock data for creatives analytics
export const mockAnalyticsCreativesData: AnalyticsCreativeData[] = [
{
creative_id: "cr1",
creative_name: 'Креатив "Присоединяйся"',
purchases_count: 10,
total_cost: 50000,
total_subscriptions: 1180,
avg_cpf: 42.4,
conversion_rate: 0.95,
},
{
creative_id: "cr2",
creative_name: 'Креатив "Эксклюзив"',
purchases_count: 5,
total_cost: 28000,
total_subscriptions: 577,
avg_cpf: 48.5,
conversion_rate: 0.88,
},
{
creative_id: "cr3",
creative_name: 'Креатив "Обучение"',
purchases_count: 8,
total_cost: 42000,
total_subscriptions: 803,
avg_cpf: 52.3,
conversion_rate: 0.92,
},
];

View File

@@ -11,12 +11,6 @@ import {
mockPlacements,
mockSubscriptions,
mockViewsHistory,
mockAnalyticsOverview,
mockCostsReportByDay,
mockCostsReportByWeek,
mockCostsReportByMonth,
mockAnalyticsChannelsData,
mockAnalyticsCreativesData,
} from "./index";
import type {
@@ -481,50 +475,99 @@ export const mockApiHandlers = {
// --------------------------------------------------------------------------
// Analytics
// --------------------------------------------------------------------------
async getAnalyticsOverview(params?: {
async getSpendingAnalytics(params?: {
target_channel_id?: string;
date_from?: string;
date_to?: string;
}) {
await delay();
return mockAnalyticsOverview;
},
async getAnalyticsCosts(params?: {
period?: "day" | "week" | "month";
target_channel_id?: string;
}) {
await delay();
const period = params?.period || "week";
if (period === "day") {
return mockCostsReportByDay;
} else if (period === "month") {
return mockCostsReportByMonth;
}
return mockCostsReportByWeek;
},
async getAnalyticsChannels(params?: {
target_channel_id?: string;
date_from?: string;
date_to?: string;
type?: "external" | "target";
grouping?: "day" | "week" | "month" | "quarter" | "year";
}) {
await delay();
return {
data: mockAnalyticsChannelsData,
total_cost: 45000,
total_subscriptions: 850,
total_views: 125000,
avg_cpf: 52.94,
avg_cpm: 360,
chart_data: [
{
period: "2024-03-01",
cost: 15000,
subscriptions: 280,
views: 42000,
cpf: 53.57,
cpm: 357.14,
},
{
period: "2024-03-08",
cost: 18000,
subscriptions: 340,
views: 51000,
cpf: 52.94,
cpm: 352.94,
},
{
period: "2024-03-15",
cost: 12000,
subscriptions: 230,
views: 32000,
cpf: 52.17,
cpm: 375,
},
],
};
},
async getAnalyticsCreatives(params?: {
target_channel_id?: string;
date_from?: string;
date_to?: string;
}) {
async getPlacementsAnalytics(params?: { target_channel_id?: string }) {
await delay();
return {
data: mockAnalyticsCreativesData,
placements: mockPlacements.map((p) => ({
id: p.id,
target_channel_id: p.target_channel_id,
target_channel_title: p.target_channel_title,
external_channel_id: p.external_channel_id,
external_channel_title: p.external_channel_title,
creative_id: p.creative_id,
creative_name: p.creative_name,
placement_date: p.placement_date,
cost: p.cost,
subscriptions_count: p.subscriptions_count,
views_count: p.views_count,
cpf: p.cost && p.subscriptions_count > 0 ? p.cost / p.subscriptions_count : null,
cpm: p.cost && p.views_count ? (p.cost / p.views_count) * 1000 : null,
})),
};
},
async getCreativesAnalytics(params?: { target_channel_id?: string }) {
await delay();
return {
creatives: mockCreatives.map((c) => ({
id: c.id,
name: c.name,
placements_count: c.placements_count,
total_cost: 15000,
total_subscriptions: 280,
total_views: 42000,
avg_cpf: 53.57,
avg_cpm: 357.14,
})),
};
},
async getExternalChannelsAnalytics(params?: { target_channel_id?: string }) {
await delay();
return {
external_channels: mockExternalChannels.map((ec) => ({
id: ec.id,
title: ec.title,
username: ec.username,
placements_count: 3,
total_cost: 12000,
total_subscriptions: 210,
total_views: 28000,
avg_cpf: 57.14,
avg_cpm: 428.57,
})),
};
},
};

View File

@@ -7,4 +7,3 @@ export * from "./data/channels";
export * from "./data/external-channels";
export * from "./data/creatives";
export * from "./data/placements";
export * from "./data/analytics";

View File

@@ -238,79 +238,79 @@ export interface ViewsSnapshot {
// Analytics
// ----------------------------------------------------------------------------
export interface AnalyticsOverview {
totalPurchases: number;
purchasesChange: number;
totalFollowers: number;
followersChange: number;
averageCpf: number;
cpfChange: number;
averageCpm: number;
cpmChange: number;
topChannelsByCpf: {
channel_id: string;
channel_name: string;
total_purchases: number;
avg_cpf: number;
}[];
topCreativesByCpf: {
creative_id: string;
creative_name: string;
total_subscriptions: number;
avg_cpf: number;
}[];
}
export type DateGrouping = "day" | "week" | "month" | "quarter" | "year";
export type GroupByPeriod = "day" | "week" | "month" | "quarter" | "year";
export interface AnalyticsCostsDataPoint {
period: string; // ISO 8601 date (start of period)
// Spending Analytics
export interface SpendingDataPoint {
period: string;
cost: number;
purchases_count: number;
subscriptions: number;
views: number;
cpf: number | null;
cpm: number | null;
}
export interface AnalyticsCostsResponse {
data: AnalyticsCostsDataPoint[];
}
export interface CostsReport {
totalCost: number;
averageCost: number;
periods: {
date: string;
total_cost: number;
purchases_count: number;
avg_cost: number;
}[];
}
export interface AnalyticsChannelData {
channel_id: string;
channel_name: string;
purchases_count: number;
export interface SpendingAnalytics {
total_cost: number;
total_subscriptions: number;
avg_cpf: number;
avg_cpm: number;
total_views: number;
avg_cpf: number | null;
avg_cpm: number | null;
chart_data: SpendingDataPoint[];
}
export interface AnalyticsChannelsResponse {
data: AnalyticsChannelData[];
}
export interface AnalyticsCreativeData {
// Placements Analytics
export interface PlacementAnalytics {
id: string;
target_channel_id: string;
target_channel_title: string;
external_channel_id: string;
external_channel_title: string;
creative_id: string;
creative_name: string;
purchases_count: number;
total_cost: number;
total_subscriptions: number;
avg_cpf: number;
conversion_rate: number;
placement_date: string;
cost: number | null;
subscriptions_count: number;
views_count: number | null;
cpf: number | null;
cpm: number | null;
}
export interface AnalyticsCreativesResponse {
data: AnalyticsCreativeData[];
export interface PlacementsAnalytics {
placements: PlacementAnalytics[];
}
// Creatives Analytics
export interface CreativeAnalytics {
id: string;
name: string;
placements_count: number;
total_cost: number;
total_subscriptions: number;
total_views: number;
avg_cpf: number | null;
avg_cpm: number | null;
}
export interface CreativesAnalytics {
creatives: CreativeAnalytics[];
}
// External Channels Analytics
export interface ExternalChannelAnalytics {
id: string;
title: string;
username: string | null;
placements_count: number;
total_cost: number;
total_subscriptions: number;
total_views: number;
avg_cpf: number | null;
avg_cpm: number | null;
}
export interface ExternalChannelsAnalytics {
external_channels: ExternalChannelAnalytics[];
}
// ----------------------------------------------------------------------------
@@ -369,22 +369,3 @@ export interface AnalyticsOverviewQueryParams {
date_to?: string;
}
export interface AnalyticsCostsQueryParams {
target_channel_id?: string;
date_from: string; // required
date_to: string; // required
group_by: GroupByPeriod;
}
export interface AnalyticsChannelsQueryParams {
target_channel_id?: string;
date_from?: string;
date_to?: string;
type: "external" | "target";
}
export interface AnalyticsCreativesQueryParams {
target_channel_id?: string;
date_from?: string;
date_to?: string;
}

View File

@@ -14,6 +14,7 @@ export const STORAGE_KEYS = {
ACCESS_TOKEN: "tgex_access_token",
REFRESH_TOKEN: "tgex_refresh_token",
USER: "tgex_user",
SELECTED_TARGET_CHANNEL: "tgex_selected_target_channel",
} as const;
// API Endpoints