feat: global platform v2 update
This commit is contained in:
196
lib/demo/api.ts
Normal file
196
lib/demo/api.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
// ============================================================================
|
||||
// Demo API - Mock responses for demo mode
|
||||
// ============================================================================
|
||||
|
||||
import type {
|
||||
PaginatedResponse,
|
||||
Workspace,
|
||||
Project,
|
||||
Channel,
|
||||
Creative,
|
||||
Placement,
|
||||
PurchasePlanChannel,
|
||||
WorkspaceMember,
|
||||
SpendingAnalytics,
|
||||
CreativeAnalyticsItem,
|
||||
ChannelAnalyticsItem,
|
||||
} from "@/lib/types/api";
|
||||
import {
|
||||
demoWorkspace,
|
||||
demoMember,
|
||||
demoProjects,
|
||||
demoChannels,
|
||||
demoCreatives,
|
||||
demoPlacements,
|
||||
demoPurchasePlanChannels,
|
||||
demoSpendingAnalytics,
|
||||
demoCreativeAnalytics,
|
||||
demoChannelAnalytics,
|
||||
} from "./mock-data";
|
||||
import { DEMO_USER } from "./constants";
|
||||
|
||||
// ============================================================================
|
||||
// Helper to create paginated response
|
||||
// ============================================================================
|
||||
|
||||
const paginate = <T>(items: T[], page = 1, size = 50): PaginatedResponse<T> => ({
|
||||
items: items.slice((page - 1) * size, page * size),
|
||||
total: items.length,
|
||||
page,
|
||||
size,
|
||||
pages: Math.ceil(items.length / size),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Demo API Methods
|
||||
// ============================================================================
|
||||
|
||||
export const demoApi = {
|
||||
// Auth
|
||||
auth: {
|
||||
me: () => Promise.resolve(DEMO_USER),
|
||||
},
|
||||
|
||||
// Workspaces
|
||||
workspaces: {
|
||||
list: () => Promise.resolve(paginate([demoWorkspace])),
|
||||
get: () => Promise.resolve(demoWorkspace),
|
||||
},
|
||||
|
||||
// Members
|
||||
members: {
|
||||
list: () => Promise.resolve(paginate([demoMember])),
|
||||
},
|
||||
|
||||
// Projects
|
||||
projects: {
|
||||
list: (params?: { project_id?: string }) => {
|
||||
let items = demoProjects;
|
||||
if (params?.project_id) {
|
||||
items = items.filter((p) => p.id === params.project_id);
|
||||
}
|
||||
return Promise.resolve(paginate(items));
|
||||
},
|
||||
},
|
||||
|
||||
// Channels
|
||||
channels: {
|
||||
list: () => Promise.resolve(paginate(demoChannels)),
|
||||
},
|
||||
|
||||
// Creatives
|
||||
creatives: {
|
||||
list: (params?: { project_id?: string; include_archived?: boolean }) => {
|
||||
let items = demoCreatives;
|
||||
if (params?.project_id) {
|
||||
items = items.filter((c) => c.project_id === params.project_id);
|
||||
}
|
||||
if (!params?.include_archived) {
|
||||
items = items.filter((c) => c.status === "active");
|
||||
}
|
||||
return Promise.resolve(paginate(items));
|
||||
},
|
||||
get: (id: string) => {
|
||||
const creative = demoCreatives.find((c) => c.id === id);
|
||||
if (!creative) throw new Error("Creative not found");
|
||||
return Promise.resolve(creative);
|
||||
},
|
||||
},
|
||||
|
||||
// Placements
|
||||
placements: {
|
||||
list: (params?: {
|
||||
project_id?: string;
|
||||
placement_channel_id?: string;
|
||||
creative_id?: string;
|
||||
include_archived?: boolean;
|
||||
}) => {
|
||||
let items = demoPlacements;
|
||||
if (params?.project_id) {
|
||||
items = items.filter((p) => p.project_id === params.project_id);
|
||||
}
|
||||
if (params?.placement_channel_id) {
|
||||
items = items.filter(
|
||||
(p) => p.placement_channel_id === params.placement_channel_id
|
||||
);
|
||||
}
|
||||
if (params?.creative_id) {
|
||||
items = items.filter((p) => p.creative_id === params.creative_id);
|
||||
}
|
||||
if (!params?.include_archived) {
|
||||
items = items.filter((p) => p.status === "active");
|
||||
}
|
||||
return Promise.resolve(paginate(items));
|
||||
},
|
||||
get: (id: string) => {
|
||||
const placement = demoPlacements.find((p) => p.id === id);
|
||||
if (!placement) throw new Error("Placement not found");
|
||||
return Promise.resolve(placement);
|
||||
},
|
||||
},
|
||||
|
||||
// Purchase Plan
|
||||
purchasePlan: {
|
||||
list: (projectId: string) => {
|
||||
const items = demoPurchasePlanChannels[projectId] || [];
|
||||
return Promise.resolve(paginate(items));
|
||||
},
|
||||
},
|
||||
|
||||
// Analytics
|
||||
analytics: {
|
||||
spending: (params?: { project_id?: string; grouping?: string }) => {
|
||||
// Filter by project if specified
|
||||
if (params?.project_id) {
|
||||
const projectPlacements = demoPlacements.filter(
|
||||
(p) => p.project_id === params.project_id && p.status === "active"
|
||||
);
|
||||
const totalCost = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.cost || 0),
|
||||
0
|
||||
);
|
||||
const totalSubs = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.subscriptions_count || 0),
|
||||
0
|
||||
);
|
||||
const totalViews = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.views_count || 0),
|
||||
0
|
||||
);
|
||||
|
||||
return Promise.resolve({
|
||||
total_cost: totalCost,
|
||||
total_subscriptions: totalSubs,
|
||||
total_views: totalViews,
|
||||
avg_cpf: totalSubs > 0 ? totalCost / totalSubs : null,
|
||||
avg_cpm: totalViews > 0 ? (totalCost / totalViews) * 1000 : null,
|
||||
chart_data: demoSpendingAnalytics.chart_data.slice(0, 3),
|
||||
} as SpendingAnalytics);
|
||||
}
|
||||
return Promise.resolve(demoSpendingAnalytics);
|
||||
},
|
||||
creatives: (params?: { project_id?: string }) => {
|
||||
let items = demoCreativeAnalytics;
|
||||
if (params?.project_id) {
|
||||
const projectCreativeIds = demoCreatives
|
||||
.filter((c) => c.project_id === params.project_id)
|
||||
.map((c) => c.id);
|
||||
items = items.filter((a) => projectCreativeIds.includes(a.id));
|
||||
}
|
||||
return Promise.resolve(paginate(items));
|
||||
},
|
||||
channels: (params?: { project_id?: string }) => {
|
||||
let items = demoChannelAnalytics;
|
||||
if (params?.project_id) {
|
||||
const projectPlacementChannelIds = new Set(
|
||||
demoPlacements
|
||||
.filter((p) => p.project_id === params.project_id)
|
||||
.map((p) => p.placement_channel_id)
|
||||
);
|
||||
items = items.filter((a) => projectPlacementChannelIds.has(a.channel_id));
|
||||
}
|
||||
return Promise.resolve(paginate(items));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
25
lib/demo/constants.ts
Normal file
25
lib/demo/constants.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// ============================================================================
|
||||
// Demo Mode Constants
|
||||
// ============================================================================
|
||||
|
||||
// Demo workspace ID - красивый и запоминающийся
|
||||
export const DEMO_WORKSPACE_ID = "demo";
|
||||
|
||||
// Check if workspace is demo
|
||||
export const isDemoWorkspace = (workspaceId: string | undefined): boolean => {
|
||||
return workspaceId === DEMO_WORKSPACE_ID;
|
||||
};
|
||||
|
||||
// Demo user
|
||||
export const DEMO_USER = {
|
||||
id: "demo-user-0000-0000-000000000000",
|
||||
telegram_id: 123456789,
|
||||
username: "demo_user",
|
||||
};
|
||||
|
||||
// Demo workspace
|
||||
export const DEMO_WORKSPACE = {
|
||||
id: DEMO_WORKSPACE_ID,
|
||||
name: "Демо Workspace",
|
||||
};
|
||||
|
||||
309
lib/demo/demo-aware-api.ts
Normal file
309
lib/demo/demo-aware-api.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
// ============================================================================
|
||||
// Demo-Aware API Wrapper
|
||||
// ============================================================================
|
||||
// This wrapper checks if the workspace is demo and returns mock data instead
|
||||
|
||||
import { isDemoWorkspace, DEMO_WORKSPACE_ID } from "./constants";
|
||||
import {
|
||||
demoChannels,
|
||||
demoCreatives,
|
||||
demoPlacements,
|
||||
demoPurchasePlanChannels,
|
||||
demoSpendingAnalytics,
|
||||
demoCreativeAnalytics,
|
||||
demoChannelAnalytics,
|
||||
demoProjects,
|
||||
demoWorkspace,
|
||||
demoMember,
|
||||
} from "./mock-data";
|
||||
import type {
|
||||
PaginatedResponse,
|
||||
Channel,
|
||||
Creative,
|
||||
Placement,
|
||||
PurchasePlanChannel,
|
||||
SpendingAnalytics,
|
||||
CreativeAnalyticsItem,
|
||||
ChannelAnalyticsItem,
|
||||
Project,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
DateGrouping,
|
||||
} from "@/lib/types/api";
|
||||
import {
|
||||
workspacesApi,
|
||||
projectsApi,
|
||||
channelsApi,
|
||||
creativesApi,
|
||||
placementsApi,
|
||||
purchasePlanApi,
|
||||
analyticsApi,
|
||||
membersApi,
|
||||
} from "@/lib/api";
|
||||
|
||||
// ============================================================================
|
||||
// Helper
|
||||
// ============================================================================
|
||||
|
||||
const paginate = <T>(items: T[], page = 1, size = 50): PaginatedResponse<T> => ({
|
||||
items: items.slice((page - 1) * size, page * size),
|
||||
total: items.length,
|
||||
page,
|
||||
size,
|
||||
pages: Math.ceil(items.length / size),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Workspaces API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwareWorkspacesApi = {
|
||||
list: async (params?: { page?: number; size?: number }) => {
|
||||
// Always include demo workspace in list for demo mode
|
||||
return workspacesApi.list(params);
|
||||
},
|
||||
get: async (workspaceId: string): Promise<Workspace> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
return demoWorkspace;
|
||||
}
|
||||
// Workspaces API doesn't have get method, fetch from list
|
||||
const response = await workspacesApi.list({ size: 100 });
|
||||
const workspace = response.items.find(w => w.id === workspaceId);
|
||||
if (!workspace) throw new Error("Workspace not found");
|
||||
return workspace;
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Projects API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwareProjectsApi = {
|
||||
list: async (
|
||||
workspaceId: string,
|
||||
params?: { page?: number; size?: number }
|
||||
): Promise<PaginatedResponse<Project>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
return paginate(demoProjects, params?.page, params?.size);
|
||||
}
|
||||
return projectsApi.list(workspaceId, params);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Members API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwareMembersApi = {
|
||||
list: async (
|
||||
workspaceId: string,
|
||||
params?: { page?: number; size?: number }
|
||||
): Promise<PaginatedResponse<WorkspaceMember>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
return paginate([demoMember], params?.page, params?.size);
|
||||
}
|
||||
return membersApi.list(workspaceId, params);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Channels API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwareChannelsApi = {
|
||||
list: async (params?: {
|
||||
page?: number;
|
||||
size?: number;
|
||||
username?: string;
|
||||
}): Promise<PaginatedResponse<Channel>> => {
|
||||
// Channels are global, check if we're in demo context via some mechanism
|
||||
// For now, always return real data - demo channels are handled per-workspace
|
||||
return channelsApi.list(params);
|
||||
},
|
||||
listForDemo: (): PaginatedResponse<Channel> => {
|
||||
return paginate(demoChannels);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Creatives API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwareCreativesApi = {
|
||||
list: async (
|
||||
workspaceId: string,
|
||||
params?: {
|
||||
project_id?: string;
|
||||
include_archived?: boolean;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
): Promise<PaginatedResponse<Creative>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
let items = demoCreatives;
|
||||
if (params?.project_id) {
|
||||
items = items.filter((c) => c.project_id === params.project_id);
|
||||
}
|
||||
if (!params?.include_archived) {
|
||||
items = items.filter((c) => c.status === "active");
|
||||
}
|
||||
return paginate(items, params?.page, params?.size);
|
||||
}
|
||||
return creativesApi.list(workspaceId, params);
|
||||
},
|
||||
get: async (workspaceId: string, creativeId: string): Promise<Creative> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
const creative = demoCreatives.find((c) => c.id === creativeId);
|
||||
if (!creative) throw new Error("Creative not found");
|
||||
return creative;
|
||||
}
|
||||
return creativesApi.get(workspaceId, creativeId);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Placements API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwarePlacementsApi = {
|
||||
list: async (
|
||||
workspaceId: string,
|
||||
params?: {
|
||||
project_id?: string;
|
||||
placement_channel_id?: string;
|
||||
creative_id?: string;
|
||||
include_archived?: boolean;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
): Promise<PaginatedResponse<Placement>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
let items = demoPlacements;
|
||||
if (params?.project_id) {
|
||||
items = items.filter((p) => p.project_id === params.project_id);
|
||||
}
|
||||
if (params?.placement_channel_id) {
|
||||
items = items.filter(
|
||||
(p) => p.placement_channel_id === params.placement_channel_id
|
||||
);
|
||||
}
|
||||
if (params?.creative_id) {
|
||||
items = items.filter((p) => p.creative_id === params.creative_id);
|
||||
}
|
||||
if (!params?.include_archived) {
|
||||
items = items.filter((p) => p.status === "active");
|
||||
}
|
||||
return paginate(items, params?.page, params?.size);
|
||||
}
|
||||
return placementsApi.list(workspaceId, params);
|
||||
},
|
||||
get: async (workspaceId: string, placementId: string): Promise<Placement> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
const placement = demoPlacements.find((p) => p.id === placementId);
|
||||
if (!placement) throw new Error("Placement not found");
|
||||
return placement;
|
||||
}
|
||||
return placementsApi.get(workspaceId, placementId);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Purchase Plan API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwarePurchasePlanApi = {
|
||||
list: async (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
params?: { page?: number; size?: number }
|
||||
): Promise<PaginatedResponse<PurchasePlanChannel>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
const items = demoPurchasePlanChannels[projectId] || [];
|
||||
return paginate(items, params?.page, params?.size);
|
||||
}
|
||||
return purchasePlanApi.list(workspaceId, projectId, params);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Demo-Aware Analytics API
|
||||
// ============================================================================
|
||||
|
||||
export const demoAwareAnalyticsApi = {
|
||||
spending: async (
|
||||
workspaceId: string,
|
||||
params?: {
|
||||
project_id?: string;
|
||||
date_from?: string;
|
||||
date_to?: string;
|
||||
grouping?: DateGrouping;
|
||||
}
|
||||
): Promise<SpendingAnalytics> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
if (params?.project_id) {
|
||||
const projectPlacements = demoPlacements.filter(
|
||||
(p) => p.project_id === params.project_id && p.status === "active"
|
||||
);
|
||||
const totalCost = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.cost || 0),
|
||||
0
|
||||
);
|
||||
const totalSubs = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.subscriptions_count || 0),
|
||||
0
|
||||
);
|
||||
const totalViews = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.views_count || 0),
|
||||
0
|
||||
);
|
||||
return {
|
||||
total_cost: totalCost,
|
||||
total_subscriptions: totalSubs,
|
||||
total_views: totalViews,
|
||||
avg_cpf: totalSubs > 0 ? totalCost / totalSubs : null,
|
||||
avg_cpm: totalViews > 0 ? (totalCost / totalViews) * 1000 : null,
|
||||
chart_data: demoSpendingAnalytics.chart_data.slice(0, 4),
|
||||
};
|
||||
}
|
||||
return demoSpendingAnalytics;
|
||||
}
|
||||
return analyticsApi.spending(workspaceId, params);
|
||||
},
|
||||
creatives: async (
|
||||
workspaceId: string,
|
||||
params?: { project_id?: string; page?: number; size?: number }
|
||||
): Promise<PaginatedResponse<CreativeAnalyticsItem>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
let items = demoCreativeAnalytics;
|
||||
if (params?.project_id) {
|
||||
const projectCreativeIds = demoCreatives
|
||||
.filter((c) => c.project_id === params.project_id)
|
||||
.map((c) => c.id);
|
||||
items = items.filter((a) => projectCreativeIds.includes(a.id));
|
||||
}
|
||||
return paginate(items, params?.page, params?.size);
|
||||
}
|
||||
return analyticsApi.creatives(workspaceId, params);
|
||||
},
|
||||
channels: async (
|
||||
workspaceId: string,
|
||||
params?: { project_id?: string; page?: number; size?: number }
|
||||
): Promise<PaginatedResponse<ChannelAnalyticsItem>> => {
|
||||
if (isDemoWorkspace(workspaceId)) {
|
||||
let items = demoChannelAnalytics;
|
||||
if (params?.project_id) {
|
||||
const projectPlacementChannelIds = new Set(
|
||||
demoPlacements
|
||||
.filter((p) => p.project_id === params.project_id)
|
||||
.map((p) => p.placement_channel_id)
|
||||
);
|
||||
items = items.filter((a) =>
|
||||
projectPlacementChannelIds.has(a.channel_id)
|
||||
);
|
||||
}
|
||||
return paginate(items, params?.page, params?.size);
|
||||
}
|
||||
return analyticsApi.channels(workspaceId, params);
|
||||
},
|
||||
};
|
||||
|
||||
272
lib/demo/hooks.ts
Normal file
272
lib/demo/hooks.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
// ============================================================================
|
||||
// Demo Mode Hooks
|
||||
// ============================================================================
|
||||
|
||||
import { useWorkspace } from "@/components/providers/workspace-provider";
|
||||
import { demoApi } from "./api";
|
||||
import {
|
||||
demoChannels,
|
||||
demoCreatives,
|
||||
demoPlacements,
|
||||
demoPurchasePlanChannels,
|
||||
demoSpendingAnalytics,
|
||||
demoCreativeAnalytics,
|
||||
demoChannelAnalytics,
|
||||
} from "./mock-data";
|
||||
import type {
|
||||
Channel,
|
||||
Creative,
|
||||
Placement,
|
||||
PurchasePlanChannel,
|
||||
SpendingAnalytics,
|
||||
CreativeAnalyticsItem,
|
||||
ChannelAnalyticsItem,
|
||||
PaginatedResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
interface UseDemoDataResult<T> {
|
||||
data: T | null;
|
||||
isDemo: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Check if should use demo mode
|
||||
// ============================================================================
|
||||
|
||||
export const useIsDemoMode = (): boolean => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
return isDemoMode;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo channels
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoChannels = (): UseDemoDataResult<PaginatedResponse<Channel>> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
return {
|
||||
data: {
|
||||
items: demoChannels,
|
||||
total: demoChannels.length,
|
||||
page: 1,
|
||||
size: 50,
|
||||
pages: 1,
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo creatives
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoCreatives = (
|
||||
projectId?: string
|
||||
): UseDemoDataResult<PaginatedResponse<Creative>> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
let items = demoCreatives;
|
||||
if (projectId) {
|
||||
items = items.filter((c) => c.project_id === projectId);
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
items,
|
||||
total: items.length,
|
||||
page: 1,
|
||||
size: 50,
|
||||
pages: 1,
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo placements
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoPlacements = (
|
||||
projectId?: string
|
||||
): UseDemoDataResult<PaginatedResponse<Placement>> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
let items = demoPlacements;
|
||||
if (projectId) {
|
||||
items = items.filter((p) => p.project_id === projectId);
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
items,
|
||||
total: items.length,
|
||||
page: 1,
|
||||
size: 50,
|
||||
pages: 1,
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo purchase plan
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoPurchasePlan = (
|
||||
projectId: string
|
||||
): UseDemoDataResult<PaginatedResponse<PurchasePlanChannel>> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
const items = demoPurchasePlanChannels[projectId] || [];
|
||||
|
||||
return {
|
||||
data: {
|
||||
items,
|
||||
total: items.length,
|
||||
page: 1,
|
||||
size: 50,
|
||||
pages: 1,
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo spending analytics
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoSpendingAnalytics = (
|
||||
projectId?: string
|
||||
): UseDemoDataResult<SpendingAnalytics> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
if (projectId) {
|
||||
// Filter by project
|
||||
const projectPlacements = demoPlacements.filter(
|
||||
(p) => p.project_id === projectId && p.status === "active"
|
||||
);
|
||||
const totalCost = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.cost || 0),
|
||||
0
|
||||
);
|
||||
const totalSubs = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.subscriptions_count || 0),
|
||||
0
|
||||
);
|
||||
const totalViews = projectPlacements.reduce(
|
||||
(sum, p) => sum + (p.views_count || 0),
|
||||
0
|
||||
);
|
||||
|
||||
return {
|
||||
data: {
|
||||
total_cost: totalCost,
|
||||
total_subscriptions: totalSubs,
|
||||
total_views: totalViews,
|
||||
avg_cpf: totalSubs > 0 ? totalCost / totalSubs : null,
|
||||
avg_cpm: totalViews > 0 ? (totalCost / totalViews) * 1000 : null,
|
||||
chart_data: demoSpendingAnalytics.chart_data.slice(0, 3),
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
data: demoSpendingAnalytics,
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo creative analytics
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoCreativeAnalytics = (
|
||||
projectId?: string
|
||||
): UseDemoDataResult<PaginatedResponse<CreativeAnalyticsItem>> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
let items = demoCreativeAnalytics;
|
||||
if (projectId) {
|
||||
const projectCreativeIds = demoCreatives
|
||||
.filter((c) => c.project_id === projectId)
|
||||
.map((c) => c.id);
|
||||
items = items.filter((a) => projectCreativeIds.includes(a.id));
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
items,
|
||||
total: items.length,
|
||||
page: 1,
|
||||
size: 50,
|
||||
pages: 1,
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Hook: Get demo channel analytics
|
||||
// ============================================================================
|
||||
|
||||
export const useDemoChannelAnalytics = (
|
||||
projectId?: string
|
||||
): UseDemoDataResult<PaginatedResponse<ChannelAnalyticsItem>> => {
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
if (isDemoMode) {
|
||||
let items = demoChannelAnalytics;
|
||||
if (projectId) {
|
||||
const projectPlacementChannelIds = new Set(
|
||||
demoPlacements
|
||||
.filter((p) => p.project_id === projectId)
|
||||
.map((p) => p.placement_channel_id)
|
||||
);
|
||||
items = items.filter((a) => projectPlacementChannelIds.has(a.channel_id));
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
items,
|
||||
total: items.length,
|
||||
page: 1,
|
||||
size: 50,
|
||||
pages: 1,
|
||||
},
|
||||
isDemo: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: null, isDemo: false };
|
||||
};
|
||||
|
||||
10
lib/demo/index.ts
Normal file
10
lib/demo/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// ============================================================================
|
||||
// Demo Module Exports
|
||||
// ============================================================================
|
||||
|
||||
export * from "./constants";
|
||||
export * from "./mock-data";
|
||||
export { demoApi } from "./api";
|
||||
export * from "./hooks";
|
||||
export * from "./demo-aware-api";
|
||||
|
||||
583
lib/demo/mock-data.ts
Normal file
583
lib/demo/mock-data.ts
Normal file
@@ -0,0 +1,583 @@
|
||||
// ============================================================================
|
||||
// Demo Mock Data
|
||||
// ============================================================================
|
||||
|
||||
import type {
|
||||
Workspace,
|
||||
Project,
|
||||
Channel,
|
||||
Creative,
|
||||
Placement,
|
||||
PurchasePlanChannel,
|
||||
WorkspaceMember,
|
||||
Permission,
|
||||
SpendingAnalytics,
|
||||
CreativeAnalyticsItem,
|
||||
ChannelAnalyticsItem,
|
||||
} from "@/lib/types/api";
|
||||
import { DEMO_WORKSPACE_ID, DEMO_USER } from "./constants";
|
||||
|
||||
// ============================================================================
|
||||
// Workspace & User
|
||||
// ============================================================================
|
||||
|
||||
export const demoWorkspace: Workspace = {
|
||||
id: DEMO_WORKSPACE_ID,
|
||||
name: "Демо Workspace",
|
||||
};
|
||||
|
||||
export const demoMember: WorkspaceMember = {
|
||||
id: "demo-member-0000-0000-000000000001",
|
||||
user: {
|
||||
id: DEMO_USER.id,
|
||||
telegram_id: DEMO_USER.telegram_id,
|
||||
username: DEMO_USER.username,
|
||||
},
|
||||
status: "active",
|
||||
permissions: [{ key: "admin_full" }],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Projects (Target Channels)
|
||||
// ============================================================================
|
||||
|
||||
export const demoProjects: Project[] = [
|
||||
{
|
||||
id: "proj-0001-0000-0000-000000000001",
|
||||
telegram_id: -1001234567001,
|
||||
title: "Крипто Новости",
|
||||
username: "crypto_news_demo",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: "proj-0002-0000-0000-000000000002",
|
||||
telegram_id: -1001234567002,
|
||||
title: "IT Вакансии",
|
||||
username: "it_jobs_demo",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: "proj-0003-0000-0000-000000000003",
|
||||
telegram_id: -1001234567003,
|
||||
title: "Стартапы и Бизнес",
|
||||
username: "startups_demo",
|
||||
status: "active",
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Channels (External Channels for placements)
|
||||
// ============================================================================
|
||||
|
||||
export const demoChannels: Channel[] = [
|
||||
{
|
||||
id: "chan-0001-0000-0000-000000000001",
|
||||
telegram_id: -1002345678001,
|
||||
title: "Технологии Будущего",
|
||||
username: "tech_future",
|
||||
description: "Новости о технологиях и инновациях",
|
||||
subscribers_count: 125000,
|
||||
},
|
||||
{
|
||||
id: "chan-0002-0000-0000-000000000002",
|
||||
telegram_id: -1002345678002,
|
||||
title: "Инвестиции Pro",
|
||||
username: "invest_pro",
|
||||
description: "Аналитика и инвестиционные идеи",
|
||||
subscribers_count: 89000,
|
||||
},
|
||||
{
|
||||
id: "chan-0003-0000-0000-000000000003",
|
||||
telegram_id: -1002345678003,
|
||||
title: "Программисты",
|
||||
username: "programmers_hub",
|
||||
description: "Сообщество разработчиков",
|
||||
subscribers_count: 156000,
|
||||
},
|
||||
{
|
||||
id: "chan-0004-0000-0000-000000000004",
|
||||
telegram_id: -1002345678004,
|
||||
title: "Маркетинг Pro",
|
||||
username: "marketing_pro",
|
||||
description: "Digital маркетинг и реклама",
|
||||
subscribers_count: 67000,
|
||||
},
|
||||
{
|
||||
id: "chan-0005-0000-0000-000000000005",
|
||||
telegram_id: -1002345678005,
|
||||
title: "Финансовая грамотность",
|
||||
username: "finance_edu",
|
||||
description: "Обучение финансам",
|
||||
subscribers_count: 234000,
|
||||
},
|
||||
{
|
||||
id: "chan-0006-0000-0000-000000000006",
|
||||
telegram_id: -1002345678006,
|
||||
title: "Startup Daily",
|
||||
username: "startup_daily",
|
||||
description: "Ежедневные новости стартапов",
|
||||
subscribers_count: 45000,
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Creatives
|
||||
// ============================================================================
|
||||
|
||||
export const demoCreatives: Creative[] = [
|
||||
{
|
||||
id: "crea-0001-0000-0000-000000000001",
|
||||
name: "Крипто - Основной",
|
||||
text: "🚀 Присоединяйтесь к лучшему крипто-каналу!\n\n💰 Ежедневные сигналы\n📊 Аналитика рынка\n🎯 Точные прогнозы\n\n{invite_link}",
|
||||
status: "active",
|
||||
placements_count: 12,
|
||||
project_id: "proj-0001-0000-0000-000000000001",
|
||||
project_channel_title: "Крипто Новости",
|
||||
created_at: "2024-11-15T10:00:00Z",
|
||||
},
|
||||
{
|
||||
id: "crea-0002-0000-0000-000000000002",
|
||||
name: "Крипто - Агрессивный",
|
||||
text: "⚡️ СРОЧНО! Секретный канал для избранных!\n\n🔥 Закрытая информация\n💎 Альткоины x100\n🚀 Успей до памп!\n\n{invite_link}",
|
||||
status: "active",
|
||||
placements_count: 5,
|
||||
project_id: "proj-0001-0000-0000-000000000001",
|
||||
project_channel_title: "Крипто Новости",
|
||||
created_at: "2024-11-20T14:30:00Z",
|
||||
},
|
||||
{
|
||||
id: "crea-0003-0000-0000-000000000003",
|
||||
name: "IT Jobs - Основной",
|
||||
text: "👨💻 Ищешь работу в IT?\n\n✅ Вакансии от топ-компаний\n✅ Удаленка и офис\n✅ Junior - Senior\n\nПодписывайся: {invite_link}",
|
||||
status: "active",
|
||||
placements_count: 8,
|
||||
project_id: "proj-0002-0000-0000-000000000002",
|
||||
project_channel_title: "IT Вакансии",
|
||||
created_at: "2024-10-01T09:00:00Z",
|
||||
},
|
||||
{
|
||||
id: "crea-0004-0000-0000-000000000004",
|
||||
name: "Startups - Нетворкинг",
|
||||
text: "🤝 Нетворкинг для стартаперов\n\n🎯 Найди кофаундера\n💡 Обменяйся идеями\n🚀 Развивай проект\n\n{invite_link}",
|
||||
status: "active",
|
||||
placements_count: 6,
|
||||
project_id: "proj-0003-0000-0000-000000000003",
|
||||
project_channel_title: "Стартапы и Бизнес",
|
||||
created_at: "2024-12-01T16:00:00Z",
|
||||
},
|
||||
{
|
||||
id: "crea-0005-0000-0000-000000000005",
|
||||
name: "IT Jobs - Тестовый",
|
||||
text: "🔥 Тестовый креатив для IT вакансий\n\n{invite_link}",
|
||||
status: "archived",
|
||||
placements_count: 2,
|
||||
project_id: "proj-0002-0000-0000-000000000002",
|
||||
project_channel_title: "IT Вакансии",
|
||||
created_at: "2024-09-15T11:00:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Placements
|
||||
// ============================================================================
|
||||
|
||||
export const demoPlacements: Placement[] = [
|
||||
{
|
||||
id: "plac-0001-0000-0000-000000000001",
|
||||
placement_date: "2024-12-20T12:00:00Z",
|
||||
cost: 5500,
|
||||
comment: "Отличное размещение",
|
||||
ad_post_url: "https://t.me/tech_future/1234",
|
||||
invite_link_type: "approval",
|
||||
invite_link: "https://t.me/+ABC123demo1",
|
||||
status: "active",
|
||||
subscriptions_count: 156,
|
||||
views_count: 8500,
|
||||
views_availability: "available",
|
||||
last_views_fetch_at: "2024-12-21T10:00:00Z",
|
||||
created_at: "2024-12-19T15:00:00Z",
|
||||
project_id: "proj-0001-0000-0000-000000000001",
|
||||
project_channel_title: "Крипто Новости",
|
||||
placement_channel_id: "chan-0001-0000-0000-000000000001",
|
||||
placement_channel_title: "Технологии Будущего",
|
||||
creative_id: "crea-0001-0000-0000-000000000001",
|
||||
creative_name: "Крипто - Основной",
|
||||
},
|
||||
{
|
||||
id: "plac-0002-0000-0000-000000000002",
|
||||
placement_date: "2024-12-18T14:00:00Z",
|
||||
cost: 3200,
|
||||
comment: null,
|
||||
ad_post_url: "https://t.me/invest_pro/5678",
|
||||
invite_link_type: "approval",
|
||||
invite_link: "https://t.me/+ABC123demo2",
|
||||
status: "active",
|
||||
subscriptions_count: 89,
|
||||
views_count: 4200,
|
||||
views_availability: "available",
|
||||
last_views_fetch_at: "2024-12-20T08:00:00Z",
|
||||
created_at: "2024-12-17T10:00:00Z",
|
||||
project_id: "proj-0001-0000-0000-000000000001",
|
||||
project_channel_title: "Крипто Новости",
|
||||
placement_channel_id: "chan-0002-0000-0000-000000000002",
|
||||
placement_channel_title: "Инвестиции Pro",
|
||||
creative_id: "crea-0001-0000-0000-000000000001",
|
||||
creative_name: "Крипто - Основной",
|
||||
},
|
||||
{
|
||||
id: "plac-0003-0000-0000-000000000003",
|
||||
placement_date: "2024-12-15T10:00:00Z",
|
||||
cost: 4800,
|
||||
comment: "Программисты - хорошая аудитория",
|
||||
ad_post_url: "https://t.me/programmers_hub/9012",
|
||||
invite_link_type: "public",
|
||||
invite_link: "https://t.me/+ABC123demo3",
|
||||
status: "active",
|
||||
subscriptions_count: 234,
|
||||
views_count: 12000,
|
||||
views_availability: "available",
|
||||
last_views_fetch_at: "2024-12-19T14:00:00Z",
|
||||
created_at: "2024-12-14T09:00:00Z",
|
||||
project_id: "proj-0002-0000-0000-000000000002",
|
||||
project_channel_title: "IT Вакансии",
|
||||
placement_channel_id: "chan-0003-0000-0000-000000000003",
|
||||
placement_channel_title: "Программисты",
|
||||
creative_id: "crea-0003-0000-0000-000000000003",
|
||||
creative_name: "IT Jobs - Основной",
|
||||
},
|
||||
{
|
||||
id: "plac-0004-0000-0000-000000000004",
|
||||
placement_date: "2024-12-10T16:00:00Z",
|
||||
cost: 2500,
|
||||
comment: null,
|
||||
ad_post_url: "https://t.me/marketing_pro/3456",
|
||||
invite_link_type: "approval",
|
||||
invite_link: "https://t.me/+ABC123demo4",
|
||||
status: "active",
|
||||
subscriptions_count: 67,
|
||||
views_count: 3100,
|
||||
views_availability: "manual",
|
||||
last_views_fetch_at: "2024-12-12T11:00:00Z",
|
||||
created_at: "2024-12-09T14:00:00Z",
|
||||
project_id: "proj-0003-0000-0000-000000000003",
|
||||
project_channel_title: "Стартапы и Бизнес",
|
||||
placement_channel_id: "chan-0004-0000-0000-000000000004",
|
||||
placement_channel_title: "Маркетинг Pro",
|
||||
creative_id: "crea-0004-0000-0000-000000000004",
|
||||
creative_name: "Startups - Нетворкинг",
|
||||
},
|
||||
{
|
||||
id: "plac-0005-0000-0000-000000000005",
|
||||
placement_date: "2024-12-05T11:00:00Z",
|
||||
cost: 6200,
|
||||
comment: "Финансовая аудитория",
|
||||
ad_post_url: "https://t.me/finance_edu/7890",
|
||||
invite_link_type: "approval",
|
||||
invite_link: "https://t.me/+ABC123demo5",
|
||||
status: "active",
|
||||
subscriptions_count: 312,
|
||||
views_count: 15600,
|
||||
views_availability: "available",
|
||||
last_views_fetch_at: "2024-12-08T09:00:00Z",
|
||||
created_at: "2024-12-04T10:00:00Z",
|
||||
project_id: "proj-0001-0000-0000-000000000001",
|
||||
project_channel_title: "Крипто Новости",
|
||||
placement_channel_id: "chan-0005-0000-0000-000000000005",
|
||||
placement_channel_title: "Финансовая грамотность",
|
||||
creative_id: "crea-0002-0000-0000-000000000002",
|
||||
creative_name: "Крипто - Агрессивный",
|
||||
},
|
||||
{
|
||||
id: "plac-0006-0000-0000-000000000006",
|
||||
placement_date: "2024-11-28T13:00:00Z",
|
||||
cost: 1800,
|
||||
comment: null,
|
||||
ad_post_url: "https://t.me/startup_daily/2345",
|
||||
invite_link_type: "public",
|
||||
invite_link: "https://t.me/+ABC123demo6",
|
||||
status: "archived",
|
||||
subscriptions_count: 45,
|
||||
views_count: 2100,
|
||||
views_availability: "unavailable",
|
||||
last_views_fetch_at: null,
|
||||
created_at: "2024-11-27T15:00:00Z",
|
||||
project_id: "proj-0003-0000-0000-000000000003",
|
||||
project_channel_title: "Стартапы и Бизнес",
|
||||
placement_channel_id: "chan-0006-0000-0000-000000000006",
|
||||
placement_channel_title: "Startup Daily",
|
||||
creative_id: "crea-0004-0000-0000-000000000004",
|
||||
creative_name: "Startups - Нетворкинг",
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Purchase Plan Channels
|
||||
// ============================================================================
|
||||
|
||||
export const demoPurchasePlanChannels: Record<string, PurchasePlanChannel[]> = {
|
||||
"proj-0001-0000-0000-000000000001": [
|
||||
{
|
||||
id: "plan-0001-0000-0000-000000000001",
|
||||
status: "completed",
|
||||
planned_cost: 5500,
|
||||
comment: "Основной канал",
|
||||
channel: {
|
||||
id: "chan-0001-0000-0000-000000000001",
|
||||
telegram_id: -1002345678001,
|
||||
title: "Технологии Будущего",
|
||||
username: "tech_future",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "plan-0002-0000-0000-000000000002",
|
||||
status: "completed",
|
||||
planned_cost: 3500,
|
||||
comment: null,
|
||||
channel: {
|
||||
id: "chan-0002-0000-0000-000000000002",
|
||||
telegram_id: -1002345678002,
|
||||
title: "Инвестиции Pro",
|
||||
username: "invest_pro",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "plan-0003-0000-0000-000000000003",
|
||||
status: "planned",
|
||||
planned_cost: 4000,
|
||||
comment: "На следующую неделю",
|
||||
channel: {
|
||||
id: "chan-0005-0000-0000-000000000005",
|
||||
telegram_id: -1002345678005,
|
||||
title: "Финансовая грамотность",
|
||||
username: "finance_edu",
|
||||
},
|
||||
},
|
||||
],
|
||||
"proj-0002-0000-0000-000000000002": [
|
||||
{
|
||||
id: "plan-0004-0000-0000-000000000004",
|
||||
status: "completed",
|
||||
planned_cost: 4800,
|
||||
comment: "IT аудитория",
|
||||
channel: {
|
||||
id: "chan-0003-0000-0000-000000000003",
|
||||
telegram_id: -1002345678003,
|
||||
title: "Программисты",
|
||||
username: "programmers_hub",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "plan-0005-0000-0000-000000000005",
|
||||
status: "planned",
|
||||
planned_cost: 3000,
|
||||
comment: null,
|
||||
channel: {
|
||||
id: "chan-0001-0000-0000-000000000001",
|
||||
telegram_id: -1002345678001,
|
||||
title: "Технологии Будущего",
|
||||
username: "tech_future",
|
||||
},
|
||||
},
|
||||
],
|
||||
"proj-0003-0000-0000-000000000003": [
|
||||
{
|
||||
id: "plan-0006-0000-0000-000000000006",
|
||||
status: "completed",
|
||||
planned_cost: 2500,
|
||||
comment: null,
|
||||
channel: {
|
||||
id: "chan-0004-0000-0000-000000000004",
|
||||
telegram_id: -1002345678004,
|
||||
title: "Маркетинг Pro",
|
||||
username: "marketing_pro",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "plan-0007-0000-0000-000000000007",
|
||||
status: "completed",
|
||||
planned_cost: 1800,
|
||||
comment: "Стартап аудитория",
|
||||
channel: {
|
||||
id: "chan-0006-0000-0000-000000000006",
|
||||
telegram_id: -1002345678006,
|
||||
title: "Startup Daily",
|
||||
username: "startup_daily",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Analytics Data
|
||||
// ============================================================================
|
||||
|
||||
export const demoSpendingAnalytics: SpendingAnalytics = {
|
||||
total_cost: 24000,
|
||||
total_subscriptions: 903,
|
||||
total_views: 45500,
|
||||
avg_cpf: 26.58,
|
||||
avg_cpm: 527.47,
|
||||
chart_data: [
|
||||
{
|
||||
period: "2024-11",
|
||||
cost: 1800,
|
||||
subscriptions: 45,
|
||||
views: 2100,
|
||||
cpf: 40.0,
|
||||
cpm: 857.14,
|
||||
},
|
||||
{
|
||||
period: "2024-12-05",
|
||||
cost: 6200,
|
||||
subscriptions: 312,
|
||||
views: 15600,
|
||||
cpf: 19.87,
|
||||
cpm: 397.44,
|
||||
},
|
||||
{
|
||||
period: "2024-12-10",
|
||||
cost: 2500,
|
||||
subscriptions: 67,
|
||||
views: 3100,
|
||||
cpf: 37.31,
|
||||
cpm: 806.45,
|
||||
},
|
||||
{
|
||||
period: "2024-12-15",
|
||||
cost: 4800,
|
||||
subscriptions: 234,
|
||||
views: 12000,
|
||||
cpf: 20.51,
|
||||
cpm: 400.0,
|
||||
},
|
||||
{
|
||||
period: "2024-12-18",
|
||||
cost: 3200,
|
||||
subscriptions: 89,
|
||||
views: 4200,
|
||||
cpf: 35.96,
|
||||
cpm: 761.9,
|
||||
},
|
||||
{
|
||||
period: "2024-12-20",
|
||||
cost: 5500,
|
||||
subscriptions: 156,
|
||||
views: 8500,
|
||||
cpf: 35.26,
|
||||
cpm: 647.06,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const demoCreativeAnalytics: CreativeAnalyticsItem[] = [
|
||||
{
|
||||
id: "crea-0001-0000-0000-000000000001",
|
||||
name: "Крипто - Основной",
|
||||
placements_count: 12,
|
||||
total_cost: 8700,
|
||||
total_subscriptions: 245,
|
||||
total_views: 12700,
|
||||
avg_cpf: 35.51,
|
||||
avg_cpm: 685.04,
|
||||
},
|
||||
{
|
||||
id: "crea-0002-0000-0000-000000000002",
|
||||
name: "Крипто - Агрессивный",
|
||||
placements_count: 5,
|
||||
total_cost: 6200,
|
||||
total_subscriptions: 312,
|
||||
total_views: 15600,
|
||||
avg_cpf: 19.87,
|
||||
avg_cpm: 397.44,
|
||||
},
|
||||
{
|
||||
id: "crea-0003-0000-0000-000000000003",
|
||||
name: "IT Jobs - Основной",
|
||||
placements_count: 8,
|
||||
total_cost: 4800,
|
||||
total_subscriptions: 234,
|
||||
total_views: 12000,
|
||||
avg_cpf: 20.51,
|
||||
avg_cpm: 400.0,
|
||||
},
|
||||
{
|
||||
id: "crea-0004-0000-0000-000000000004",
|
||||
name: "Startups - Нетворкинг",
|
||||
placements_count: 6,
|
||||
total_cost: 4300,
|
||||
total_subscriptions: 112,
|
||||
total_views: 5200,
|
||||
avg_cpf: 38.39,
|
||||
avg_cpm: 826.92,
|
||||
},
|
||||
];
|
||||
|
||||
export const demoChannelAnalytics: ChannelAnalyticsItem[] = [
|
||||
{
|
||||
channel_id: "chan-0001-0000-0000-000000000001",
|
||||
channel_title: "Технологии Будущего",
|
||||
channel_username: "tech_future",
|
||||
placements_count: 3,
|
||||
total_cost: 5500,
|
||||
total_subscriptions: 156,
|
||||
total_views: 8500,
|
||||
avg_cps: 35.26,
|
||||
avg_cpm: 647.06,
|
||||
},
|
||||
{
|
||||
channel_id: "chan-0002-0000-0000-000000000002",
|
||||
channel_title: "Инвестиции Pro",
|
||||
channel_username: "invest_pro",
|
||||
placements_count: 2,
|
||||
total_cost: 3200,
|
||||
total_subscriptions: 89,
|
||||
total_views: 4200,
|
||||
avg_cps: 35.96,
|
||||
avg_cpm: 761.9,
|
||||
},
|
||||
{
|
||||
channel_id: "chan-0003-0000-0000-000000000003",
|
||||
channel_title: "Программисты",
|
||||
channel_username: "programmers_hub",
|
||||
placements_count: 4,
|
||||
total_cost: 4800,
|
||||
total_subscriptions: 234,
|
||||
total_views: 12000,
|
||||
avg_cps: 20.51,
|
||||
avg_cpm: 400.0,
|
||||
},
|
||||
{
|
||||
channel_id: "chan-0005-0000-0000-000000000005",
|
||||
channel_title: "Финансовая грамотность",
|
||||
channel_username: "finance_edu",
|
||||
placements_count: 2,
|
||||
total_cost: 6200,
|
||||
total_subscriptions: 312,
|
||||
total_views: 15600,
|
||||
avg_cps: 19.87,
|
||||
avg_cpm: 397.44,
|
||||
},
|
||||
{
|
||||
channel_id: "chan-0004-0000-0000-000000000004",
|
||||
channel_title: "Маркетинг Pro",
|
||||
channel_username: "marketing_pro",
|
||||
placements_count: 1,
|
||||
total_cost: 2500,
|
||||
total_subscriptions: 67,
|
||||
total_views: 3100,
|
||||
avg_cps: 37.31,
|
||||
avg_cpm: 806.45,
|
||||
},
|
||||
{
|
||||
channel_id: "chan-0006-0000-0000-000000000006",
|
||||
channel_title: "Startup Daily",
|
||||
channel_username: "startup_daily",
|
||||
placements_count: 1,
|
||||
total_cost: 1800,
|
||||
total_subscriptions: 45,
|
||||
total_views: 2100,
|
||||
avg_cps: 40.0,
|
||||
avg_cpm: 857.14,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user