From 368601f68c7399772d81f36bda828313d5e63364 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Mon, 12 Jan 2026 05:28:36 +0000 Subject: [PATCH] header footer metrics update and credits by site fixes --- frontend/src/config/pages/approved.config.tsx | 47 +++++++--- frontend/src/config/pages/content.config.tsx | 24 ++++- frontend/src/config/pages/images.config.tsx | 50 ++++++---- frontend/src/config/pages/review.config.tsx | 46 +++++---- frontend/src/config/pages/tasks.config.tsx | 39 ++++---- frontend/src/hooks/useModuleStats.ts | 2 +- frontend/src/pages/Writer/Approved.tsx | 66 +++++++------ frontend/src/pages/Writer/Content.tsx | 32 +++++-- frontend/src/pages/Writer/Images.tsx | 93 +++++++++---------- frontend/src/pages/Writer/Review.tsx | 71 ++++++++++---- frontend/src/pages/Writer/Tasks.tsx | 82 ++++++++-------- frontend/src/services/api.ts | 2 + 12 files changed, 339 insertions(+), 215 deletions(-) diff --git a/frontend/src/config/pages/approved.config.tsx b/frontend/src/config/pages/approved.config.tsx index eae40241..46e853a0 100644 --- a/frontend/src/config/pages/approved.config.tsx +++ b/frontend/src/config/pages/approved.config.tsx @@ -296,24 +296,45 @@ export function createApprovedPageConfig(params: { const headerMetrics: HeaderMetricConfig[] = [ { - label: 'Approved', - accentColor: 'green', - calculate: (data: { totalCount: number }) => data.totalCount, - tooltip: 'Total approved content ready for publishing.', - }, - { - label: 'On Site', + label: 'Content', accentColor: 'blue', - calculate: (data: { content: Content[] }) => - data.content.filter(c => c.external_id).length, - tooltip: 'Content published to your website.', + calculate: (data: { totalCount: number }) => data.totalCount, + tooltip: 'Total content items tracked. Overall volume across all stages.', }, { - label: 'Pending', + label: 'Draft', accentColor: 'amber', calculate: (data: { content: Content[] }) => - data.content.filter(c => !c.external_id).length, - tooltip: 'Approved content not yet published to site.', + data.content.filter(c => c.status === 'draft').length, + tooltip: 'Content written, images not generated. Generate images to move to review.', + }, + { + label: 'In Review', + accentColor: 'purple', + calculate: (data: { content: Content[] }) => + data.content.filter(c => c.status === 'review').length, + tooltip: 'Images generated, awaiting approval. Review and approve to publish.', + }, + { + label: 'Approved', + accentColor: 'green', + calculate: (data: { content: Content[] }) => + data.content.filter(c => c.status === 'approved').length, + tooltip: 'Approved content awaiting publishing. Publish to site when ready.', + }, + { + label: 'Published', + accentColor: 'green', + calculate: (data: { content: Content[] }) => + data.content.filter(c => c.status === 'published').length, + tooltip: 'Live content on your website. Successfully published and accessible.', + }, + { + label: 'Total Images', + accentColor: 'blue', + calculate: (data: { content: Content[] }) => + data.content.filter(c => c.has_generated_images).length, + tooltip: 'Total images generated across all content. Tracks visual asset coverage.', }, ]; diff --git a/frontend/src/config/pages/content.config.tsx b/frontend/src/config/pages/content.config.tsx index 5c64f3a8..99416c41 100644 --- a/frontend/src/config/pages/content.config.tsx +++ b/frontend/src/config/pages/content.config.tsx @@ -456,28 +456,42 @@ export const createContentPageConfig = ( value: 0, accentColor: 'blue' as const, calculate: (data) => data.totalCount || 0, - tooltip: 'Total content pieces generated. Includes drafts, review, and published content.', + tooltip: 'Total content items tracked. Overall volume across all stages.', }, { label: 'Draft', value: 0, accentColor: 'amber' as const, calculate: (data) => data.content.filter((c: Content) => c.status === 'draft').length, - tooltip: 'Content in draft stage. Edit and refine before moving to review.', + tooltip: 'Content written, images not generated. Generate images to move to review.', }, { label: 'In Review', value: 0, - accentColor: 'blue' as const, + accentColor: 'purple' as const, calculate: (data) => data.content.filter((c: Content) => c.status === 'review').length, - tooltip: 'Content awaiting review and approval. Review for quality before publishing.', + tooltip: 'Images generated, awaiting approval. Review and approve to publish.', + }, + { + label: 'Approved', + value: 0, + accentColor: 'green' as const, + calculate: (data) => data.content.filter((c: Content) => c.status === 'approved').length, + tooltip: 'Approved content awaiting publishing. Publish to site when ready.', }, { label: 'Published', value: 0, accentColor: 'green' as const, calculate: (data) => data.content.filter((c: Content) => c.status === 'published').length, - tooltip: 'Published content ready for WordPress sync. Track your published library.', + tooltip: 'Live content on your website. Successfully published and accessible.', + }, + { + label: 'Total Images', + value: 0, + accentColor: 'blue' as const, + calculate: (data) => 0, + tooltip: 'Total images generated across all content. Tracks visual asset coverage.', }, ], }; diff --git a/frontend/src/config/pages/images.config.tsx b/frontend/src/config/pages/images.config.tsx index 2d66bb23..1b5bdeb9 100644 --- a/frontend/src/config/pages/images.config.tsx +++ b/frontend/src/config/pages/images.config.tsx @@ -207,28 +207,42 @@ export const createImagesPageConfig = ( value: 0, accentColor: 'blue' as const, calculate: (data) => data.totalCount || 0, - tooltip: 'Total content pieces with image generation. Track image coverage across all content.', + tooltip: 'Total content items tracked. Overall volume across all stages.', }, { - label: 'Complete', - value: 0, - accentColor: 'green' as const, - calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.overall_status === 'complete').length, - tooltip: 'Content with all images generated. Ready for publishing with full visual coverage.', - }, - { - label: 'Partial', - value: 0, - accentColor: 'blue' as const, - calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.overall_status === 'partial').length, - tooltip: 'Content with some images missing. Generate remaining images to complete visual assets.', - }, - { - label: 'Pending', + label: 'Draft', value: 0, accentColor: 'amber' as const, - calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.overall_status === 'pending').length, - tooltip: 'Content waiting for image generation. Queue these to start creating visual assets.', + calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.content_status === 'draft').length, + tooltip: 'Content written, images not generated. Generate images to move to review.', + }, + { + label: 'In Review', + value: 0, + accentColor: 'purple' as const, + calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.content_status === 'review').length, + tooltip: 'Images generated, awaiting approval. Review and approve to publish.', + }, + { + label: 'Approved', + value: 0, + accentColor: 'green' as const, + calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.content_status === 'approved').length, + tooltip: 'Approved content awaiting publishing. Publish to site when ready.', + }, + { + label: 'Published', + value: 0, + accentColor: 'green' as const, + calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.content_status === 'published').length, + tooltip: 'Live content on your website. Successfully published and accessible.', + }, + { + label: 'Total Images', + value: 0, + accentColor: 'blue' as const, + calculate: (data) => data.images.filter((i: ContentImagesGroup) => i.overall_status === 'complete').length, + tooltip: 'Total images generated across all content. Tracks visual asset coverage.', }, ], maxInArticleImages: maxImages, diff --git a/frontend/src/config/pages/review.config.tsx b/frontend/src/config/pages/review.config.tsx index 53c80c5e..a524550e 100644 --- a/frontend/src/config/pages/review.config.tsx +++ b/frontend/src/config/pages/review.config.tsx @@ -259,28 +259,40 @@ export function createReviewPageConfig(params: { ], headerMetrics: [ { - label: 'Ready', + label: 'Content', accentColor: 'blue', calculate: ({ totalCount }) => totalCount, - tooltip: 'Content ready for final review. Review quality, SEO, and images before publishing.', + tooltip: 'Total content items tracked. Overall volume across all stages.', }, { - label: 'Images', - accentColor: 'green', - calculate: ({ content }) => content.filter(c => c.has_generated_images).length, - tooltip: 'Content with generated images. Visual assets complete and ready for review.', - }, - { - label: 'Optimized', - accentColor: 'purple', - calculate: ({ content }) => content.filter(c => c.optimization_scores && c.optimization_scores.overall_score >= 80).length, - tooltip: 'Content with high SEO optimization scores (80%+). Well-optimized for search engines.', - }, - { - label: 'Sync Ready', + label: 'Draft', accentColor: 'amber', - calculate: ({ content }) => content.filter(c => c.has_generated_images && c.optimization_scores && c.optimization_scores.overall_score >= 70).length, - tooltip: 'Content ready for WordPress sync. Has images and good optimization score.', + calculate: ({ content }) => content.filter(c => c.status === 'draft').length, + tooltip: 'Content written, images not generated. Generate images to move to review.', + }, + { + label: 'In Review', + accentColor: 'purple', + calculate: ({ content }) => content.filter(c => c.status === 'review').length, + tooltip: 'Images generated, awaiting approval. Review and approve to publish.', + }, + { + label: 'Approved', + accentColor: 'green', + calculate: ({ content }) => content.filter(c => c.status === 'approved').length, + tooltip: 'Approved content awaiting publishing. Publish to site when ready.', + }, + { + label: 'Published', + accentColor: 'green', + calculate: ({ content }) => content.filter(c => c.status === 'published').length, + tooltip: 'Live content on your website. Successfully published and accessible.', + }, + { + label: 'Total Images', + accentColor: 'blue', + calculate: ({ content }) => content.filter(c => c.has_generated_images).length, + tooltip: 'Total images generated across all content. Tracks visual asset coverage.', }, ], }; diff --git a/frontend/src/config/pages/tasks.config.tsx b/frontend/src/config/pages/tasks.config.tsx index dba0722c..a8fe993e 100644 --- a/frontend/src/config/pages/tasks.config.tsx +++ b/frontend/src/config/pages/tasks.config.tsx @@ -454,39 +454,46 @@ export const createTasksPageConfig = ( ], headerMetrics: [ { - label: 'Tasks', + label: 'Content', value: 0, accentColor: 'blue' as const, calculate: (data) => data.totalCount || 0, - tooltip: 'Total content generation tasks. Tasks process ideas into written content automatically.', + tooltip: 'Total content items tracked. Overall volume across all stages.', }, { - label: 'In Queue', + label: 'Draft', value: 0, accentColor: 'amber' as const, - calculate: (data) => data.tasks.filter((t: Task) => t.status === 'queued').length, - tooltip: 'Tasks queued for processing. These will be picked up by the content generation system.', + calculate: (data) => data.tasks.filter((t: Task) => t.status === 'draft').length, + tooltip: 'Content written, images not generated. Generate images to move to review.', }, { - label: 'Processing', + label: 'In Review', value: 0, - accentColor: 'blue' as const, - calculate: (data) => data.tasks.filter((t: Task) => t.status === 'in_progress').length, - tooltip: 'Tasks currently being processed. Content is being generated by AI right now.', + accentColor: 'purple' as const, + calculate: (data) => data.tasks.filter((t: Task) => t.status === 'review').length, + tooltip: 'Images generated, awaiting approval. Review and approve to publish.', }, { - label: 'Completed', + label: 'Approved', value: 0, accentColor: 'green' as const, - calculate: (data) => data.tasks.filter((t: Task) => t.status === 'completed').length, - tooltip: 'Successfully completed tasks. Generated content is ready for review and publishing.', + calculate: (data) => data.tasks.filter((t: Task) => t.status === 'approved').length, + tooltip: 'Approved content awaiting publishing. Publish to site when ready.', }, { - label: 'Failed', + label: 'Published', value: 0, - accentColor: 'red' as const, - calculate: (data) => data.tasks.filter((t: Task) => t.status === 'failed').length, - tooltip: 'Failed tasks that need attention. Review error logs and retry or modify the task.', + accentColor: 'green' as const, + calculate: (data) => data.tasks.filter((t: Task) => t.status === 'published').length, + tooltip: 'Live content on your website. Successfully published and accessible.', + }, + { + label: 'Total Images', + value: 0, + accentColor: 'blue' as const, + calculate: (data) => 0, + tooltip: 'Total images generated across all content. Tracks visual asset coverage.', }, ], }; diff --git a/frontend/src/hooks/useModuleStats.ts b/frontend/src/hooks/useModuleStats.ts index 258c08db..9b170f23 100644 --- a/frontend/src/hooks/useModuleStats.ts +++ b/frontend/src/hooks/useModuleStats.ts @@ -165,7 +165,7 @@ export function useModuleStats() { // Total images fetchImages({ ...baseFilters }), // Credits usage from billing summary - fetchAPI('/v1/billing/credits/usage/summary/').catch(() => ({ + fetchAPI(`/v1/billing/credits/usage/summary/?site_id=${activeSite.id}${activeSector?.id ? `§or_id=${activeSector.id}` : ''}`).catch(() => ({ data: { by_operation: {} } })), ]); diff --git a/frontend/src/pages/Writer/Approved.tsx b/frontend/src/pages/Writer/Approved.tsx index 960748c4..53e5a9f0 100644 --- a/frontend/src/pages/Writer/Approved.tsx +++ b/frontend/src/pages/Writer/Approved.tsx @@ -22,6 +22,7 @@ import { FileIcon, CheckCircleIcon, BoltIcon } from '../../icons'; import { RocketLaunchIcon } from '@heroicons/react/24/outline'; import { createApprovedPageConfig } from '../../config/pages/approved.config'; import { useSectorStore } from '../../store/sectorStore'; +import { useSiteStore } from '../../store/siteStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import PageHeader from '../../components/common/PageHeader'; import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeWidgetFooter'; @@ -30,6 +31,7 @@ export default function Approved() { const toast = useToast(); const navigate = useNavigate(); const { activeSector } = useSectorStore(); + const { activeSite } = useSiteStore(); const { pageSize } = usePageSizeStore(); // Data state @@ -37,8 +39,11 @@ export default function Approved() { const [loading, setLoading] = useState(true); // Total counts for footer widget and header metrics (not page-filtered) - const [totalOnSite, setTotalOnSite] = useState(0); - const [totalPendingPublish, setTotalPendingPublish] = useState(0); + const [totalContent, setTotalContent] = useState(0); + const [totalDraft, setTotalDraft] = useState(0); + const [totalReview, setTotalReview] = useState(0); + const [totalApproved, setTotalApproved] = useState(0); + const [totalPublished, setTotalPublished] = useState(0); const [totalImagesCount, setTotalImagesCount] = useState(0); // Filter state - default to approved status @@ -59,27 +64,26 @@ export default function Approved() { // Load total metrics for footer widget and header metrics (not affected by pagination) const loadTotalMetrics = useCallback(async () => { try { - // Fetch all approved+published content to calculate totals - const data = await fetchContent({ - status__in: 'approved,published', // Both approved and published content - page_size: 1000, // Fetch enough to count - }); - - const allContent = data.results || []; - // Count by external_id presence - const onSite = allContent.filter(c => c.external_id).length; - const pending = allContent.filter(c => !c.external_id).length; - - setTotalOnSite(onSite); - setTotalPendingPublish(pending); - - // Get actual total images count - const imagesRes = await fetchImages({ page_size: 1 }); + // Fetch counts in parallel for performance + const [allRes, draftRes, reviewRes, approvedRes, publishedRes, imagesRes] = await Promise.all([ + fetchContent({ page_size: 1, site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'draft', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'review', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'approved', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'published', site_id: activeSite?.id }), + fetchImages({ page_size: 1, site_id: activeSite?.id }), + ]); + + setTotalContent(allRes.count || 0); + setTotalDraft(draftRes.count || 0); + setTotalReview(reviewRes.count || 0); + setTotalApproved(approvedRes.count || 0); + setTotalPublished(publishedRes.count || 0); setTotalImagesCount(imagesRes.count || 0); } catch (error) { console.error('Error loading total metrics:', error); } - }, []); + }, [activeSite]); // Load total metrics on mount useEffect(() => { @@ -342,16 +346,23 @@ export default function Approved() { let value: number; switch (metric.label) { + case 'Content': + value = totalContent || 0; + break; + case 'Draft': + value = totalDraft; + break; + case 'In Review': + value = totalReview; + break; case 'Approved': - value = totalCount || 0; + value = totalApproved; break; - case 'On Site': - // Use totalOnSite from loadTotalMetrics() - value = totalOnSite; + case 'Published': + value = totalPublished; break; - case 'Pending': - // Use totalPendingPublish from loadTotalMetrics() - value = totalPendingPublish; + case 'Total Images': + value = totalImagesCount; break; default: value = metric.calculate({ content, totalCount }); @@ -361,9 +372,10 @@ export default function Approved() { label: metric.label, value, accentColor: metric.accentColor, + tooltip: (metric as any).tooltip, }; }); - }, [pageConfig?.headerMetrics, content, totalCount, totalOnSite, totalPendingPublish]); + }, [pageConfig?.headerMetrics, content, totalCount, totalContent, totalDraft, totalReview, totalApproved, totalPublished, totalImagesCount]); return ( <> diff --git a/frontend/src/pages/Writer/Content.tsx b/frontend/src/pages/Writer/Content.tsx index 0591f360..3701a0cb 100644 --- a/frontend/src/pages/Writer/Content.tsx +++ b/frontend/src/pages/Writer/Content.tsx @@ -39,8 +39,10 @@ export default function Content() { const [loading, setLoading] = useState(true); // Total counts for footer widget and header metrics (not page-filtered) + const [totalContent, setTotalContent] = useState(0); const [totalDraft, setTotalDraft] = useState(0); const [totalReview, setTotalReview] = useState(0); + const [totalApproved, setTotalApproved] = useState(0); const [totalPublished, setTotalPublished] = useState(0); const [totalImagesCount, setTotalImagesCount] = useState(0); @@ -68,7 +70,7 @@ export default function Content() { const loadTotalMetrics = useCallback(async () => { try { // Batch all API calls in parallel for better performance - const [allRes, draftRes, reviewRes, publishedRes, imagesRes] = await Promise.all([ + const [allRes, draftRes, reviewRes, approvedRes, publishedRes, imagesRes] = await Promise.all([ // Get all content (site-wide) fetchContent({ page_size: 1, @@ -86,19 +88,26 @@ export default function Content() { site_id: activeSite?.id, status: 'review', }), - // Get content with status='approved' or 'published' (ready for publishing or on site) + // Get content with status='approved' fetchContent({ page_size: 1, site_id: activeSite?.id, - status__in: 'approved,published', + status: 'approved', + }), + // Get content with status='published' + fetchContent({ + page_size: 1, + site_id: activeSite?.id, + status: 'published', }), // Get actual total images count - fetchImages({ page_size: 1 }), + fetchImages({ page_size: 1, site_id: activeSite?.id }), ]); - setTotalCount(allRes.count || 0); + setTotalContent(allRes.count || 0); setTotalDraft(draftRes.count || 0); setTotalReview(reviewRes.count || 0); + setTotalApproved(approvedRes.count || 0); setTotalPublished(publishedRes.count || 0); setTotalImagesCount(imagesRes.count || 0); } catch (error) { @@ -226,20 +235,23 @@ export default function Content() { switch (metric.label) { case 'Content': - value = totalCount || 0; + value = totalContent || 0; break; case 'Draft': - // Use totalDraft from loadTotalMetrics() value = totalDraft; break; case 'In Review': - // Use totalReview from loadTotalMetrics() value = totalReview; break; + case 'Approved': + value = totalApproved; + break; case 'Published': - // Use totalPublished from loadTotalMetrics() value = totalPublished; break; + case 'Total Images': + value = totalImagesCount; + break; default: value = metric.calculate({ content, totalCount }); } @@ -251,7 +263,7 @@ export default function Content() { tooltip: (metric as any).tooltip, }; }); - }, [pageConfig?.headerMetrics, content, totalCount, totalDraft, totalReview, totalPublished]); + }, [pageConfig?.headerMetrics, content, totalContent, totalDraft, totalReview, totalApproved, totalPublished, totalImagesCount]); const handleRowAction = useCallback(async (action: string, row: ContentType) => { if (action === 'view_on_wordpress') { diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx index fd00b009..45e50929 100644 --- a/frontend/src/pages/Writer/Images.tsx +++ b/frontend/src/pages/Writer/Images.tsx @@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate'; import { fetchContentImages, fetchImages, + fetchContent, ContentImagesGroup, ContentImagesResponse, fetchImageGenerationSettings, @@ -28,19 +29,28 @@ import SingleRecordStatusUpdateModal from '../../components/common/SingleRecordS import PageHeader from '../../components/common/PageHeader'; import { Modal } from '../../components/ui/modal'; import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeWidgetFooter'; +import { useSiteStore } from '../../store/siteStore'; export default function Images() { const toast = useToast(); + const { activeSite } = useSiteStore(); // Data state const [images, setImages] = useState([]); const [loading, setLoading] = useState(true); // Total counts for footer widget and header metrics (not page-filtered) + const [totalContent, setTotalContent] = useState(0); + const [totalDraft, setTotalDraft] = useState(0); + const [totalReview, setTotalReview] = useState(0); + const [totalApproved, setTotalApproved] = useState(0); + const [totalPublished, setTotalPublished] = useState(0); + const [totalImagesCount, setTotalImagesCount] = useState(0); + + // Footer widget specific counts (image-based) const [totalComplete, setTotalComplete] = useState(0); const [totalPartial, setTotalPartial] = useState(0); const [totalPending, setTotalPending] = useState(0); - const [totalImagesCount, setTotalImagesCount] = useState(0); // Actual images count // Filter state const [searchTerm, setSearchTerm] = useState(''); @@ -79,40 +89,26 @@ export default function Images() { // Load total metrics for footer widget and header metrics (not affected by pagination) const loadTotalMetrics = useCallback(async () => { try { - // Fetch content-grouped images for status counts - const data: ContentImagesResponse = await fetchContentImages({}); - const allImages = data.results || []; - - // Count by overall_status (content-level status) - let complete = 0; - let partial = 0; - let pending = 0; - - allImages.forEach(img => { - switch (img.overall_status) { - case 'complete': - complete++; - break; - case 'partial': - partial++; - break; - case 'pending': - pending++; - break; - } - }); - - setTotalComplete(complete); - setTotalPartial(partial); - setTotalPending(pending); - - // Fetch ACTUAL total images count from the images endpoint - const imagesData = await fetchImages({ page_size: 1 }); - setTotalImagesCount(imagesData.count || 0); + // Fetch counts in parallel for performance + const [allRes, draftRes, reviewRes, approvedRes, publishedRes, imagesRes] = await Promise.all([ + fetchContent({ page_size: 1, site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'draft', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'review', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'approved', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'published', site_id: activeSite?.id }), + fetchImages({ page_size: 1, site_id: activeSite?.id }), + ]); + + setTotalContent(allRes.count || 0); + setTotalDraft(draftRes.count || 0); + setTotalReview(reviewRes.count || 0); + setTotalApproved(approvedRes.count || 0); + setTotalPublished(publishedRes.count || 0); + setTotalImagesCount(imagesRes.count || 0); } catch (error) { console.error('Error loading total metrics:', error); } - }, []); + }, [activeSite]); // Load total metrics on mount useEffect(() => { @@ -502,19 +498,22 @@ export default function Images() { switch (metric.label) { case 'Content': - value = totalCount || 0; + value = totalContent || 0; break; - case 'Complete': - // Use totalComplete from loadTotalMetrics() - value = totalComplete; + case 'Draft': + value = totalDraft; break; - case 'Partial': - // Use totalPartial from loadTotalMetrics() - value = totalPartial; + case 'In Review': + value = totalReview; break; - case 'Pending': - // Use totalPending from loadTotalMetrics() - value = totalPending; + case 'Approved': + value = totalApproved; + break; + case 'Published': + value = totalPublished; + break; + case 'Total Images': + value = totalImagesCount; break; default: value = metric.calculate({ images, totalCount }); @@ -528,16 +527,8 @@ export default function Images() { }; }); - // Add total images count metric - baseMetrics.push({ - label: 'Total Images', - value: totalImagesCount, - accentColor: 'purple' as const, - tooltip: 'Total number of images across all content', - }); - return baseMetrics; - }, [pageConfig?.headerMetrics, images, totalCount, totalComplete, totalPartial, totalPending, totalImagesCount]); + }, [pageConfig?.headerMetrics, images, totalCount, totalContent, totalDraft, totalReview, totalApproved, totalPublished, totalImagesCount]); return ( <> diff --git a/frontend/src/pages/Writer/Review.tsx b/frontend/src/pages/Writer/Review.tsx index 0b67f9a4..2fbf5a56 100644 --- a/frontend/src/pages/Writer/Review.tsx +++ b/frontend/src/pages/Writer/Review.tsx @@ -19,6 +19,7 @@ import { CheckCircleIcon } from '../../icons'; import { ClipboardDocumentCheckIcon } from '@heroicons/react/24/outline'; import { createReviewPageConfig } from '../../config/pages/review.config'; import { useSectorStore } from '../../store/sectorStore'; +import { useSiteStore } from '../../store/siteStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import PageHeader from '../../components/common/PageHeader'; import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeWidgetFooter'; @@ -27,17 +28,20 @@ export default function Review() { const toast = useToast(); const navigate = useNavigate(); const { activeSector } = useSectorStore(); + const { activeSite } = useSiteStore(); const { pageSize } = usePageSizeStore(); // Data state const [content, setContent] = useState([]); const [loading, setLoading] = useState(true); - const [totalImagesCount, setTotalImagesCount] = useState(0); - // Total metrics for footer widget (not page-filtered) - const [totalDrafts, setTotalDrafts] = useState(0); + // Total metrics for footer widget and header metrics (not page-filtered) + const [totalContent, setTotalContent] = useState(0); + const [totalDraft, setTotalDraft] = useState(0); + const [totalReview, setTotalReview] = useState(0); const [totalApproved, setTotalApproved] = useState(0); - const [totalTasks, setTotalTasks] = useState(0); + const [totalPublished, setTotalPublished] = useState(0); + const [totalImagesCount, setTotalImagesCount] = useState(0); // Filter state - default to review status const [searchTerm, setSearchTerm] = useState(''); @@ -94,21 +98,25 @@ export default function Review() { const loadTotalMetrics = useCallback(async () => { try { // Fetch counts in parallel for performance - const [imagesRes, draftsRes, approvedRes, tasksRes] = await Promise.all([ - fetchImages({ page_size: 1 }), - fetchContent({ page_size: 1, status: 'draft' }), - fetchContent({ page_size: 1, status: 'approved' }), - fetchAPI<{ count: number }>('/writer/tasks/?page_size=1'), + const [allRes, draftRes, reviewRes, approvedRes, publishedRes, imagesRes] = await Promise.all([ + fetchContent({ page_size: 1, site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'draft', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'review', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'approved', site_id: activeSite?.id }), + fetchContent({ page_size: 1, status: 'published', site_id: activeSite?.id }), + fetchImages({ page_size: 1, site_id: activeSite?.id }), ]); - setTotalImagesCount(imagesRes.count || 0); - setTotalDrafts(draftsRes.count || 0); + setTotalContent(allRes.count || 0); + setTotalDraft(draftRes.count || 0); + setTotalReview(reviewRes.count || 0); setTotalApproved(approvedRes.count || 0); - setTotalTasks(tasksRes.count || 0); + setTotalPublished(publishedRes.count || 0); + setTotalImagesCount(imagesRes.count || 0); } catch (error) { console.error('Error loading metrics:', error); } - }, []); + }, [activeSite]); useEffect(() => { loadTotalMetrics(); @@ -161,12 +169,37 @@ export default function Review() { // Header metrics (calculated from loaded data) const headerMetrics = useMemo(() => - pageConfig.headerMetrics.map(metric => ({ - ...metric, - value: metric.calculate({ content, totalCount }), - tooltip: (metric as any).tooltip, - })), - [pageConfig.headerMetrics, content, totalCount] + pageConfig.headerMetrics.map(metric => { + let value: number; + switch (metric.label) { + case 'Content': + value = totalContent || 0; + break; + case 'Draft': + value = totalDraft; + break; + case 'In Review': + value = totalReview; + break; + case 'Approved': + value = totalApproved; + break; + case 'Published': + value = totalPublished; + break; + case 'Total Images': + value = totalImagesCount; + break; + default: + value = metric.calculate({ content, totalCount }); + } + return { + ...metric, + value, + tooltip: (metric as any).tooltip, + }; + }), + [pageConfig.headerMetrics, content, totalCount, totalContent, totalDraft, totalReview, totalApproved, totalPublished, totalImagesCount] ); // Export handler diff --git a/frontend/src/pages/Writer/Tasks.tsx b/frontend/src/pages/Writer/Tasks.tsx index f2522e67..5816a6e3 100644 --- a/frontend/src/pages/Writer/Tasks.tsx +++ b/frontend/src/pages/Writer/Tasks.tsx @@ -9,6 +9,7 @@ import TablePageTemplate from '../../templates/TablePageTemplate'; import { fetchTasks, fetchImages, + fetchContent, createTask, updateTask, deleteTask, @@ -47,11 +48,17 @@ export default function Tasks() { const [loading, setLoading] = useState(true); // Total counts for footer widget and header metrics (not page-filtered) + const [totalContent, setTotalContent] = useState(0); + const [totalDraft, setTotalDraft] = useState(0); + const [totalReview, setTotalReview] = useState(0); + const [totalApproved, setTotalApproved] = useState(0); + const [totalPublished, setTotalPublished] = useState(0); + const [totalImagesCount, setTotalImagesCount] = useState(0); + + // Footer widget specific counts (task-based) const [totalQueued, setTotalQueued] = useState(0); const [totalProcessing, setTotalProcessing] = useState(0); const [totalCompleted, setTotalCompleted] = useState(0); - const [totalFailed, setTotalFailed] = useState(0); - const [totalImagesCount, setTotalImagesCount] = useState(0); // Filter state const [searchTerm, setSearchTerm] = useState(''); @@ -111,45 +118,45 @@ export default function Tasks() { const loadTotalMetrics = useCallback(async () => { try { // Batch all API calls in parallel for better performance - const [allRes, queuedRes, processingRes, completedRes, failedRes, imagesRes] = await Promise.all([ - // Get all tasks (site-wide) - fetchTasks({ + const [allRes, draftRes, reviewRes, approvedRes, publishedRes, imagesRes] = await Promise.all([ + // Get all content (site-wide) + fetchContent({ page_size: 1, site_id: activeSite?.id, }), - // Get tasks with status='queued' - fetchTasks({ + // Get content with status='draft' + fetchContent({ page_size: 1, site_id: activeSite?.id, - status: 'queued', + status: 'draft', }), - // Get tasks with status='in_progress' - fetchTasks({ + // Get content with status='review' + fetchContent({ page_size: 1, site_id: activeSite?.id, - status: 'in_progress', + status: 'review', }), - // Get tasks with status='completed' - fetchTasks({ + // Get content with status='approved' + fetchContent({ page_size: 1, site_id: activeSite?.id, - status: 'completed', + status: 'approved', }), - // Get tasks with status='failed' - fetchTasks({ + // Get content with status='published' + fetchContent({ page_size: 1, site_id: activeSite?.id, - status: 'failed', + status: 'published', }), // Get actual total images count - fetchImages({ page_size: 1 }), + fetchImages({ page_size: 1, site_id: activeSite?.id }), ]); - setTotalCount(allRes.count || 0); - setTotalQueued(queuedRes.count || 0); - setTotalProcessing(processingRes.count || 0); - setTotalCompleted(completedRes.count || 0); - setTotalFailed(failedRes.count || 0); + setTotalContent(allRes.count || 0); + setTotalDraft(draftRes.count || 0); + setTotalReview(reviewRes.count || 0); + setTotalApproved(approvedRes.count || 0); + setTotalPublished(publishedRes.count || 0); setTotalImagesCount(imagesRes.count || 0); } catch (error) { console.error('Error loading total metrics:', error); @@ -384,24 +391,23 @@ export default function Tasks() { let value: number; switch (metric.label) { - case 'Tasks': - value = totalCount || 0; + case 'Content': + value = totalContent || 0; break; - case 'In Queue': - // Use totalQueued from loadTotalMetrics() - value = totalQueued; + case 'Draft': + value = totalDraft; break; - case 'Processing': - // Use totalProcessing from loadTotalMetrics() - value = totalProcessing; + case 'In Review': + value = totalReview; break; - case 'Completed': - // Use totalCompleted from loadTotalMetrics() - value = totalCompleted; + case 'Approved': + value = totalApproved; break; - case 'Failed': - // Use totalFailed from loadTotalMetrics() - value = totalFailed; + case 'Published': + value = totalPublished; + break; + case 'Total Images': + value = totalImagesCount; break; default: value = metric.calculate({ tasks, totalCount }); @@ -414,7 +420,7 @@ export default function Tasks() { tooltip: (metric as any).tooltip, }; }); - }, [pageConfig?.headerMetrics, tasks, totalCount, totalQueued, totalProcessing, totalCompleted, totalFailed]); + }, [pageConfig?.headerMetrics, tasks, totalContent, totalDraft, totalReview, totalApproved, totalPublished, totalImagesCount]); const resetForm = useCallback(() => { setFormData({ diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 6dc16cd5..989cf402 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1425,6 +1425,7 @@ export interface ImageListResponse { export interface ImageFilters { content_id?: number; task_id?: number; + site_id?: number; image_type?: string; status?: string; ordering?: string; @@ -1502,6 +1503,7 @@ export async function fetchImages(filters: ImageFilters = {}): Promise