metricsa dn backedn fixes
This commit is contained in:
@@ -11,15 +11,10 @@ import { useOnboardingStore } from "../../store/onboardingStore";
|
||||
import { useBillingStore } from "../../store/billingStore";
|
||||
import { GridIcon, PlusIcon } from "../../icons";
|
||||
import {
|
||||
fetchKeywords,
|
||||
fetchClusters,
|
||||
fetchContentIdeas,
|
||||
fetchTasks,
|
||||
fetchContent,
|
||||
fetchContentImages,
|
||||
fetchSites,
|
||||
Site,
|
||||
} from "../../services/api";
|
||||
import { getDashboardStats } from "../../services/billing.api";
|
||||
import { useSiteStore } from "../../store/siteStore";
|
||||
import { useSectorStore } from "../../store/sectorStore";
|
||||
import { useToast } from "../../components/ui/toast/ToastContainer";
|
||||
@@ -155,49 +150,33 @@ export default function Home() {
|
||||
const fetchDashboardData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||
|
||||
const siteId = siteFilter === 'all' ? undefined : siteFilter;
|
||||
|
||||
// Fetch pipeline counts sequentially to avoid rate limiting
|
||||
const keywordsRes = await fetchKeywords({ page_size: 1, site_id: siteId });
|
||||
await delay(100);
|
||||
const clustersRes = await fetchClusters({ page_size: 1, site_id: siteId });
|
||||
await delay(100);
|
||||
const ideasRes = await fetchContentIdeas({ page_size: 1, site_id: siteId });
|
||||
await delay(100);
|
||||
const tasksRes = await fetchTasks({ page_size: 1, site_id: siteId });
|
||||
await delay(100);
|
||||
const contentRes = await fetchContent({ page_size: 1, site_id: siteId });
|
||||
await delay(100);
|
||||
const imagesRes = await fetchContentImages({ page_size: 1, site_id: siteId });
|
||||
// Fetch real dashboard stats from API
|
||||
const stats = await getDashboardStats({
|
||||
site_id: siteId,
|
||||
days: 7
|
||||
});
|
||||
|
||||
const totalKeywords = keywordsRes.count || 0;
|
||||
const totalClusters = clustersRes.count || 0;
|
||||
const totalIdeas = ideasRes.count || 0;
|
||||
const totalTasks = tasksRes.count || 0;
|
||||
const totalContent = contentRes.count || 0;
|
||||
const totalImages = imagesRes.count || 0;
|
||||
const publishedContent = Math.floor(totalContent * 0.6); // Placeholder
|
||||
|
||||
// Calculate completion percentage
|
||||
const completionPercentage = totalKeywords > 0
|
||||
? Math.round((publishedContent / totalKeywords) * 100)
|
||||
// Update pipeline data from real API data
|
||||
const { pipeline, counts } = stats;
|
||||
const completionPercentage = pipeline.keywords > 0
|
||||
? Math.round((pipeline.published / pipeline.keywords) * 100)
|
||||
: 0;
|
||||
|
||||
// Update pipeline data
|
||||
setPipelineData({
|
||||
sites: sites.length,
|
||||
keywords: totalKeywords,
|
||||
clusters: totalClusters,
|
||||
ideas: totalIdeas,
|
||||
tasks: totalTasks,
|
||||
drafts: totalContent,
|
||||
published: publishedContent,
|
||||
sites: pipeline.sites,
|
||||
keywords: pipeline.keywords,
|
||||
clusters: pipeline.clusters,
|
||||
ideas: pipeline.ideas,
|
||||
tasks: pipeline.tasks,
|
||||
drafts: pipeline.drafts,
|
||||
published: pipeline.published,
|
||||
completionPercentage: Math.min(completionPercentage, 100),
|
||||
});
|
||||
|
||||
// Generate attention items based on data
|
||||
// Generate attention items based on real data
|
||||
const attentionList: AttentionItem[] = [];
|
||||
|
||||
// Check for sites without sectors
|
||||
@@ -213,130 +192,85 @@ export default function Home() {
|
||||
});
|
||||
}
|
||||
|
||||
// Check for content needing images
|
||||
const contentWithoutImages = totalContent - Math.floor(totalContent * 0.7);
|
||||
if (contentWithoutImages > 0 && totalContent > 0) {
|
||||
// Check for content needing images (content in review without all images generated)
|
||||
const contentWithPendingImages = counts.images.pending;
|
||||
if (contentWithPendingImages > 0) {
|
||||
attentionList.push({
|
||||
id: 'needs_images',
|
||||
type: 'pending_review',
|
||||
title: 'articles need images',
|
||||
count: contentWithoutImages,
|
||||
title: 'images pending',
|
||||
count: contentWithPendingImages,
|
||||
description: 'Generate images before publishing',
|
||||
actionLabel: 'Generate Images',
|
||||
actionHref: '/writer/images',
|
||||
});
|
||||
}
|
||||
|
||||
// Check for content in review
|
||||
if (counts.content.review > 0) {
|
||||
attentionList.push({
|
||||
id: 'pending_review',
|
||||
type: 'pending_review',
|
||||
title: 'articles ready for review',
|
||||
count: counts.content.review,
|
||||
description: 'Review and publish content',
|
||||
actionLabel: 'Review Content',
|
||||
actionHref: '/writer/content?status=review',
|
||||
});
|
||||
}
|
||||
|
||||
setAttentionItems(attentionList);
|
||||
|
||||
// Update content velocity (using mock calculations based on totals)
|
||||
const weeklyArticles = Math.floor(totalContent * 0.15);
|
||||
const monthlyArticles = Math.floor(totalContent * 0.4);
|
||||
setContentVelocity({
|
||||
thisWeek: {
|
||||
articles: weeklyArticles,
|
||||
words: weeklyArticles * 1500,
|
||||
images: Math.floor(totalImages * 0.15)
|
||||
},
|
||||
thisMonth: {
|
||||
articles: monthlyArticles,
|
||||
words: monthlyArticles * 1500,
|
||||
images: Math.floor(totalImages * 0.4)
|
||||
},
|
||||
total: {
|
||||
articles: totalContent,
|
||||
words: totalContent * 1500,
|
||||
images: totalImages
|
||||
},
|
||||
trend: totalContent > 0 ? Math.floor(Math.random() * 40) - 10 : 0,
|
||||
});
|
||||
// Update content velocity from real API data
|
||||
setContentVelocity(stats.content_velocity);
|
||||
|
||||
// Generate mock recent activity based on actual data
|
||||
const activityList: ActivityItem[] = [];
|
||||
if (totalClusters > 0) {
|
||||
activityList.push({
|
||||
id: 'cluster_1',
|
||||
type: 'clustering',
|
||||
title: `Clustered ${Math.min(45, totalKeywords)} keywords → ${Math.min(8, totalClusters)} clusters`,
|
||||
description: '',
|
||||
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
||||
href: '/planner/clusters',
|
||||
});
|
||||
}
|
||||
if (totalContent > 0) {
|
||||
activityList.push({
|
||||
id: 'content_1',
|
||||
type: 'content',
|
||||
title: `Generated ${Math.min(5, totalContent)} articles`,
|
||||
description: '',
|
||||
timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000),
|
||||
href: '/writer/content',
|
||||
});
|
||||
}
|
||||
if (totalImages > 0) {
|
||||
activityList.push({
|
||||
id: 'images_1',
|
||||
type: 'images',
|
||||
title: `Created ${Math.min(15, totalImages)} image prompts`,
|
||||
description: '',
|
||||
timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
href: '/writer/images',
|
||||
});
|
||||
}
|
||||
if (publishedContent > 0) {
|
||||
activityList.push({
|
||||
id: 'published_1',
|
||||
type: 'published',
|
||||
title: `Published article to WordPress`,
|
||||
description: '',
|
||||
timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
href: '/writer/published',
|
||||
});
|
||||
}
|
||||
if (totalKeywords > 0) {
|
||||
activityList.push({
|
||||
id: 'keywords_1',
|
||||
type: 'keywords',
|
||||
title: `Added ${Math.min(23, totalKeywords)} keywords`,
|
||||
description: '',
|
||||
timestamp: new Date(Date.now() - 48 * 60 * 60 * 1000),
|
||||
href: '/planner/keywords',
|
||||
});
|
||||
}
|
||||
// Update recent activity from real API data (convert timestamp strings to Date objects)
|
||||
const activityList: ActivityItem[] = stats.recent_activity.map(item => ({
|
||||
...item,
|
||||
timestamp: new Date(item.timestamp),
|
||||
}));
|
||||
setRecentActivity(activityList);
|
||||
|
||||
// Update AI operations (mock data based on content created)
|
||||
const clusteringOps = totalClusters > 0 ? Math.ceil(totalClusters / 3) : 0;
|
||||
const ideasOps = totalIdeas > 0 ? Math.ceil(totalIdeas / 5) : 0;
|
||||
const contentOps = totalContent;
|
||||
const imageOps = totalImages > 0 ? Math.ceil(totalImages / 3) : 0;
|
||||
|
||||
// Update AI operations from real API data
|
||||
// Map operation types to display types
|
||||
const operationTypeMap: Record<string, string> = {
|
||||
'clustering': 'clustering',
|
||||
'idea_generation': 'ideas',
|
||||
'content_generation': 'content',
|
||||
'image_generation': 'images',
|
||||
'image_prompt_extraction': 'images',
|
||||
};
|
||||
|
||||
const mappedOperations = stats.ai_operations.operations.map(op => ({
|
||||
type: operationTypeMap[op.type] || op.type,
|
||||
count: op.count,
|
||||
credits: op.credits,
|
||||
}));
|
||||
|
||||
// Ensure all expected types exist
|
||||
const expectedTypes = ['clustering', 'ideas', 'content', 'images'];
|
||||
for (const type of expectedTypes) {
|
||||
if (!mappedOperations.find(op => op.type === type)) {
|
||||
mappedOperations.push({ type, count: 0, credits: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
setAIOperations({
|
||||
period: '7d',
|
||||
operations: [
|
||||
{ type: 'clustering', count: clusteringOps, credits: clusteringOps * 10 },
|
||||
{ type: 'ideas', count: ideasOps, credits: ideasOps * 2 },
|
||||
{ type: 'content', count: contentOps, credits: contentOps * 50 },
|
||||
{ type: 'images', count: imageOps, credits: imageOps * 5 },
|
||||
],
|
||||
totals: {
|
||||
count: clusteringOps + ideasOps + contentOps + imageOps,
|
||||
credits: (clusteringOps * 10) + (ideasOps * 2) + (contentOps * 50) + (imageOps * 5),
|
||||
successRate: 98.5,
|
||||
avgCreditsPerOp: contentOps > 0 ? 18.6 : 0,
|
||||
},
|
||||
period: stats.ai_operations.period,
|
||||
operations: mappedOperations,
|
||||
totals: stats.ai_operations.totals,
|
||||
});
|
||||
|
||||
// Set automation status (would come from API in real implementation)
|
||||
// Set automation status (would come from automation API)
|
||||
setAutomationData({
|
||||
status: sites.length > 0 ? 'active' : 'not_configured',
|
||||
schedule: sites.length > 0 ? 'Daily 9 AM' : undefined,
|
||||
lastRun: sites.length > 0 ? {
|
||||
lastRun: sites.length > 0 && counts.content.total > 0 ? {
|
||||
timestamp: new Date(Date.now() - 12 * 60 * 60 * 1000),
|
||||
clustered: Math.min(12, totalKeywords),
|
||||
ideas: Math.min(8, totalIdeas),
|
||||
content: Math.min(5, totalContent),
|
||||
images: Math.min(15, totalImages),
|
||||
clustered: pipeline.clusters,
|
||||
ideas: pipeline.ideas,
|
||||
content: counts.content.total,
|
||||
images: counts.images.total,
|
||||
success: true,
|
||||
} : undefined,
|
||||
nextRun: sites.length > 0 ? new Date(Date.now() + 12 * 60 * 60 * 1000) : undefined,
|
||||
@@ -352,7 +286,7 @@ export default function Home() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [siteFilter, sites.length, toast]);
|
||||
}, [siteFilter, sites, toast]);
|
||||
|
||||
// Fetch dashboard data when filter changes
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user