feat: global ui & functional update

This commit is contained in:
ivannoskov
2026-01-21 22:43:54 +03:00
parent 8958d89d12
commit 619fd5c1ef
32 changed files with 3803 additions and 1325 deletions

View File

@@ -8,6 +8,7 @@ import { ru } from "date-fns/locale";
import { isDemoWorkspace, DEMO_WORKSPACE_ID } from "./constants";
import {
demoChannels,
demoPlacementChannels,
demoCreatives,
demoPlacements,
demoPurchasePlanChannels,
@@ -124,6 +125,17 @@ export const demoAwareProjectsApi = {
}
return projectsApi.archive(workspaceId, projectId);
},
unarchive: async (
workspaceId: string,
projectId: string
): Promise<Project> => {
if (isDemoWorkspace(workspaceId)) {
const project = demoProjects.find(p => p.id === projectId);
if (!project) throw new Error("Project not found");
return { ...project, status: "active" };
}
return projectsApi.unarchive(workspaceId, projectId);
},
delete: async (
workspaceId: string,
projectId: string
@@ -138,6 +150,19 @@ export const demoAwareProjectsApi = {
}
return projectsApi.delete(workspaceId, projectId);
},
move: async (
workspaceId: string,
projectId: string,
data: { target_workspace_id: string }
): Promise<Project> => {
if (isDemoWorkspace(workspaceId)) {
const project = demoProjects.find(p => p.id === projectId);
if (!project) throw new Error("Project not found");
// In demo mode, just return the project (mock move)
return project;
}
return projectsApi.move(workspaceId, projectId, data);
},
};
// ============================================================================
@@ -234,14 +259,14 @@ export const demoAwarePlacementsApi = {
}
if (params?.placement_channel_id) {
items = items.filter(
(p) => p.placement_channel_id === params.placement_channel_id
(p) => p.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");
items = items.filter((p) => p.status !== "Отмена" && p.status !== "Неактуально");
}
return paginate(items, params?.page, params?.size);
}
@@ -255,6 +280,18 @@ export const demoAwarePlacementsApi = {
}
return placementsApi.get(workspaceId, placementId);
},
getByProject: async (workspaceId: string, projectId: string): Promise<Placement[]> => {
if (isDemoWorkspace(workspaceId)) {
// In demo mode, return all placements for demo project
// Filter by demo project id for realistic behavior
return demoPlacements.filter((p) => {
// For demo purposes, we'll just return all placements
// In real usage, this would filter by project_id
return true;
});
}
return placementsApi.getByProject(workspaceId, projectId);
},
};
// ============================================================================
@@ -292,18 +329,18 @@ export const demoAwareAnalyticsApi = {
if (isDemoWorkspace(workspaceId)) {
if (params?.project_id) {
const projectPlacements = demoPlacements.filter(
(p) => p.project_id === params.project_id && p.status === "active"
(p) => p.project_id === params.project_id && p.status !== "Отмена" && p.status !== "Неактуально"
);
const totalCost = projectPlacements.reduce(
(sum, p) => sum + (p.cost || 0),
(sum, p) => sum + (p.details?.cost?.value || 0),
0
);
const totalSubs = projectPlacements.reduce(
(sum, p) => sum + (p.subscriptions_count || 0),
(sum, p) => sum + (p.placement_post?.subscriptions_count || 0),
0
);
const totalViews = projectPlacements.reduce(
(sum, p) => sum + (p.views_count || 0),
(sum, p) => sum + (p.placement_post?.views_count || 0),
0
);
return {
@@ -313,6 +350,7 @@ export const demoAwareAnalyticsApi = {
avg_cpf: totalSubs > 0 ? totalCost / totalSubs : null,
avg_cpm: totalViews > 0 ? (totalCost / totalViews) * 1000 : null,
chart_data: demoSpendingAnalytics.chart_data.slice(0, 4),
placements_count: projectPlacements.length,
};
}
return demoSpendingAnalytics;
@@ -345,7 +383,7 @@ export const demoAwareAnalyticsApi = {
const projectPlacementChannelIds = new Set(
demoPlacements
.filter((p) => p.project_id === params.project_id)
.map((p) => p.placement_channel_id)
.map((p) => p.channel.id)
);
items = items.filter((a) =>
projectPlacementChannelIds.has(a.channel_id)
@@ -357,13 +395,35 @@ export const demoAwareAnalyticsApi = {
},
placements: async (
workspaceId: string,
params?: { project_id?: string; page?: number; size?: number }
params?: {
project_id?: string;
placement_channel_id?: string;
creative_id?: string;
date_from?: string;
date_to?: string;
page?: number;
size?: number;
}
): Promise<PaginatedResponse<PlacementAnalyticsItem>> => {
if (isDemoWorkspace(workspaceId)) {
let items = demoPlacementAnalytics;
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?.date_from) {
const from = new Date(params.date_from);
items = items.filter((p) => new Date(p.placement_date) >= from);
}
if (params?.date_to) {
const to = new Date(params.date_to);
items = items.filter((p) => new Date(p.placement_date) <= to);
}
return paginate(items, params?.page, params?.size);
}
return analyticsApi.placements(workspaceId, params);