header rekated fixes
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchClusters,
|
||||
@@ -390,20 +389,8 @@ export default function Clusters() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Clusters"
|
||||
description="Group keywords into topic clusters"
|
||||
badge={{ icon: <GroupIcon />, color: 'purple' }}
|
||||
breadcrumb="Planner"
|
||||
actions={
|
||||
<Link
|
||||
to="/planner/ideas"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
Generate Ideas
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Planner"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -418,6 +405,15 @@ export default function Clusters() {
|
||||
volumeMin: volumeMin,
|
||||
volumeMax: volumeMax,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Generate Ideas',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleBulkAction('generate_ideas', selectedIds),
|
||||
} : clusters.length > 0 ? {
|
||||
label: 'Generate Ideas',
|
||||
href: '/planner/ideas',
|
||||
message: `${clusters.length} clusters`,
|
||||
} : undefined}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
if (key === 'search') {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchContentIdeas,
|
||||
@@ -301,20 +300,8 @@ export default function Ideas() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Ideas"
|
||||
description="Content ideas generated from keywords"
|
||||
badge={{ icon: <LightBulbIcon />, color: 'yellow' }}
|
||||
breadcrumb="Planner"
|
||||
actions={
|
||||
<Link
|
||||
to="/writer/queue"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
Start Writing
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Planner"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -329,6 +316,15 @@ export default function Ideas() {
|
||||
content_structure: structureFilter,
|
||||
content_type: typeFilter,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Queue to Writer',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleBulkAction('queue_to_writer', selectedIds),
|
||||
} : ideas.filter(i => i.status === 'approved').length > 0 ? {
|
||||
label: 'Start Writing',
|
||||
href: '/writer/queue',
|
||||
message: `${ideas.filter(i => i.status === 'approved').length} approved`,
|
||||
} : undefined}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
if (key === 'search') {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchKeywords,
|
||||
@@ -507,20 +506,6 @@ export default function Keywords() {
|
||||
};
|
||||
}, [keywords, totalCount]);
|
||||
|
||||
// Determine next step action
|
||||
const nextStep = useMemo(() => {
|
||||
if (totalCount === 0) {
|
||||
return { label: 'Import Keywords', path: '/add-keywords', disabled: false };
|
||||
}
|
||||
if (workflowStats.unclustered >= 5) {
|
||||
return { label: 'Auto-Cluster', action: 'cluster', disabled: false };
|
||||
}
|
||||
if (workflowStats.clustered > 0) {
|
||||
return { label: 'Generate Ideas', path: '/planner/ideas', disabled: false };
|
||||
}
|
||||
return { label: 'Add More Keywords', path: '/add-keywords', disabled: false };
|
||||
}, [totalCount, workflowStats]);
|
||||
|
||||
// Handle create/edit
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
@@ -594,37 +579,8 @@ export default function Keywords() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Keywords"
|
||||
description="Your target search terms organized for content creation"
|
||||
badge={{ icon: <ListIcon />, color: 'green' }}
|
||||
breadcrumb="Planner"
|
||||
actions={
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 hidden md:block">
|
||||
{workflowStats.clustered}/{workflowStats.total} clustered
|
||||
</span>
|
||||
{nextStep.path ? (
|
||||
<Link
|
||||
to={nextStep.path}
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
{nextStep.label}
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
) : nextStep.action === 'cluster' ? (
|
||||
<button
|
||||
onClick={handleAutoCluster}
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
{nextStep.label}
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
parent="Planner"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -641,6 +597,19 @@ export default function Keywords() {
|
||||
volumeMin: volumeMin,
|
||||
volumeMax: volumeMax,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Auto-Cluster Selected',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: handleAutoCluster,
|
||||
} : workflowStats.unclustered >= 5 ? {
|
||||
label: 'Auto-Cluster All',
|
||||
message: `${workflowStats.unclustered} unclustered`,
|
||||
onClick: handleAutoCluster,
|
||||
} : workflowStats.clustered > 0 ? {
|
||||
label: 'Generate Ideas',
|
||||
href: '/planner/ideas',
|
||||
message: `${workflowStats.clustered} clustered`,
|
||||
} : undefined}
|
||||
onFilterChange={(key, value) => {
|
||||
// Normalize value to string, preserving empty strings
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
bulkDeleteContent,
|
||||
} from '../../services/api';
|
||||
import { optimizerApi } from '../../api/optimizer.api';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { FileIcon, TaskIcon, CheckCircleIcon } from '../../icons';
|
||||
import { createContentPageConfig } from '../../config/pages/content.config';
|
||||
@@ -228,20 +228,8 @@ export default function Content() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Drafts"
|
||||
description="Manage content drafts"
|
||||
badge={{ icon: <PencilSquareIcon />, color: 'orange' }}
|
||||
breadcrumb="Writer"
|
||||
actions={
|
||||
<Link
|
||||
to="/writer/images"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
Generate Images
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Writer"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -254,6 +242,15 @@ export default function Content() {
|
||||
status: statusFilter,
|
||||
source: sourceFilter,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Generate Images',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleRowAction('generate_images', { id: selectedIds[0] }),
|
||||
} : content.filter(c => c.status === 'draft').length > 0 ? {
|
||||
label: 'Generate Images',
|
||||
href: '/writer/images',
|
||||
message: `${content.filter(c => c.status === 'draft').length} drafts`,
|
||||
} : undefined}
|
||||
onFilterChange={(key: string, value: any) => {
|
||||
if (key === 'search') {
|
||||
setSearchTerm(value);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchContentImages,
|
||||
@@ -452,20 +451,8 @@ export default function Images() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Images"
|
||||
description="Generate and manage content images"
|
||||
badge={{ icon: <PhotoIcon />, color: 'pink' }}
|
||||
breadcrumb="Writer"
|
||||
actions={
|
||||
<Link
|
||||
to="/writer/review"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
Review Content
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Writer"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -477,6 +464,15 @@ export default function Images() {
|
||||
search: searchTerm,
|
||||
status: statusFilter,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Generate Images',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleBulkAction('generate_images', selectedIds),
|
||||
} : images.filter(i => i.overall_status === 'ready').length > 0 ? {
|
||||
label: 'Review Content',
|
||||
href: '/writer/review',
|
||||
message: `${images.filter(i => i.overall_status === 'ready').length} ready`,
|
||||
} : undefined}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
if (key === 'search') {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchContent,
|
||||
@@ -309,20 +309,8 @@ export default function Published() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Published"
|
||||
description="Published content and WordPress sync status"
|
||||
badge={{ icon: <RocketLaunchIcon />, color: 'green' }}
|
||||
breadcrumb="Writer"
|
||||
actions={
|
||||
<Link
|
||||
to="/planner/keywords"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
Create More Content
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Writer"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -335,6 +323,15 @@ export default function Published() {
|
||||
status: statusFilter,
|
||||
publishStatus: publishStatusFilter,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Sync to WordPress',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleBulkAction('publish_to_wordpress', selectedIds),
|
||||
} : {
|
||||
label: 'Create More Content',
|
||||
href: '/planner/keywords',
|
||||
message: `${content.length} published`,
|
||||
}}
|
||||
onFilterChange={(key: string, value: any) => {
|
||||
if (key === 'search') {
|
||||
setSearchTerm(value);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchContent,
|
||||
@@ -348,20 +348,8 @@ export default function Review() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Review"
|
||||
description="Review and approve content before publishing"
|
||||
badge={{ icon: <ClipboardDocumentCheckIcon />, color: 'emerald' }}
|
||||
breadcrumb="Writer"
|
||||
actions={
|
||||
<Link
|
||||
to="/writer/published"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
View Published
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Writer"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -372,6 +360,15 @@ export default function Review() {
|
||||
filterValues={{
|
||||
search: searchTerm,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Publish Selected',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleBulkAction('publish', selectedIds),
|
||||
} : content.filter(c => c.status === 'review').length > 0 ? {
|
||||
label: 'View Published',
|
||||
href: '/writer/published',
|
||||
message: `${content.filter(c => c.status === 'review').length} in review`,
|
||||
} : undefined}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
if (key === 'search') {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchTasks,
|
||||
@@ -369,20 +368,8 @@ export default function Tasks() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="Queue"
|
||||
description="Content writing queue"
|
||||
badge={{ icon: <DocumentTextIcon />, color: 'blue' }}
|
||||
breadcrumb="Writer"
|
||||
actions={
|
||||
<Link
|
||||
to="/writer/content"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded-lg transition-colors"
|
||||
>
|
||||
View Drafts
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
}
|
||||
parent="Writer"
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
@@ -398,6 +385,15 @@ export default function Tasks() {
|
||||
content_type: typeFilter,
|
||||
source: sourceFilter,
|
||||
}}
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Generate Content',
|
||||
message: `${selectedIds.length} selected`,
|
||||
onClick: () => handleBulkAction('generate_content', selectedIds),
|
||||
} : tasks.filter(t => t.status === 'queued').length > 0 ? {
|
||||
label: 'View Drafts',
|
||||
href: '/writer/content',
|
||||
message: `${tasks.filter(t => t.status === 'queued').length} queued`,
|
||||
} : undefined}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
if (key === 'search') {
|
||||
|
||||
Reference in New Issue
Block a user