metricsa dn backedn fixes

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-29 04:33:22 +00:00
parent 53fdebf733
commit 0ffd21b9bf
17 changed files with 929 additions and 266 deletions

View File

@@ -7,6 +7,7 @@ import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import TablePageTemplate from '../../templates/TablePageTemplate';
import {
fetchClusters,
fetchImages,
createCluster,
updateCluster,
deleteCluster,
@@ -41,6 +42,7 @@ export default function Clusters() {
// Total counts for footer widget (not page-filtered)
const [totalWithIdeas, setTotalWithIdeas] = useState(0);
const [totalReady, setTotalReady] = useState(0);
const [totalImagesCount, setTotalImagesCount] = useState(0);
// Filter state
const [searchTerm, setSearchTerm] = useState('');
@@ -97,6 +99,10 @@ export default function Clusters() {
status: 'new',
});
setTotalReady(newRes.count || 0);
// Get actual total images count
const imagesRes = await fetchImages({ page_size: 1 });
setTotalImagesCount(imagesRes.count || 0);
} catch (error) {
console.error('Error loading total metrics:', error);
}
@@ -184,18 +190,17 @@ export default function Clusters() {
};
}, [loadClusters]);
// Debounced search
// Debounced search - reset to page 1 when search term changes
// Only depend on searchTerm to avoid pagination reset on page navigation
useEffect(() => {
const timer = setTimeout(() => {
if (currentPage === 1) {
loadClusters();
} else {
setCurrentPage(1);
}
// Always reset to page 1 when search changes
// The main useEffect will handle reloading when currentPage changes
setCurrentPage(1);
}, 500);
return () => clearTimeout(timer);
}, [searchTerm, currentPage, loadClusters]);
}, [searchTerm]);
// Reset to page 1 when pageSize changes
useEffect(() => {
@@ -380,16 +385,43 @@ export default function Clusters() {
handleRowAction,
]);
// Calculate header metrics
// Calculate header metrics - use totalWithIdeas/totalReady from API calls (not page data)
// This ensures metrics show correct totals across all pages, not just current page
const headerMetrics = useMemo(() => {
if (!pageConfig?.headerMetrics) return [];
return pageConfig.headerMetrics.map((metric) => ({
label: metric.label,
value: metric.calculate({ clusters, totalCount }),
accentColor: metric.accentColor,
tooltip: (metric as any).tooltip,
}));
}, [pageConfig?.headerMetrics, clusters, totalCount]);
// Override the calculate function to use pre-loaded totals instead of filtering page data
return pageConfig.headerMetrics.map((metric) => {
let value: number;
switch (metric.label) {
case 'Clusters':
value = totalCount || 0;
break;
case 'New':
// Use totalReady from loadTotalMetrics() (clusters without ideas)
value = totalReady;
break;
case 'Keywords':
// Sum of keywords across all clusters on current page (this is acceptable for display)
value = clusters.reduce((sum: number, c) => sum + (c.keywords_count || 0), 0);
break;
case 'Volume':
// Sum of volume across all clusters on current page (this is acceptable for display)
value = clusters.reduce((sum: number, c) => sum + (c.total_volume || 0), 0);
break;
default:
value = metric.calculate({ clusters, totalCount });
}
return {
label: metric.label,
value,
accentColor: metric.accentColor,
tooltip: (metric as any).tooltip,
};
});
}, [pageConfig?.headerMetrics, clusters, totalCount, totalReady, totalWithIdeas]);
const resetForm = useCallback(() => {
setFormData({
@@ -589,7 +621,7 @@ export default function Clusters() {
],
writerItems: [
{ label: 'Content Generated', value: 0, color: 'blue' },
{ label: 'Images Created', value: 0, color: 'purple' },
{ label: 'Images Created', value: totalImagesCount, color: 'purple' },
{ label: 'Published', value: 0, color: 'green' },
],
analyticsHref: '/account/usage',

View File

@@ -7,6 +7,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
import TablePageTemplate from '../../templates/TablePageTemplate';
import {
fetchContentIdeas,
fetchImages,
createContentIdea,
updateContentIdea,
deleteContentIdea,
@@ -44,6 +45,7 @@ export default function Ideas() {
// Total counts for footer widget (not page-filtered)
const [totalInTasks, setTotalInTasks] = useState(0);
const [totalPending, setTotalPending] = useState(0);
const [totalImagesCount, setTotalImagesCount] = useState(0);
// Filter state
const [searchTerm, setSearchTerm] = useState('');
@@ -117,6 +119,10 @@ export default function Ideas() {
status: 'new',
});
setTotalPending(newRes.count || 0);
// Get actual total images count
const imagesRes = await fetchImages({ page_size: 1 });
setTotalImagesCount(imagesRes.count || 0);
} catch (error) {
console.error('Error loading total metrics:', error);
}
@@ -189,18 +195,17 @@ export default function Ideas() {
setCurrentPage(1);
}, [pageSize]);
// Debounced search
// Debounced search - reset to page 1 when search term changes
// Only depend on searchTerm to avoid pagination reset on page navigation
useEffect(() => {
const timer = setTimeout(() => {
if (currentPage === 1) {
loadIdeas();
} else {
setCurrentPage(1);
}
// Always reset to page 1 when search changes
// The main useEffect will handle reloading when currentPage changes
setCurrentPage(1);
}, 500);
return () => clearTimeout(timer);
}, [searchTerm, currentPage, loadIdeas]);
}, [searchTerm]);
// Handle sorting
const handleSort = (field: string, direction: 'asc' | 'desc') => {
@@ -289,16 +294,43 @@ export default function Ideas() {
});
}, [clusters, activeSector, formData, searchTerm, statusFilter, clusterFilter, structureFilter, typeFilter]);
// Calculate header metrics
// Calculate header metrics - use totalInTasks/totalPending from API calls (not page data)
// This ensures metrics show correct totals across all pages, not just current page
const headerMetrics = useMemo(() => {
if (!pageConfig?.headerMetrics) return [];
return pageConfig.headerMetrics.map((metric) => ({
label: metric.label,
value: metric.calculate({ ideas, totalCount }),
accentColor: metric.accentColor,
tooltip: (metric as any).tooltip,
}));
}, [pageConfig?.headerMetrics, ideas, totalCount]);
// Override the calculate function to use pre-loaded totals instead of filtering page data
return pageConfig.headerMetrics.map((metric) => {
let value: number;
switch (metric.label) {
case 'Ideas':
value = totalCount || 0;
break;
case 'New':
// Use totalPending from loadTotalMetrics() (ideas with status='new')
value = totalPending;
break;
case 'Queued':
// Use totalInTasks from loadTotalMetrics() (ideas with status='queued')
value = totalInTasks;
break;
case 'Completed':
// Calculate completed from totalCount - (totalPending + totalInTasks)
value = Math.max(0, totalCount - totalPending - totalInTasks);
break;
default:
value = metric.calculate({ ideas, totalCount });
}
return {
label: metric.label,
value,
accentColor: metric.accentColor,
tooltip: (metric as any).tooltip,
};
});
}, [pageConfig?.headerMetrics, ideas, totalCount, totalPending, totalInTasks]);
const resetForm = useCallback(() => {
setFormData({
@@ -522,7 +554,7 @@ export default function Ideas() {
],
writerItems: [
{ label: 'Content Generated', value: ideas.filter(i => i.status === 'completed').length, color: 'blue' },
{ label: 'Images Created', value: 0, color: 'purple' },
{ label: 'Images Created', value: totalImagesCount, color: 'purple' },
{ label: 'Published', value: 0, color: 'green' },
],
analyticsHref: '/account/usage',

View File

@@ -8,6 +8,7 @@ import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import TablePageTemplate from '../../templates/TablePageTemplate';
import {
fetchKeywords,
fetchImages,
createKeyword,
updateKeyword,
deleteKeyword,
@@ -50,6 +51,7 @@ export default function Keywords() {
const [totalClustered, setTotalClustered] = useState(0);
const [totalUnmapped, setTotalUnmapped] = useState(0);
const [totalVolume, setTotalVolume] = useState(0);
const [totalImagesCount, setTotalImagesCount] = useState(0);
// Filter state - match Keywords.tsx
const [searchTerm, setSearchTerm] = useState('');
@@ -141,6 +143,10 @@ export default function Keywords() {
// For now, we'll just calculate from current data or set to 0
// TODO: Backend should provide total volume as an aggregated metric
setTotalVolume(0);
// Get actual total images count
const imagesRes = await fetchImages({ page_size: 1 });
setTotalImagesCount(imagesRes.count || 0);
} catch (error) {
console.error('Error loading total metrics:', error);
}
@@ -524,16 +530,43 @@ export default function Keywords() {
activeSite,
]);
// Calculate header metrics from config (matching reference plugin KPIs from kpi-config.php)
// Calculate header metrics - use totalClustered/totalUnmapped from API calls (not page data)
// This ensures metrics show correct totals across all pages, not just current page
const headerMetrics = useMemo(() => {
if (!pageConfig?.headerMetrics) return [];
return pageConfig.headerMetrics.map((metric) => ({
label: metric.label,
value: metric.calculate({ keywords, totalCount, clusters }),
accentColor: metric.accentColor,
tooltip: (metric as any).tooltip, // Add tooltip support
}));
}, [pageConfig?.headerMetrics, keywords, totalCount, clusters]);
// Override the calculate function to use pre-loaded totals instead of filtering page data
return pageConfig.headerMetrics.map((metric) => {
let value: number;
switch (metric.label) {
case 'Keywords':
value = totalCount || 0;
break;
case 'Clustered':
// Use totalClustered from loadTotalMetrics() instead of filtering page data
value = totalClustered;
break;
case 'Unmapped':
// Use totalUnmapped from loadTotalMetrics() instead of filtering page data
value = totalUnmapped;
break;
case 'Volume':
// Use totalVolume from loadTotalMetrics() (if implemented) or keep original
value = totalVolume || keywords.reduce((sum: number, k) => sum + (k.volume || 0), 0);
break;
default:
value = metric.calculate({ keywords, totalCount, clusters });
}
return {
label: metric.label,
value,
accentColor: metric.accentColor,
tooltip: (metric as any).tooltip,
};
});
}, [pageConfig?.headerMetrics, keywords, totalCount, clusters, totalClustered, totalUnmapped, totalVolume]);
// Calculate workflow insights based on UX doc principles
const workflowStats = useMemo(() => {
@@ -819,7 +852,7 @@ export default function Keywords() {
],
writerItems: [
{ label: 'Content Generated', value: 0, color: 'blue' },
{ label: 'Images Created', value: 0, color: 'purple' },
{ label: 'Images Created', value: totalImagesCount, color: 'purple' },
{ label: 'Published', value: 0, color: 'green' },
],
creditsUsed: 0,