automation and ai and some planning and fixes adn docs reorg
This commit is contained in:
@@ -37,7 +37,11 @@ export default function Clusters() {
|
||||
// Data state
|
||||
const [clusters, setClusters] = useState<Cluster[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
|
||||
// Total counts for footer widget (not page-filtered)
|
||||
const [totalWithIdeas, setTotalWithIdeas] = useState(0);
|
||||
const [totalReady, setTotalReady] = useState(0);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
@@ -75,6 +79,34 @@ export default function Clusters() {
|
||||
const progressModal = useProgressModal();
|
||||
const hasReloadedRef = useRef(false);
|
||||
|
||||
// Load total metrics for footer widget (not affected by pagination)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
// Get clusters with status='mapped' (those that have ideas)
|
||||
const mappedRes = await fetchClusters({
|
||||
page_size: 1,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'mapped',
|
||||
});
|
||||
setTotalWithIdeas(mappedRes.count || 0);
|
||||
|
||||
// Get clusters with status='new' (those that are ready for ideas)
|
||||
const newRes = await fetchClusters({
|
||||
page_size: 1,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'new',
|
||||
});
|
||||
setTotalReady(newRes.count || 0);
|
||||
} catch (error) {
|
||||
console.error('Error loading total metrics:', error);
|
||||
}
|
||||
}, [activeSector]);
|
||||
|
||||
// Load total metrics when sector changes
|
||||
useEffect(() => {
|
||||
loadTotalMetrics();
|
||||
}, [loadTotalMetrics]);
|
||||
|
||||
// Load clusters - wrapped in useCallback to prevent infinite loops
|
||||
const loadClusters = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -494,17 +526,17 @@ export default function Clusters() {
|
||||
submoduleColor: 'green',
|
||||
metrics: [
|
||||
{ label: 'Clusters', value: totalCount },
|
||||
{ label: 'With Ideas', value: clusters.filter(c => (c.ideas_count || 0) > 0).length, percentage: `${totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0}%` },
|
||||
{ label: 'With Ideas', value: totalWithIdeas, percentage: `${totalCount > 0 ? Math.round((totalWithIdeas / totalCount) * 100) : 0}%` },
|
||||
{ label: 'Keywords', value: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0) },
|
||||
{ label: 'Ready', value: clusters.filter(c => (c.ideas_count || 0) === 0).length },
|
||||
{ label: 'Ready', value: totalReady },
|
||||
],
|
||||
progress: {
|
||||
value: totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0,
|
||||
value: totalCount > 0 ? Math.round((totalWithIdeas / totalCount) * 100) : 0,
|
||||
label: 'Have Ideas',
|
||||
color: 'green',
|
||||
},
|
||||
hint: clusters.filter(c => (c.ideas_count || 0) === 0).length > 0
|
||||
? `${clusters.filter(c => (c.ideas_count || 0) === 0).length} clusters ready for idea generation`
|
||||
hint: totalReady > 0
|
||||
? `${totalReady} clusters ready for idea generation`
|
||||
: 'All clusters have ideas!',
|
||||
}}
|
||||
moduleStats={{
|
||||
|
||||
@@ -40,7 +40,11 @@ export default function Ideas() {
|
||||
const [ideas, setIdeas] = useState<ContentIdea[]>([]);
|
||||
const [clusters, setClusters] = useState<Cluster[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
|
||||
// Total counts for footer widget (not page-filtered)
|
||||
const [totalInTasks, setTotalInTasks] = useState(0);
|
||||
const [totalPending, setTotalPending] = useState(0);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
@@ -90,6 +94,39 @@ export default function Ideas() {
|
||||
loadClusters();
|
||||
}, []);
|
||||
|
||||
// Load total metrics for footer widget (not affected by pagination)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
try {
|
||||
// Get ideas with status='queued' or 'completed' (those in tasks/writer)
|
||||
const queuedRes = await fetchContentIdeas({
|
||||
page_size: 1,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'queued',
|
||||
});
|
||||
const completedRes = await fetchContentIdeas({
|
||||
page_size: 1,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'completed',
|
||||
});
|
||||
setTotalInTasks((queuedRes.count || 0) + (completedRes.count || 0));
|
||||
|
||||
// Get ideas with status='new' (those ready to become tasks)
|
||||
const newRes = await fetchContentIdeas({
|
||||
page_size: 1,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'new',
|
||||
});
|
||||
setTotalPending(newRes.count || 0);
|
||||
} catch (error) {
|
||||
console.error('Error loading total metrics:', error);
|
||||
}
|
||||
}, [activeSector]);
|
||||
|
||||
// Load total metrics when sector changes
|
||||
useEffect(() => {
|
||||
loadTotalMetrics();
|
||||
}, [loadTotalMetrics]);
|
||||
|
||||
// Load ideas - wrapped in useCallback
|
||||
const loadIdeas = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -422,17 +459,17 @@ export default function Ideas() {
|
||||
submoduleColor: 'amber',
|
||||
metrics: [
|
||||
{ label: 'Ideas', value: totalCount },
|
||||
{ label: 'In Tasks', value: ideas.filter(i => i.status === 'queued' || i.status === 'completed').length, percentage: `${totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'queued' || i.status === 'completed').length / totalCount) * 100) : 0}%` },
|
||||
{ label: 'Pending', value: ideas.filter(i => i.status === 'new').length },
|
||||
{ label: 'In Tasks', value: totalInTasks, percentage: `${totalCount > 0 ? Math.round((totalInTasks / totalCount) * 100) : 0}%` },
|
||||
{ label: 'Pending', value: totalPending },
|
||||
{ label: 'From Clusters', value: clusters.length },
|
||||
],
|
||||
progress: {
|
||||
value: totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'queued' || i.status === 'completed').length / totalCount) * 100) : 0,
|
||||
value: totalCount > 0 ? Math.round((totalInTasks / totalCount) * 100) : 0,
|
||||
label: 'Converted',
|
||||
color: 'amber',
|
||||
},
|
||||
hint: ideas.filter(i => i.status === 'new').length > 0
|
||||
? `${ideas.filter(i => i.status === 'new').length} ideas ready to become tasks`
|
||||
hint: totalPending > 0
|
||||
? `${totalPending} ideas ready to become tasks`
|
||||
: 'All ideas converted!',
|
||||
}}
|
||||
moduleStats={{
|
||||
|
||||
@@ -45,7 +45,12 @@ export default function Keywords() {
|
||||
const [keywords, setKeywords] = useState<Keyword[]>([]);
|
||||
const [clusters, setClusters] = useState<Cluster[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
|
||||
// Total counts for footer widget (not page-filtered)
|
||||
const [totalClustered, setTotalClustered] = useState(0);
|
||||
const [totalUnmapped, setTotalUnmapped] = useState(0);
|
||||
const [totalVolume, setTotalVolume] = useState(0);
|
||||
|
||||
// Filter state - match Keywords.tsx
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
@@ -108,6 +113,44 @@ export default function Keywords() {
|
||||
loadClusters();
|
||||
}, []);
|
||||
|
||||
// Load total metrics for footer widget (not affected by pagination)
|
||||
const loadTotalMetrics = useCallback(async () => {
|
||||
if (!activeSite) return;
|
||||
|
||||
try {
|
||||
// Get all keywords (total count) - this is already in totalCount from main load
|
||||
// Get keywords with status='mapped' (those that have been mapped to a cluster)
|
||||
const mappedRes = await fetchKeywords({
|
||||
page_size: 1,
|
||||
site_id: activeSite.id,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'mapped',
|
||||
});
|
||||
setTotalClustered(mappedRes.count || 0);
|
||||
|
||||
// Get keywords with status='new' (those that are ready to cluster but haven't been yet)
|
||||
const newRes = await fetchKeywords({
|
||||
page_size: 1,
|
||||
site_id: activeSite.id,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
status: 'new',
|
||||
});
|
||||
setTotalUnmapped(newRes.count || 0);
|
||||
|
||||
// Get total volume across all keywords (we need to fetch all or rely on backend aggregation)
|
||||
// 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);
|
||||
} catch (error) {
|
||||
console.error('Error loading total metrics:', error);
|
||||
}
|
||||
}, [activeSite, activeSector]);
|
||||
|
||||
// Load total metrics when site/sector changes
|
||||
useEffect(() => {
|
||||
loadTotalMetrics();
|
||||
}, [loadTotalMetrics]);
|
||||
|
||||
// Load keywords - wrapped in useCallback to prevent infinite loops
|
||||
const loadKeywords = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -712,17 +755,17 @@ export default function Keywords() {
|
||||
submoduleColor: 'blue',
|
||||
metrics: [
|
||||
{ label: 'Keywords', value: totalCount },
|
||||
{ label: 'Clustered', value: keywords.filter(k => k.cluster_id).length, percentage: `${totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0}%` },
|
||||
{ label: 'Unmapped', value: keywords.filter(k => !k.cluster_id).length },
|
||||
{ label: 'Volume', value: `${(keywords.reduce((sum, k) => sum + (k.volume || 0), 0) / 1000).toFixed(1)}K` },
|
||||
{ label: 'Clustered', value: totalClustered, percentage: `${totalCount > 0 ? Math.round((totalClustered / totalCount) * 100) : 0}%` },
|
||||
{ label: 'Unmapped', value: totalUnmapped },
|
||||
{ label: 'Volume', value: totalVolume > 0 ? `${(totalVolume / 1000).toFixed(1)}K` : '-' },
|
||||
],
|
||||
progress: {
|
||||
value: totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0,
|
||||
value: totalCount > 0 ? Math.round((totalClustered / totalCount) * 100) : 0,
|
||||
label: 'Clustered',
|
||||
color: 'blue',
|
||||
},
|
||||
hint: keywords.filter(k => !k.cluster_id).length > 0
|
||||
? `${keywords.filter(k => !k.cluster_id).length} keywords ready to cluster`
|
||||
hint: totalUnmapped > 0
|
||||
? `${totalUnmapped} keywords ready to cluster`
|
||||
: 'All keywords clustered!',
|
||||
}}
|
||||
moduleStats={{
|
||||
|
||||
Reference in New Issue
Block a user