diff --git a/frontend/src/components/common/PageHeader.tsx b/frontend/src/components/common/PageHeader.tsx new file mode 100644 index 00000000..e63e5efe --- /dev/null +++ b/frontend/src/components/common/PageHeader.tsx @@ -0,0 +1,80 @@ +/** + * Standardized Page Header Component + * Used across all Planner and Writer module pages + * Includes: Page title, last updated, site/sector info, and selectors + */ +import { useSiteStore } from '../../store/siteStore'; +import { useSectorStore } from '../../store/sectorStore'; +import SiteAndSectorSelector from './SiteAndSectorSelector'; + +interface PageHeaderProps { + title: string; + lastUpdated?: Date; + showRefresh?: boolean; + onRefresh?: () => void; + className?: string; +} + +export default function PageHeader({ + title, + lastUpdated, + showRefresh = false, + onRefresh, + className = "", +}: PageHeaderProps) { + const { activeSite } = useSiteStore(); + const { activeSector } = useSectorStore(); + + return ( +
+
+

{title}

+
+ {lastUpdated && ( + <> +

+ Last updated: {lastUpdated.toLocaleTimeString()} +

+ + )} + {activeSite && ( + <> + {lastUpdated && } +

+ Site: {activeSite.name} +

+ + )} + {activeSector && ( + <> + +

+ Sector: {activeSector.name} +

+ + )} + {!activeSector && activeSite && ( + <> + +

+ Sector: All Sectors +

+ + )} +
+
+
+ + {showRefresh && onRefresh && ( + + )} +
+
+ ); +} + diff --git a/frontend/src/layout/AppHeader.tsx b/frontend/src/layout/AppHeader.tsx index 2835f658..1f79b199 100644 --- a/frontend/src/layout/AppHeader.tsx +++ b/frontend/src/layout/AppHeader.tsx @@ -6,7 +6,6 @@ import { ThemeToggleButton } from "../components/common/ThemeToggleButton"; import NotificationDropdown from "../components/header/NotificationDropdown"; import UserDropdown from "../components/header/UserDropdown"; import { HeaderMetrics } from "../components/header/HeaderMetrics"; -import SiteSwitcher from "../components/header/SiteSwitcher"; import ResourceDebugToggle from "../components/debug/ResourceDebugToggle"; const AppHeader: React.FC = () => { @@ -160,8 +159,6 @@ const AppHeader: React.FC = () => { } items-center justify-between w-full gap-4 px-5 py-4 lg:flex shadow-theme-md lg:justify-end lg:px-0 lg:shadow-none`} >
- {/* */} - {/* */} {/* */} diff --git a/frontend/src/pages/Planner/Clusters.tsx b/frontend/src/pages/Planner/Clusters.tsx index e3c8c9c5..6670ed2c 100644 --- a/frontend/src/pages/Planner/Clusters.tsx +++ b/frontend/src/pages/Planner/Clusters.tsx @@ -26,6 +26,7 @@ import { createClustersPageConfig } from '../../config/pages/clusters.config'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty'; +import PageHeader from '../../components/common/PageHeader'; export default function Clusters() { const toast = useToast(); @@ -384,6 +385,7 @@ export default function Clusters() { return ( <> + } diff --git a/frontend/src/pages/Planner/Dashboard.tsx b/frontend/src/pages/Planner/Dashboard.tsx index 95aa30fb..5f77be50 100644 --- a/frontend/src/pages/Planner/Dashboard.tsx +++ b/frontend/src/pages/Planner/Dashboard.tsx @@ -27,7 +27,7 @@ import { } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; -import SiteAndSectorSelector from "../../components/common/SiteAndSectorSelector"; +import PageHeader from "../../components/common/PageHeader"; interface DashboardStats { keywords: { @@ -468,49 +468,12 @@ export default function PlannerDashboard() {
{/* Header with site/sector info and controls */} -
-
-

Planner Dashboard

-
-

- Last updated: {lastUpdated.toLocaleTimeString()} -

- {activeSite && ( - <> - -

- Site: {activeSite.name} -

- - )} - {activeSector && ( - <> - -

- Sector: {activeSector.name} -

- - )} - {!activeSector && ( - <> - -

- Sector: All Sectors -

- - )} -
-
-
- - -
-
+ {/* Hero Section - Key Metric */}
diff --git a/frontend/src/pages/Planner/Ideas.tsx b/frontend/src/pages/Planner/Ideas.tsx index 302bb67e..c85813b4 100644 --- a/frontend/src/pages/Planner/Ideas.tsx +++ b/frontend/src/pages/Planner/Ideas.tsx @@ -27,6 +27,7 @@ import { BoltIcon, PlusIcon, DownloadIcon } from '../../icons'; import { createIdeasPageConfig } from '../../config/pages/ideas.config'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; +import PageHeader from '../../components/common/PageHeader'; export default function Ideas() { const toast = useToast(); @@ -294,6 +295,7 @@ export default function Ideas() { return ( <> + } diff --git a/frontend/src/pages/Planner/Keywords.tsx b/frontend/src/pages/Planner/Keywords.tsx index 4ab764a9..684000be 100644 --- a/frontend/src/pages/Planner/Keywords.tsx +++ b/frontend/src/pages/Planner/Keywords.tsx @@ -26,6 +26,7 @@ import { import { useSiteStore } from '../../store/siteStore'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; +import PageHeader from '../../components/common/PageHeader'; import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty'; import FormModal from '../../components/common/FormModal'; import ProgressModal from '../../components/common/ProgressModal'; @@ -751,6 +752,7 @@ export default function Keywords() { return ( <> + } diff --git a/frontend/src/pages/Planner/Mapping.tsx b/frontend/src/pages/Planner/Mapping.tsx index 5cc3f7e2..33e62001 100644 --- a/frontend/src/pages/Planner/Mapping.tsx +++ b/frontend/src/pages/Planner/Mapping.tsx @@ -1,11 +1,12 @@ import PageMeta from "../../components/common/PageMeta"; import ComponentCard from "../../components/common/ComponentCard"; +import PageHeader from "../../components/common/PageHeader"; export default function Mapping() { return ( <> - +

diff --git a/frontend/src/pages/Writer/Content.tsx b/frontend/src/pages/Writer/Content.tsx index 31ecb110..896f6e4a 100644 --- a/frontend/src/pages/Writer/Content.tsx +++ b/frontend/src/pages/Writer/Content.tsx @@ -18,6 +18,7 @@ import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import ProgressModal from '../../components/common/ProgressModal'; import { useProgressModal } from '../../hooks/useProgressModal'; +import PageHeader from '../../components/common/PageHeader'; export default function Content() { const toast = useToast(); @@ -180,6 +181,7 @@ export default function Content() { return ( <> + } diff --git a/frontend/src/pages/Writer/Dashboard.tsx b/frontend/src/pages/Writer/Dashboard.tsx index 8ab43b6c..613ee699 100644 --- a/frontend/src/pages/Writer/Dashboard.tsx +++ b/frontend/src/pages/Writer/Dashboard.tsx @@ -26,7 +26,7 @@ import { } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; -import SiteAndSectorSelector from "../../components/common/SiteAndSectorSelector"; +import PageHeader from "../../components/common/PageHeader"; interface WriterStats { tasks: { @@ -547,49 +547,12 @@ export default function WriterDashboard() {

{/* Header with site/sector info and controls */} -
-
-

Writer Dashboard

-
-

- Last updated: {lastUpdated.toLocaleTimeString()} -

- {activeSite && ( - <> - -

- Site: {activeSite.name} -

- - )} - {activeSector && ( - <> - -

- Sector: {activeSector.name} -

- - )} - {!activeSector && ( - <> - -

- Sector: All Sectors -

- - )} -
-
-
- - -
-
+ {/* Hero Section - Key Metric */}
diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx index 37f44949..82cacda2 100644 --- a/frontend/src/pages/Writer/Images.tsx +++ b/frontend/src/pages/Writer/Images.tsx @@ -19,6 +19,7 @@ import { createImagesPageConfig } from '../../config/pages/images.config'; import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQueueModal'; import SingleRecordStatusUpdateModal from '../../components/common/SingleRecordStatusUpdateModal'; import { useResourceDebug } from '../../hooks/useResourceDebug'; +import PageHeader from '../../components/common/PageHeader'; export default function Images() { const toast = useToast(); @@ -89,7 +90,7 @@ export default function Images() { setLoading(true); setShowContent(false); try { - const data: ContentImagesResponse = await fetchContentImages(); + const data: ContentImagesResponse = await fetchContentImages({}); let filteredResults = data.results || []; // Client-side search filter @@ -384,8 +385,9 @@ export default function Images() { return ( <> - + } subtitle="Manage images for content articles" columns={pageConfig.columns} diff --git a/frontend/src/pages/Writer/Tasks.tsx b/frontend/src/pages/Writer/Tasks.tsx index 93663525..f8d2fc4a 100644 --- a/frontend/src/pages/Writer/Tasks.tsx +++ b/frontend/src/pages/Writer/Tasks.tsx @@ -34,6 +34,7 @@ import { KanbanBoard, TaskList, Task as KanbanTask } from '../../components/task import WorkflowPipeline, { WorkflowStep } from '../../components/dashboard/WorkflowPipeline'; import ComponentCard from '../../components/common/ComponentCard'; import { useNavigate } from 'react-router'; +import PageHeader from '../../components/common/PageHeader'; export default function Tasks() { const navigate = useNavigate(); @@ -698,16 +699,7 @@ export default function Tasks() { {/* View Toggle - Only show for Kanban/List views */} {currentView !== 'table' && ( -
-
-

- - Tasks -

-

- Manage content generation queue and tasks -

-
+
)} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index ad03ee42..78503767 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1062,8 +1062,35 @@ export interface ImageFilters { page_size?: number; } -export async function fetchContentImages(): Promise { - return fetchAPI('/v1/writer/images/content_images/'); +export interface ContentImagesFilters { + site_id?: number; + sector_id?: number; +} + +export async function fetchContentImages(filters: ContentImagesFilters = {}): Promise { + const params = new URLSearchParams(); + + // Automatically add active site filter if not explicitly provided + if (!filters.site_id) { + const activeSiteId = getActiveSiteId(); + if (activeSiteId) { + filters.site_id = activeSiteId; + } + } + + // Automatically add active sector filter if not explicitly provided + if (filters.sector_id === undefined) { + const activeSectorId = getActiveSectorId(); + if (activeSectorId !== null && activeSectorId !== undefined) { + filters.sector_id = activeSectorId; + } + } + + if (filters.site_id) params.append('site_id', filters.site_id.toString()); + if (filters.sector_id) params.append('sector_id', filters.sector_id.toString()); + + const queryString = params.toString(); + return fetchAPI(`/v1/writer/images/content_images/${queryString ? `?${queryString}` : ''}`); } export async function bulkUpdateImagesStatus(contentId: number, status: string): Promise<{ updated_count: number }> {