Filters and badges
This commit is contained in:
@@ -7,7 +7,9 @@ import { Content } from '../../services/api';
|
|||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { CheckCircleIcon, ArrowRightIcon } from '../../icons';
|
import { CheckCircleIcon, ArrowRightIcon } from '../../icons';
|
||||||
import { STRUCTURE_LABELS, TYPE_LABELS, CONTENT_TYPE_OPTIONS, ALL_CONTENT_STRUCTURES } from '../structureMapping';
|
import { TYPE_LABELS, CONTENT_TYPE_OPTIONS, ALL_CONTENT_STRUCTURES } from '../structureMapping';
|
||||||
|
import { getSectorBadgeColor, getStructureBadgeColor, getStructureLabel } from '../../utils/badgeColors';
|
||||||
|
import type { Sector } from '../../store/sectorStore';
|
||||||
|
|
||||||
export interface ColumnConfig {
|
export interface ColumnConfig {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -54,7 +56,12 @@ export function createApprovedPageConfig(params: {
|
|||||||
setSiteStatusFilter: (value: string) => void;
|
setSiteStatusFilter: (value: string) => void;
|
||||||
setCurrentPage: (page: number) => void;
|
setCurrentPage: (page: number) => void;
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Sector[];
|
||||||
onRowClick?: (row: Content) => void;
|
onRowClick?: (row: Content) => void;
|
||||||
|
statusOptions?: Array<{ value: string; label: string }>;
|
||||||
|
siteStatusOptions?: Array<{ value: string; label: string }>;
|
||||||
|
contentTypeOptions?: Array<{ value: string; label: string }>;
|
||||||
|
contentStructureOptions?: Array<{ value: string; label: string }>;
|
||||||
}): ApprovedPageConfig {
|
}): ApprovedPageConfig {
|
||||||
const showSectorColumn = !params.activeSector;
|
const showSectorColumn = !params.activeSector;
|
||||||
|
|
||||||
@@ -230,12 +237,12 @@ export function createApprovedPageConfig(params: {
|
|||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
width: '130px',
|
width: '130px',
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = STRUCTURE_LABELS[value] || value || '-';
|
const properCase = getStructureLabel(value)
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
.split(/[_\s]+/)
|
||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
).join(' ');
|
.join(' ');
|
||||||
return (
|
return (
|
||||||
<Badge color="purple" size="xs" variant="soft">
|
<Badge color={getStructureBadgeColor(value)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{properCase}</span>
|
<span className="text-[11px] font-normal">{properCase}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@@ -336,6 +343,23 @@ export function createApprovedPageConfig(params: {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (showSectorColumn) {
|
||||||
|
columns.splice(2, 0, {
|
||||||
|
key: 'sector_name',
|
||||||
|
label: 'Sector',
|
||||||
|
sortable: false,
|
||||||
|
width: '120px',
|
||||||
|
render: (value: string, row: Content) => {
|
||||||
|
const color = getSectorBadgeColor(row.sector_id, row.sector_name, params.sectors);
|
||||||
|
return (
|
||||||
|
<Badge color={color} size="xs" variant="soft">
|
||||||
|
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const filters: FilterConfig[] = [
|
const filters: FilterConfig[] = [
|
||||||
{
|
{
|
||||||
key: 'search',
|
key: 'search',
|
||||||
@@ -347,7 +371,7 @@ export function createApprovedPageConfig(params: {
|
|||||||
key: 'status',
|
key: 'status',
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.statusOptions || [
|
||||||
{ value: '', label: 'All' },
|
{ value: '', label: 'All' },
|
||||||
{ value: 'draft', label: 'Draft' },
|
{ value: 'draft', label: 'Draft' },
|
||||||
{ value: 'review', label: 'Review' },
|
{ value: 'review', label: 'Review' },
|
||||||
@@ -359,7 +383,7 @@ export function createApprovedPageConfig(params: {
|
|||||||
key: 'site_status',
|
key: 'site_status',
|
||||||
label: 'Site Status',
|
label: 'Site Status',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.siteStatusOptions || [
|
||||||
{ value: '', label: 'All' },
|
{ value: '', label: 'All' },
|
||||||
{ value: 'not_published', label: 'Not Published' },
|
{ value: 'not_published', label: 'Not Published' },
|
||||||
{ value: 'scheduled', label: 'Scheduled' },
|
{ value: 'scheduled', label: 'Scheduled' },
|
||||||
@@ -372,7 +396,7 @@ export function createApprovedPageConfig(params: {
|
|||||||
key: 'content_type',
|
key: 'content_type',
|
||||||
label: 'Type',
|
label: 'Type',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.contentTypeOptions || [
|
||||||
{ value: '', label: 'All Types' },
|
{ value: '', label: 'All Types' },
|
||||||
...CONTENT_TYPE_OPTIONS,
|
...CONTENT_TYPE_OPTIONS,
|
||||||
],
|
],
|
||||||
@@ -381,7 +405,7 @@ export function createApprovedPageConfig(params: {
|
|||||||
key: 'content_structure',
|
key: 'content_structure',
|
||||||
label: 'Structure',
|
label: 'Structure',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.contentStructureOptions || [
|
||||||
{ value: '', label: 'All Structures' },
|
{ value: '', label: 'All Structures' },
|
||||||
...ALL_CONTENT_STRUCTURES,
|
...ALL_CONTENT_STRUCTURES,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import Badge from '../../components/ui/badge/Badge';
|
|||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { getDifficultyNumber } from '../../utils/difficulty';
|
import { getDifficultyNumber } from '../../utils/difficulty';
|
||||||
import { Cluster } from '../../services/api';
|
import { Cluster } from '../../services/api';
|
||||||
|
import { getSectorBadgeColor } from '../../utils/badgeColors';
|
||||||
import Input from '../../components/form/input/InputField';
|
import Input from '../../components/form/input/InputField';
|
||||||
import Label from '../../components/form/Label';
|
import Label from '../../components/form/Label';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
@@ -68,6 +69,7 @@ export interface ClustersPageConfig {
|
|||||||
export const createClustersPageConfig = (
|
export const createClustersPageConfig = (
|
||||||
handlers: {
|
handlers: {
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Array<{ id: number; name: string }>;
|
||||||
formData: {
|
formData: {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
@@ -121,7 +123,7 @@ export const createClustersPageConfig = (
|
|||||||
...(showSectorColumn ? [{
|
...(showSectorColumn ? [{
|
||||||
...sectorColumn,
|
...sectorColumn,
|
||||||
render: (value: string, row: Cluster) => (
|
render: (value: string, row: Cluster) => (
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color={getSectorBadgeColor((row as any).sector_id, row.sector_name || undefined, handlers.sectors)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
titleColumn,
|
titleColumn,
|
||||||
statusColumn,
|
|
||||||
createdWithActionsColumn,
|
createdWithActionsColumn,
|
||||||
wordCountColumn,
|
wordCountColumn,
|
||||||
sectorColumn,
|
sectorColumn,
|
||||||
@@ -14,7 +13,8 @@ import {
|
|||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { Content } from '../../services/api';
|
import { Content } from '../../services/api';
|
||||||
import { CONTENT_TYPE_OPTIONS, STRUCTURE_LABELS, TYPE_LABELS } from '../structureMapping';
|
import { CONTENT_TYPE_OPTIONS, TYPE_LABELS } from '../structureMapping';
|
||||||
|
import { getSectorBadgeColor, getStructureBadgeColor, getStructureLabel } from '../../utils/badgeColors';
|
||||||
|
|
||||||
export interface ColumnConfig {
|
export interface ColumnConfig {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -77,14 +77,12 @@ const renderBadgeList = (items: string[], emptyLabel = '-') => {
|
|||||||
export const createContentPageConfig = (
|
export const createContentPageConfig = (
|
||||||
handlers: {
|
handlers: {
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Array<{ id: number; name: string }>;
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
setSearchTerm: (value: string) => void;
|
setSearchTerm: (value: string) => void;
|
||||||
statusFilter: string;
|
|
||||||
setStatusFilter: (value: string) => void;
|
|
||||||
setCurrentPage: (page: number) => void;
|
setCurrentPage: (page: number) => void;
|
||||||
onRowClick?: (row: Content) => void;
|
onRowClick?: (row: Content) => void;
|
||||||
// Dynamic filter options
|
// Dynamic filter options
|
||||||
statusOptions?: Array<{ value: string; label: string }>;
|
|
||||||
sourceOptions?: Array<{ value: string; label: string }>;
|
sourceOptions?: Array<{ value: string; label: string }>;
|
||||||
contentTypeOptions?: Array<{ value: string; label: string }>;
|
contentTypeOptions?: Array<{ value: string; label: string }>;
|
||||||
contentStructureOptions?: Array<{ value: string; label: string }>;
|
contentStructureOptions?: Array<{ value: string; label: string }>;
|
||||||
@@ -99,12 +97,6 @@ export const createContentPageConfig = (
|
|||||||
): ContentPageConfig => {
|
): ContentPageConfig => {
|
||||||
const showSectorColumn = !handlers.activeSector;
|
const showSectorColumn = !handlers.activeSector;
|
||||||
|
|
||||||
const statusColors: Record<string, 'warning' | 'info' | 'success' | 'primary'> = {
|
|
||||||
draft: 'warning',
|
|
||||||
review: 'info',
|
|
||||||
publish: 'success',
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@@ -133,7 +125,7 @@ export const createContentPageConfig = (
|
|||||||
...(showSectorColumn ? [{
|
...(showSectorColumn ? [{
|
||||||
...sectorColumn,
|
...sectorColumn,
|
||||||
render: (value: string, row: Content) => (
|
render: (value: string, row: Content) => (
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color={getSectorBadgeColor((row as any).sector_id, row.sector_name || undefined, handlers.sectors)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
@@ -160,13 +152,13 @@ export const createContentPageConfig = (
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = STRUCTURE_LABELS[value] || value || '-';
|
const label = getStructureLabel(value);
|
||||||
// Proper case: capitalize first letter of each word
|
// Proper case: capitalize first letter of each word
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
const properCase = label.split(/[_\s]+/).map(word =>
|
||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
word.charAt(0).toUpperCase() + word.slice(1)
|
||||||
).join(' ');
|
).join(' ');
|
||||||
return (
|
return (
|
||||||
<Badge color="purple" size="xs" variant="soft">
|
<Badge color={getStructureBadgeColor(value)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{properCase}</span>
|
<span className="text-[11px] font-normal">{properCase}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@@ -236,25 +228,6 @@ export const createContentPageConfig = (
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
...statusColumn,
|
|
||||||
sortable: true,
|
|
||||||
sortField: 'status',
|
|
||||||
render: (value: string) => {
|
|
||||||
const statusColors: Record<string, 'success' | 'amber'> = {
|
|
||||||
draft: 'amber',
|
|
||||||
published: 'success',
|
|
||||||
};
|
|
||||||
const color = statusColors[value] || 'amber';
|
|
||||||
// Proper case
|
|
||||||
const label = value ? value.charAt(0).toUpperCase() + value.slice(1) : 'Draft';
|
|
||||||
return (
|
|
||||||
<Badge color={color} size="xs" variant="soft">
|
|
||||||
<span className="text-[11px] font-normal">{label}</span>
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Removed the separate Status icon column. Both icons will be shown in the Created column below.
|
// Removed the separate Status icon column. Both icons will be shown in the Created column below.
|
||||||
{
|
{
|
||||||
key: 'source',
|
key: 'source',
|
||||||
@@ -410,30 +383,16 @@ export const createContentPageConfig = (
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: 'Search content...',
|
placeholder: 'Search content...',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ value: '', label: 'All Status' },
|
|
||||||
...(handlers.statusOptions !== undefined
|
|
||||||
? handlers.statusOptions
|
|
||||||
: [
|
|
||||||
{ value: 'draft', label: 'Draft' },
|
|
||||||
{ value: 'review', label: 'Review' },
|
|
||||||
{ value: 'approved', label: 'Approved' },
|
|
||||||
{ value: 'published', label: 'Published' },
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'content_type',
|
key: 'content_type',
|
||||||
label: 'Content Type',
|
label: 'Content Type',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: '', label: 'All Types' },
|
{ value: '', label: 'All Types' },
|
||||||
...CONTENT_TYPE_OPTIONS,
|
...(handlers.contentTypeOptions !== undefined
|
||||||
|
? handlers.contentTypeOptions
|
||||||
|
: CONTENT_TYPE_OPTIONS
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -442,20 +401,25 @@ export const createContentPageConfig = (
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: '', label: 'All Structures' },
|
{ value: '', label: 'All Structures' },
|
||||||
{ value: 'article', label: 'Article' },
|
...(handlers.contentStructureOptions !== undefined
|
||||||
{ value: 'guide', label: 'Guide' },
|
? handlers.contentStructureOptions
|
||||||
{ value: 'comparison', label: 'Comparison' },
|
: [
|
||||||
{ value: 'review', label: 'Review' },
|
{ value: 'article', label: 'Article' },
|
||||||
{ value: 'listicle', label: 'Listicle' },
|
{ value: 'guide', label: 'Guide' },
|
||||||
{ value: 'landing_page', label: 'Landing Page' },
|
{ value: 'comparison', label: 'Comparison' },
|
||||||
{ value: 'business_page', label: 'Business Page' },
|
{ value: 'review', label: 'Review' },
|
||||||
{ value: 'service_page', label: 'Service Page' },
|
{ value: 'listicle', label: 'Listicle' },
|
||||||
{ value: 'general', label: 'General' },
|
{ value: 'landing_page', label: 'Landing Page' },
|
||||||
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
{ value: 'business_page', label: 'Business Page' },
|
||||||
{ value: 'product_page', label: 'Product Page' },
|
{ value: 'service_page', label: 'Service Page' },
|
||||||
{ value: 'category_archive', label: 'Category Archive' },
|
{ value: 'general', label: 'General' },
|
||||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
||||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
{ value: 'product_page', label: 'Product Page' },
|
||||||
|
{ value: 'category_archive', label: 'Category Archive' },
|
||||||
|
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||||
|
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||||
|
]
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { ContentIdea, Cluster } from '../../services/api';
|
import { ContentIdea, Cluster } from '../../services/api';
|
||||||
|
import { getSectorBadgeColor, getStructureBadgeColor, getStructureLabel } from '../../utils/badgeColors';
|
||||||
|
|
||||||
export interface ColumnConfig {
|
export interface ColumnConfig {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -64,6 +65,7 @@ export const createIdeasPageConfig = (
|
|||||||
handlers: {
|
handlers: {
|
||||||
clusters: Array<{ id: number; name: string }>;
|
clusters: Array<{ id: number; name: string }>;
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Array<{ id: number; name: string }>;
|
||||||
formData: {
|
formData: {
|
||||||
idea_title: string;
|
idea_title: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
@@ -116,7 +118,7 @@ export const createIdeasPageConfig = (
|
|||||||
...(showSectorColumn ? [{
|
...(showSectorColumn ? [{
|
||||||
...sectorColumn,
|
...sectorColumn,
|
||||||
render: (value: string, row: ContentIdea) => (
|
render: (value: string, row: ContentIdea) => (
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color={getSectorBadgeColor((row as any).sector_id, row.sector_name || undefined, handlers.sectors)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
@@ -127,12 +129,12 @@ export const createIdeasPageConfig = (
|
|||||||
sortable: false, // Backend doesn't support sorting by content_structure
|
sortable: false, // Backend doesn't support sorting by content_structure
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = value?.replace('_', ' ') || '-';
|
const label = getStructureLabel(value);
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
const properCase = label.split(/[_\s]+/).map(word =>
|
||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
word.charAt(0).toUpperCase() + word.slice(1)
|
||||||
).join(' ');
|
).join(' ');
|
||||||
return (
|
return (
|
||||||
<Badge color="purple" size="xs" variant="soft">
|
<Badge color={getStructureBadgeColor(value)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{properCase}</span>
|
<span className="text-[11px] font-normal">{properCase}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ export const createImagesPageConfig = (
|
|||||||
setSearchTerm: (value: string) => void;
|
setSearchTerm: (value: string) => void;
|
||||||
statusFilter: string;
|
statusFilter: string;
|
||||||
setStatusFilter: (value: string) => void;
|
setStatusFilter: (value: string) => void;
|
||||||
|
contentStatusFilter?: string;
|
||||||
|
setContentStatusFilter?: (value: string) => void;
|
||||||
|
contentStatusOptions?: Array<{ value: string; label: string }>;
|
||||||
setCurrentPage: (page: number) => void;
|
setCurrentPage: (page: number) => void;
|
||||||
maxInArticleImages?: number; // Optional: max in-article images to display
|
maxInArticleImages?: number; // Optional: max in-article images to display
|
||||||
onGenerateImages?: (contentId: number) => void; // Handler for generate images button
|
onGenerateImages?: (contentId: number) => void; // Handler for generate images button
|
||||||
@@ -66,15 +69,17 @@ export const createImagesPageConfig = (
|
|||||||
sortField: 'content_title',
|
sortField: 'content_title',
|
||||||
width: '400px',
|
width: '400px',
|
||||||
render: (_value: string, row: ContentImagesGroup) => {
|
render: (_value: string, row: ContentImagesGroup) => {
|
||||||
const statusColors: Record<string, 'warning' | 'info' | 'success'> = {
|
const statusColors: Record<string, 'warning' | 'info' | 'success' | 'blue' | 'gray'> = {
|
||||||
draft: 'warning',
|
draft: 'warning',
|
||||||
review: 'info',
|
review: 'info',
|
||||||
publish: 'success',
|
approved: 'blue',
|
||||||
|
published: 'success',
|
||||||
};
|
};
|
||||||
const statusLabels: Record<string, string> = {
|
const statusLabels: Record<string, string> = {
|
||||||
draft: 'Draft',
|
draft: 'Draft',
|
||||||
review: 'Review',
|
review: 'Review',
|
||||||
publish: 'Publish',
|
approved: 'Approved',
|
||||||
|
published: 'Published',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -192,7 +197,7 @@ export const createImagesPageConfig = (
|
|||||||
key: 'content_status',
|
key: 'content_status',
|
||||||
label: 'Content Status',
|
label: 'Content Status',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: handlers.contentStatusOptions || [
|
||||||
{ value: '', label: 'All' },
|
{ value: '', label: 'All' },
|
||||||
{ value: 'draft', label: 'Draft' },
|
{ value: 'draft', label: 'Draft' },
|
||||||
{ value: 'review', label: 'Review' },
|
{ value: 'review', label: 'Review' },
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import Badge from '../../components/ui/badge/Badge';
|
|||||||
import { getDifficultyNumber, getDifficultyOptions, getDifficultyValueFromNumber } from '../../utils/difficulty';
|
import { getDifficultyNumber, getDifficultyOptions, getDifficultyValueFromNumber } from '../../utils/difficulty';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { Keyword } from '../../services/api';
|
import { Keyword } from '../../services/api';
|
||||||
|
import { getSectorBadgeColor } from '../../utils/badgeColors';
|
||||||
import Input from '../../components/form/input/InputField';
|
import Input from '../../components/form/input/InputField';
|
||||||
import Label from '../../components/form/Label';
|
import Label from '../../components/form/Label';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
@@ -102,6 +103,7 @@ export const createKeywordsPageConfig = (
|
|||||||
handlers: {
|
handlers: {
|
||||||
clusters: Array<{ id: number; name: string }>;
|
clusters: Array<{ id: number; name: string }>;
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Array<{ id: number; name: string }>;
|
||||||
formData: {
|
formData: {
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
volume?: number | null;
|
volume?: number | null;
|
||||||
@@ -162,7 +164,7 @@ export const createKeywordsPageConfig = (
|
|||||||
...(showSectorColumn ? [{
|
...(showSectorColumn ? [{
|
||||||
...sectorColumn,
|
...sectorColumn,
|
||||||
render: (value: string, row: Keyword) => (
|
render: (value: string, row: Keyword) => (
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color={getSectorBadgeColor((row as any).sector_id, row.sector_name || undefined, handlers.sectors)} size="xs" variant="soft">
|
||||||
{row.sector_name || '-'}
|
{row.sector_name || '-'}
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
import { Content } from '../../services/api';
|
import { Content } from '../../services/api';
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { CheckCircleIcon } from '../../icons';
|
import { TYPE_LABELS, CONTENT_TYPE_OPTIONS, ALL_CONTENT_STRUCTURES } from '../structureMapping';
|
||||||
import { STRUCTURE_LABELS, TYPE_LABELS, CONTENT_TYPE_OPTIONS, ALL_CONTENT_STRUCTURES } from '../structureMapping';
|
import { getSectorBadgeColor, getStructureBadgeColor, getStructureLabel } from '../../utils/badgeColors';
|
||||||
|
import type { Sector } from '../../store/sectorStore';
|
||||||
|
|
||||||
export interface ColumnConfig {
|
export interface ColumnConfig {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -48,13 +49,11 @@ export interface ReviewPageConfig {
|
|||||||
export function createReviewPageConfig(params: {
|
export function createReviewPageConfig(params: {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
setSearchTerm: (value: string) => void;
|
setSearchTerm: (value: string) => void;
|
||||||
statusFilter: string;
|
|
||||||
setStatusFilter: (value: string) => void;
|
|
||||||
setCurrentPage: (page: number) => void;
|
setCurrentPage: (page: number) => void;
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Sector[];
|
||||||
onRowClick?: (row: Content) => void;
|
onRowClick?: (row: Content) => void;
|
||||||
// Dynamic filter options
|
// Dynamic filter options
|
||||||
statusOptions?: Array<{ value: string; label: string }>;
|
|
||||||
siteStatusOptions?: Array<{ value: string; label: string }>;
|
siteStatusOptions?: Array<{ value: string; label: string }>;
|
||||||
contentTypeOptions?: Array<{ value: string; label: string }>;
|
contentTypeOptions?: Array<{ value: string; label: string }>;
|
||||||
contentStructureOptions?: Array<{ value: string; label: string }>;
|
contentStructureOptions?: Array<{ value: string; label: string }>;
|
||||||
@@ -159,12 +158,12 @@ export function createReviewPageConfig(params: {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = STRUCTURE_LABELS[value] || value || '-';
|
const properCase = getStructureLabel(value)
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
.split(/[_\s]+/)
|
||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
).join(' ');
|
.join(' ');
|
||||||
return (
|
return (
|
||||||
<Badge color="purple" size="xs" variant="soft">
|
<Badge color={getStructureBadgeColor(value)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{properCase}</span>
|
<span className="text-[11px] font-normal">{properCase}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@@ -186,35 +185,6 @@ export function createReviewPageConfig(params: {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
sortable: true,
|
|
||||||
sortField: 'status',
|
|
||||||
render: (value: string, row: Content) => {
|
|
||||||
const status = value || 'draft';
|
|
||||||
const statusColors: Record<string, 'gray' | 'blue' | 'green' | 'amber' | 'red'> = {
|
|
||||||
draft: 'gray',
|
|
||||||
review: 'blue',
|
|
||||||
published: 'green',
|
|
||||||
scheduled: 'amber',
|
|
||||||
archived: 'red',
|
|
||||||
};
|
|
||||||
const color = statusColors[status] || 'gray';
|
|
||||||
const label = status.charAt(0).toUpperCase() + status.slice(1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<Badge color={color} size="xs" variant="soft">
|
|
||||||
<span className="text-[11px] font-normal">{label}</span>
|
|
||||||
</Badge>
|
|
||||||
{row.external_id && (
|
|
||||||
<CheckCircleIcon className="w-3.5 h-3.5 text-success-500" title="Published to Site" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'word_count',
|
key: 'word_count',
|
||||||
label: 'Words',
|
label: 'Words',
|
||||||
@@ -251,11 +221,14 @@ export function createReviewPageConfig(params: {
|
|||||||
label: 'Sector',
|
label: 'Sector',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (value: string, row: Content) => (
|
render: (value: string, row: Content) => {
|
||||||
<Badge color="info" size="xs" variant="soft">
|
const color = getSectorBadgeColor(row.sector_id, row.sector_name, params.sectors);
|
||||||
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
return (
|
||||||
</Badge>
|
<Badge color={color} size="xs" variant="soft">
|
||||||
),
|
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,23 +241,11 @@ export function createReviewPageConfig(params: {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: 'Search content...',
|
placeholder: 'Search content...',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ value: '', label: 'All' },
|
|
||||||
{ value: 'draft', label: 'Draft' },
|
|
||||||
{ value: 'review', label: 'Review' },
|
|
||||||
{ value: 'approved', label: 'Approved' },
|
|
||||||
{ value: 'published', label: 'Published' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'site_status',
|
key: 'site_status',
|
||||||
label: 'Site Status',
|
label: 'Site Status',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.siteStatusOptions || [
|
||||||
{ value: '', label: 'All' },
|
{ value: '', label: 'All' },
|
||||||
{ value: 'not_published', label: 'Not Published' },
|
{ value: 'not_published', label: 'Not Published' },
|
||||||
{ value: 'scheduled', label: 'Scheduled' },
|
{ value: 'scheduled', label: 'Scheduled' },
|
||||||
@@ -297,7 +258,7 @@ export function createReviewPageConfig(params: {
|
|||||||
key: 'content_type',
|
key: 'content_type',
|
||||||
label: 'Type',
|
label: 'Type',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.contentTypeOptions || [
|
||||||
{ value: '', label: 'All Types' },
|
{ value: '', label: 'All Types' },
|
||||||
...CONTENT_TYPE_OPTIONS,
|
...CONTENT_TYPE_OPTIONS,
|
||||||
],
|
],
|
||||||
@@ -306,7 +267,7 @@ export function createReviewPageConfig(params: {
|
|||||||
key: 'content_structure',
|
key: 'content_structure',
|
||||||
label: 'Structure',
|
label: 'Structure',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: params.contentStructureOptions || [
|
||||||
{ value: '', label: 'All Structures' },
|
{ value: '', label: 'All Structures' },
|
||||||
...ALL_CONTENT_STRUCTURES,
|
...ALL_CONTENT_STRUCTURES,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import Badge from '../../components/ui/badge/Badge';
|
|||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
import { Task, Cluster } from '../../services/api';
|
import { Task, Cluster } from '../../services/api';
|
||||||
import { CONTENT_TYPE_OPTIONS, CONTENT_STRUCTURE_BY_TYPE, STRUCTURE_LABELS, TYPE_LABELS } from '../structureMapping';
|
import { CONTENT_TYPE_OPTIONS, CONTENT_STRUCTURE_BY_TYPE, STRUCTURE_LABELS, TYPE_LABELS } from '../structureMapping';
|
||||||
|
import { getSectorBadgeColor, getStructureBadgeColor } from '../../utils/badgeColors';
|
||||||
|
|
||||||
export interface ColumnConfig {
|
export interface ColumnConfig {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -68,6 +69,7 @@ export const createTasksPageConfig = (
|
|||||||
handlers: {
|
handlers: {
|
||||||
clusters: Array<{ id: number; name: string }>;
|
clusters: Array<{ id: number; name: string }>;
|
||||||
activeSector: { id: number; name: string } | null;
|
activeSector: { id: number; name: string } | null;
|
||||||
|
sectors?: Array<{ id: number; name: string }>;
|
||||||
formData: {
|
formData: {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
@@ -90,9 +92,12 @@ export const createTasksPageConfig = (
|
|||||||
setStructureFilter: (value: string) => void;
|
setStructureFilter: (value: string) => void;
|
||||||
typeFilter: string;
|
typeFilter: string;
|
||||||
setTypeFilter: (value: string) => void;
|
setTypeFilter: (value: string) => void;
|
||||||
sourceFilter: string;
|
|
||||||
setSourceFilter: (value: string) => void;
|
|
||||||
setCurrentPage: (page: number) => void;
|
setCurrentPage: (page: number) => void;
|
||||||
|
// Dynamic filter options
|
||||||
|
statusOptions?: Array<{ value: string; label: string }>;
|
||||||
|
contentTypeOptions?: Array<{ value: string; label: string }>;
|
||||||
|
contentStructureOptions?: Array<{ value: string; label: string }>;
|
||||||
|
clusterOptions?: Array<{ value: string; label: string }>;
|
||||||
}
|
}
|
||||||
): TasksPageConfig => {
|
): TasksPageConfig => {
|
||||||
const showSectorColumn = !handlers.activeSector; // Show when viewing all sectors
|
const showSectorColumn = !handlers.activeSector; // Show when viewing all sectors
|
||||||
@@ -124,7 +129,7 @@ export const createTasksPageConfig = (
|
|||||||
...(showSectorColumn ? [{
|
...(showSectorColumn ? [{
|
||||||
...sectorColumn,
|
...sectorColumn,
|
||||||
render: (value: string, row: Task) => (
|
render: (value: string, row: Task) => (
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color={getSectorBadgeColor((row as any).sector_id, row.sector_name || undefined, handlers.sectors)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
@@ -184,7 +189,7 @@ export const createTasksPageConfig = (
|
|||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
word.charAt(0).toUpperCase() + word.slice(1)
|
||||||
).join(' ');
|
).join(' ');
|
||||||
return (
|
return (
|
||||||
<Badge color="purple" size="xs" variant="soft">
|
<Badge color={getStructureBadgeColor(value)} size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{properCase}</span>
|
<span className="text-[11px] font-normal">{properCase}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@@ -314,8 +319,13 @@ export const createTasksPageConfig = (
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: '', label: 'All Status' },
|
{ value: '', label: 'All Status' },
|
||||||
{ value: 'queued', label: 'Queued' },
|
...(handlers.statusOptions !== undefined
|
||||||
{ value: 'completed', label: 'Completed' },
|
? handlers.statusOptions
|
||||||
|
: [
|
||||||
|
{ value: 'queued', label: 'Queued' },
|
||||||
|
{ value: 'completed', label: 'Completed' },
|
||||||
|
]
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -324,7 +334,10 @@ export const createTasksPageConfig = (
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: '', label: 'All Types' },
|
{ value: '', label: 'All Types' },
|
||||||
...CONTENT_TYPE_OPTIONS,
|
...(handlers.contentTypeOptions !== undefined
|
||||||
|
? handlers.contentTypeOptions
|
||||||
|
: CONTENT_TYPE_OPTIONS
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -333,20 +346,25 @@ export const createTasksPageConfig = (
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: '', label: 'All Structures' },
|
{ value: '', label: 'All Structures' },
|
||||||
{ value: 'article', label: 'Article' },
|
...(handlers.contentStructureOptions !== undefined
|
||||||
{ value: 'guide', label: 'Guide' },
|
? handlers.contentStructureOptions
|
||||||
{ value: 'comparison', label: 'Comparison' },
|
: [
|
||||||
{ value: 'review', label: 'Review' },
|
{ value: 'article', label: 'Article' },
|
||||||
{ value: 'listicle', label: 'Listicle' },
|
{ value: 'guide', label: 'Guide' },
|
||||||
{ value: 'landing_page', label: 'Landing Page' },
|
{ value: 'comparison', label: 'Comparison' },
|
||||||
{ value: 'business_page', label: 'Business Page' },
|
{ value: 'review', label: 'Review' },
|
||||||
{ value: 'service_page', label: 'Service Page' },
|
{ value: 'listicle', label: 'Listicle' },
|
||||||
{ value: 'general', label: 'General' },
|
{ value: 'landing_page', label: 'Landing Page' },
|
||||||
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
{ value: 'business_page', label: 'Business Page' },
|
||||||
{ value: 'product_page', label: 'Product Page' },
|
{ value: 'service_page', label: 'Service Page' },
|
||||||
{ value: 'category_archive', label: 'Category Archive' },
|
{ value: 'general', label: 'General' },
|
||||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
||||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
{ value: 'product_page', label: 'Product Page' },
|
||||||
|
{ value: 'category_archive', label: 'Category Archive' },
|
||||||
|
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||||
|
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||||
|
]
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -356,7 +374,10 @@ export const createTasksPageConfig = (
|
|||||||
options: (() => {
|
options: (() => {
|
||||||
return [
|
return [
|
||||||
{ value: '', label: 'All Clusters' },
|
{ value: '', label: 'All Clusters' },
|
||||||
...handlers.clusters.map((c) => ({ value: c.id.toString(), label: c.name })),
|
...(handlers.clusterOptions !== undefined
|
||||||
|
? handlers.clusterOptions
|
||||||
|
: handlers.clusters.map((c) => ({ value: c.id.toString(), label: c.name }))
|
||||||
|
),
|
||||||
];
|
];
|
||||||
})(),
|
})(),
|
||||||
dynamicOptions: 'clusters',
|
dynamicOptions: 'clusters',
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeW
|
|||||||
export default function Clusters() {
|
export default function Clusters() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { activeSector } = useSectorStore();
|
const { activeSector, sectors } = useSectorStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
const { pageSize } = usePageSizeStore();
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
@@ -429,6 +429,7 @@ export default function Clusters() {
|
|||||||
const pageConfig = useMemo(() => {
|
const pageConfig = useMemo(() => {
|
||||||
return createClustersPageConfig({
|
return createClustersPageConfig({
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
formData,
|
formData,
|
||||||
setFormData,
|
setFormData,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
@@ -458,6 +459,7 @@ export default function Clusters() {
|
|||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
formData,
|
formData,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeW
|
|||||||
export default function Ideas() {
|
export default function Ideas() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { activeSector } = useSectorStore();
|
const { activeSector, sectors } = useSectorStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
const { pageSize } = usePageSizeStore();
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
@@ -341,6 +341,8 @@ export default function Ideas() {
|
|||||||
return createIdeasPageConfig({
|
return createIdeasPageConfig({
|
||||||
clusters,
|
clusters,
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
|
sectors,
|
||||||
formData,
|
formData,
|
||||||
setFormData,
|
setFormData,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import { createKeywordsPageConfig } from '../../config/pages/keywords.config';
|
|||||||
export default function Keywords() {
|
export default function Keywords() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { activeSector, loadSectorsForSite } = useSectorStore();
|
const { activeSector, sectors, loadSectorsForSite } = useSectorStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
const { pageSize } = usePageSizeStore();
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
@@ -575,6 +575,7 @@ export default function Keywords() {
|
|||||||
return createKeywordsPageConfig({
|
return createKeywordsPageConfig({
|
||||||
clusters,
|
clusters,
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
formData,
|
formData,
|
||||||
setFormData,
|
setFormData,
|
||||||
// Filter state handlers
|
// Filter state handlers
|
||||||
@@ -611,6 +612,7 @@ export default function Keywords() {
|
|||||||
}, [
|
}, [
|
||||||
clusters,
|
clusters,
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
formData,
|
formData,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { SectorMetricGrid, StatType } from '../../components/keywords-library/Se
|
|||||||
import SmartSuggestions from '../../components/keywords-library/SmartSuggestions';
|
import SmartSuggestions from '../../components/keywords-library/SmartSuggestions';
|
||||||
import SectorCardsGrid from '../../components/keywords-library/SectorCardsGrid';
|
import SectorCardsGrid from '../../components/keywords-library/SectorCardsGrid';
|
||||||
import BulkAddConfirmation from '../../components/keywords-library/BulkAddConfirmation';
|
import BulkAddConfirmation from '../../components/keywords-library/BulkAddConfirmation';
|
||||||
|
import { getSectorBadgeColor } from '../../utils/badgeColors';
|
||||||
|
|
||||||
export default function IndustriesSectorsKeywords() {
|
export default function IndustriesSectorsKeywords() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -803,11 +804,14 @@ export default function IndustriesSectorsKeywords() {
|
|||||||
key: 'sector_name',
|
key: 'sector_name',
|
||||||
label: 'Sector',
|
label: 'Sector',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
render: (_value: string, row: SeedKeyword) => (
|
render: (_value: string, row: SeedKeyword) => {
|
||||||
<Badge color="info" size="sm" variant="light">
|
const color = getSectorBadgeColor(row.sector_id, row.sector_name, sectors);
|
||||||
{row.sector_name || '-'}
|
return (
|
||||||
</Badge>
|
<Badge color={color} size="sm" variant="light">
|
||||||
),
|
{row.sector_name || '-'}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
},
|
||||||
}] : []),
|
}] : []),
|
||||||
{
|
{
|
||||||
key: 'volume',
|
key: 'volume',
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import ErrorDetailsModal from '../../components/common/ErrorDetailsModal';
|
|||||||
export default function Approved() {
|
export default function Approved() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { activeSector } = useSectorStore();
|
const { activeSector, sectors } = useSectorStore();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
const { pageSize } = usePageSizeStore();
|
||||||
|
|
||||||
@@ -670,11 +670,16 @@ export default function Approved() {
|
|||||||
setSiteStatusFilter,
|
setSiteStatusFilter,
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
onRowClick: (row: Content) => {
|
onRowClick: (row: Content) => {
|
||||||
navigate(`/writer/content/${row.id}`);
|
navigate(`/writer/content/${row.id}`);
|
||||||
},
|
},
|
||||||
|
statusOptions,
|
||||||
|
siteStatusOptions,
|
||||||
|
contentTypeOptions,
|
||||||
|
contentStructureOptions,
|
||||||
});
|
});
|
||||||
}, [searchTerm, statusFilter, siteStatusFilter, contentTypeFilter, contentStructureFilter, activeSector, navigate]);
|
}, [searchTerm, statusFilter, siteStatusFilter, activeSector, sectors, statusOptions, siteStatusOptions, contentTypeOptions, contentStructureOptions, navigate]);
|
||||||
|
|
||||||
// Calculate header metrics - use totals from API calls (not page data)
|
// Calculate header metrics - use totals from API calls (not page data)
|
||||||
// This ensures metrics show correct totals across all pages, not just current page
|
// This ensures metrics show correct totals across all pages, not just current page
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { PencilSquareIcon } from '@heroicons/react/24/outline';
|
|||||||
export default function Content() {
|
export default function Content() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { activeSector } = useSectorStore();
|
const { activeSector, sectors } = useSectorStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
const { pageSize } = usePageSizeStore();
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
@@ -48,14 +48,13 @@ export default function Content() {
|
|||||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||||
|
|
||||||
// Dynamic filter options (loaded from backend)
|
// Dynamic filter options (loaded from backend)
|
||||||
const [statusOptions, setStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
|
||||||
const [sourceOptions, setSourceOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [sourceOptions, setSourceOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState('draft');
|
const [statusFilter] = useState('draft');
|
||||||
const [sourceFilter, setSourceFilter] = useState('');
|
const [sourceFilter, setSourceFilter] = useState('');
|
||||||
const [contentTypeFilter, setContentTypeFilter] = useState('');
|
const [contentTypeFilter, setContentTypeFilter] = useState('');
|
||||||
const [contentStructureFilter, setContentStructureFilter] = useState('');
|
const [contentStructureFilter, setContentStructureFilter] = useState('');
|
||||||
@@ -90,7 +89,6 @@ export default function Content() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
|
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
|
||||||
setStatusOptions(options.statuses || []);
|
|
||||||
setSourceOptions(options.sources || []);
|
setSourceOptions(options.sources || []);
|
||||||
setContentTypeOptions(options.content_types || []);
|
setContentTypeOptions(options.content_types || []);
|
||||||
setContentStructureOptions(options.content_structures || []);
|
setContentStructureOptions(options.content_structures || []);
|
||||||
@@ -262,14 +260,12 @@ export default function Content() {
|
|||||||
const pageConfig = useMemo(() => {
|
const pageConfig = useMemo(() => {
|
||||||
return createContentPageConfig({
|
return createContentPageConfig({
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
statusFilter,
|
|
||||||
setStatusFilter,
|
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
onRowClick: handleRowClick,
|
onRowClick: handleRowClick,
|
||||||
// Dynamic filter options
|
// Dynamic filter options
|
||||||
statusOptions,
|
|
||||||
sourceOptions,
|
sourceOptions,
|
||||||
contentTypeOptions,
|
contentTypeOptions,
|
||||||
contentStructureOptions,
|
contentStructureOptions,
|
||||||
@@ -283,10 +279,9 @@ export default function Content() {
|
|||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
statusFilter,
|
|
||||||
handleRowClick,
|
handleRowClick,
|
||||||
statusOptions,
|
|
||||||
sourceOptions,
|
sourceOptions,
|
||||||
contentTypeOptions,
|
contentTypeOptions,
|
||||||
contentStructureOptions,
|
contentStructureOptions,
|
||||||
@@ -297,7 +292,6 @@ export default function Content() {
|
|||||||
sourceFilter,
|
sourceFilter,
|
||||||
setSourceFilter,
|
setSourceFilter,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
setStatusFilter,
|
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -410,18 +404,22 @@ export default function Content() {
|
|||||||
filters={pageConfig.filters}
|
filters={pageConfig.filters}
|
||||||
filterValues={{
|
filterValues={{
|
||||||
search: searchTerm,
|
search: searchTerm,
|
||||||
status: statusFilter,
|
|
||||||
source: sourceFilter,
|
source: sourceFilter,
|
||||||
|
content_type: contentTypeFilter,
|
||||||
|
content_structure: contentStructureFilter,
|
||||||
}}
|
}}
|
||||||
onFilterChange={(key: string, value: any) => {
|
onFilterChange={(key: string, value: any) => {
|
||||||
if (key === 'search') {
|
if (key === 'search') {
|
||||||
setSearchTerm(value);
|
setSearchTerm(value);
|
||||||
} else if (key === 'status') {
|
|
||||||
setStatusFilter(value);
|
|
||||||
setCurrentPage(1);
|
|
||||||
} else if (key === 'source') {
|
} else if (key === 'source') {
|
||||||
setSourceFilter(value);
|
setSourceFilter(value);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
|
} else if (key === 'content_type') {
|
||||||
|
setContentTypeFilter(value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else if (key === 'content_structure') {
|
||||||
|
setContentStructureFilter(value);
|
||||||
|
setCurrentPage(1);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
fetchContentImages,
|
fetchContentImages,
|
||||||
fetchImages,
|
fetchImages,
|
||||||
fetchContent,
|
fetchContent,
|
||||||
|
fetchWriterContentFilterOptions,
|
||||||
ContentImagesGroup,
|
ContentImagesGroup,
|
||||||
ContentImagesResponse,
|
ContentImagesResponse,
|
||||||
fetchImageGenerationSettings,
|
fetchImageGenerationSettings,
|
||||||
@@ -55,6 +56,8 @@ export default function Images() {
|
|||||||
// Filter state
|
// Filter state
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState('');
|
const [statusFilter, setStatusFilter] = useState('');
|
||||||
|
const [contentStatusFilter, setContentStatusFilter] = useState('');
|
||||||
|
const [contentStatusOptions, setContentStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
|
|
||||||
// Pagination state (client-side for now)
|
// Pagination state (client-side for now)
|
||||||
@@ -68,6 +71,32 @@ export default function Images() {
|
|||||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
const [showContent, setShowContent] = useState(false);
|
const [showContent, setShowContent] = useState(false);
|
||||||
|
|
||||||
|
// Load dynamic filter options for content status
|
||||||
|
const loadFilterOptions = useCallback(async (currentFilters?: {
|
||||||
|
status?: string;
|
||||||
|
search?: string;
|
||||||
|
}) => {
|
||||||
|
if (!activeSite) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
|
||||||
|
setContentStatusOptions(options.statuses || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading filter options:', error);
|
||||||
|
}
|
||||||
|
}, [activeSite]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadFilterOptions();
|
||||||
|
}, [activeSite]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadFilterOptions({
|
||||||
|
status: contentStatusFilter || undefined,
|
||||||
|
search: searchTerm || undefined,
|
||||||
|
});
|
||||||
|
}, [contentStatusFilter, searchTerm, loadFilterOptions]);
|
||||||
|
|
||||||
// Image queue modal state
|
// Image queue modal state
|
||||||
const [isQueueModalOpen, setIsQueueModalOpen] = useState(false);
|
const [isQueueModalOpen, setIsQueueModalOpen] = useState(false);
|
||||||
const [imageQueue, setImageQueue] = useState<ImageQueueItem[]>([]);
|
const [imageQueue, setImageQueue] = useState<ImageQueueItem[]>([]);
|
||||||
@@ -130,7 +159,14 @@ export default function Images() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client-side status filter
|
// Client-side content status filter
|
||||||
|
if (contentStatusFilter) {
|
||||||
|
filteredResults = filteredResults.filter(group =>
|
||||||
|
group.content_status === contentStatusFilter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client-side image status filter
|
||||||
if (statusFilter) {
|
if (statusFilter) {
|
||||||
filteredResults = filteredResults.filter(group =>
|
filteredResults = filteredResults.filter(group =>
|
||||||
group.overall_status === statusFilter
|
group.overall_status === statusFilter
|
||||||
@@ -479,12 +515,15 @@ export default function Images() {
|
|||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
|
contentStatusFilter,
|
||||||
|
setContentStatusFilter,
|
||||||
|
contentStatusOptions,
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
maxInArticleImages,
|
maxInArticleImages,
|
||||||
onGenerateImages: handleGenerateImages,
|
onGenerateImages: handleGenerateImages,
|
||||||
onImageClick: handleImageClick,
|
onImageClick: handleImageClick,
|
||||||
});
|
});
|
||||||
}, [searchTerm, statusFilter, maxInArticleImages, handleGenerateImages, handleImageClick]);
|
}, [searchTerm, statusFilter, contentStatusFilter, contentStatusOptions, maxInArticleImages, handleGenerateImages, handleImageClick]);
|
||||||
|
|
||||||
// Calculate header metrics - use totals from API calls (not page data)
|
// Calculate header metrics - use totals from API calls (not page data)
|
||||||
// This ensures metrics show correct totals across all pages, not just current page
|
// This ensures metrics show correct totals across all pages, not just current page
|
||||||
@@ -545,6 +584,7 @@ export default function Images() {
|
|||||||
filters={pageConfig.filters}
|
filters={pageConfig.filters}
|
||||||
filterValues={{
|
filterValues={{
|
||||||
search: searchTerm,
|
search: searchTerm,
|
||||||
|
content_status: contentStatusFilter,
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
}}
|
}}
|
||||||
nextAction={selectedIds.length > 0 ? {
|
nextAction={selectedIds.length > 0 ? {
|
||||||
@@ -560,6 +600,8 @@ export default function Images() {
|
|||||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||||
if (key === 'search') {
|
if (key === 'search') {
|
||||||
setSearchTerm(stringValue);
|
setSearchTerm(stringValue);
|
||||||
|
} else if (key === 'content_status') {
|
||||||
|
setContentStatusFilter(stringValue);
|
||||||
} else if (key === 'status') {
|
} else if (key === 'status') {
|
||||||
setStatusFilter(stringValue);
|
setStatusFilter(stringValue);
|
||||||
}
|
}
|
||||||
@@ -593,6 +635,7 @@ export default function Images() {
|
|||||||
headerMetrics={headerMetrics}
|
headerMetrics={headerMetrics}
|
||||||
onFilterReset={() => {
|
onFilterReset={() => {
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
|
setContentStatusFilter('');
|
||||||
setStatusFilter('');
|
setStatusFilter('');
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||||
import {
|
const { activeSector, sectors } = useSectorStore();
|
||||||
fetchContent,
|
fetchContent,
|
||||||
fetchImages,
|
fetchImages,
|
||||||
fetchWriterContentFilterOptions,
|
fetchWriterContentFilterOptions,
|
||||||
@@ -24,14 +24,12 @@ import { useSectorStore } from '../../store/sectorStore';
|
|||||||
import { useSiteStore } from '../../store/siteStore';
|
import { useSiteStore } from '../../store/siteStore';
|
||||||
import { usePageSizeStore } from '../../store/pageSizeStore';
|
import { usePageSizeStore } from '../../store/pageSizeStore';
|
||||||
import PageHeader from '../../components/common/PageHeader';
|
import PageHeader from '../../components/common/PageHeader';
|
||||||
import StandardThreeWidgetFooter from '../../components/dashboard/StandardThreeWidgetFooter';
|
|
||||||
|
|
||||||
export default function Review() {
|
export default function Review() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { activeSector } = useSectorStore();
|
const { activeSector } = useSectorStore();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
const [content, setContent] = useState<Content[]>([]);
|
const [content, setContent] = useState<Content[]>([]);
|
||||||
@@ -46,21 +44,18 @@ export default function Review() {
|
|||||||
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
const [totalImagesCount, setTotalImagesCount] = useState(0);
|
||||||
|
|
||||||
// Dynamic filter options (loaded from backend)
|
// Dynamic filter options (loaded from backend)
|
||||||
const [statusOptions, setStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
|
||||||
const [siteStatusOptions, setSiteStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [siteStatusOptions, setSiteStatusOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
|
|
||||||
// Filter state - default to review status
|
// Filter state - default to review status
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState('review'); // Default to review
|
|
||||||
const [siteStatusFilter, setSiteStatusFilter] = useState('');
|
const [siteStatusFilter, setSiteStatusFilter] = useState('');
|
||||||
const [contentTypeFilter, setContentTypeFilter] = useState('');
|
const [contentTypeFilter, setContentTypeFilter] = useState('');
|
||||||
const [contentStructureFilter, setContentStructureFilter] = useState('');
|
const [contentStructureFilter, setContentStructureFilter] = useState('');
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
|
|
||||||
// Pagination state
|
// Pagination state
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
const [totalCount, setTotalCount] = useState(0);
|
||||||
|
|
||||||
@@ -71,7 +66,6 @@ export default function Review() {
|
|||||||
|
|
||||||
// Load dynamic filter options based on current site's data and applied filters
|
// Load dynamic filter options based on current site's data and applied filters
|
||||||
// This implements cascading filters - each filter's options reflect what's available
|
// This implements cascading filters - each filter's options reflect what's available
|
||||||
// given the other currently applied filters
|
|
||||||
const loadFilterOptions = useCallback(async (currentFilters?: {
|
const loadFilterOptions = useCallback(async (currentFilters?: {
|
||||||
status?: string;
|
status?: string;
|
||||||
site_status?: string;
|
site_status?: string;
|
||||||
@@ -83,7 +77,6 @@ export default function Review() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
|
const options = await fetchWriterContentFilterOptions(activeSite.id, currentFilters);
|
||||||
setStatusOptions(options.statuses || []);
|
|
||||||
setSiteStatusOptions(options.site_statuses || []);
|
setSiteStatusOptions(options.site_statuses || []);
|
||||||
setContentTypeOptions(options.content_types || []);
|
setContentTypeOptions(options.content_types || []);
|
||||||
setContentStructureOptions(options.content_structures || []);
|
setContentStructureOptions(options.content_structures || []);
|
||||||
@@ -94,7 +87,7 @@ export default function Review() {
|
|||||||
|
|
||||||
// Load filter options when site changes (initial load with no filters)
|
// Load filter options when site changes (initial load with no filters)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadFilterOptions({ status: 'review' }); // Always pass review status
|
loadFilterOptions({ status: 'review' });
|
||||||
}, [activeSite]);
|
}, [activeSite]);
|
||||||
|
|
||||||
// Reload filter options when any filter changes (cascading filters)
|
// Reload filter options when any filter changes (cascading filters)
|
||||||
@@ -189,8 +182,6 @@ export default function Review() {
|
|||||||
window.removeEventListener('sector-changed', handleSiteChange);
|
window.removeEventListener('sector-changed', handleSiteChange);
|
||||||
};
|
};
|
||||||
}, [loadContent]);
|
}, [loadContent]);
|
||||||
|
|
||||||
// Sorting handler
|
|
||||||
const handleSort = useCallback((column: string) => {
|
const handleSort = useCallback((column: string) => {
|
||||||
if (column === sortBy) {
|
if (column === sortBy) {
|
||||||
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
|
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
|
||||||
@@ -210,14 +201,12 @@ export default function Review() {
|
|||||||
const pageConfig = useMemo(() =>
|
const pageConfig = useMemo(() =>
|
||||||
createReviewPageConfig({
|
createReviewPageConfig({
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
statusFilter,
|
|
||||||
setStatusFilter,
|
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
onRowClick: handleRowClick,
|
onRowClick: handleRowClick,
|
||||||
// Dynamic filter options
|
// Dynamic filter options
|
||||||
statusOptions,
|
|
||||||
siteStatusOptions,
|
siteStatusOptions,
|
||||||
contentTypeOptions,
|
contentTypeOptions,
|
||||||
contentStructureOptions,
|
contentStructureOptions,
|
||||||
@@ -231,10 +220,9 @@ export default function Review() {
|
|||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
statusFilter,
|
|
||||||
handleRowClick,
|
handleRowClick,
|
||||||
statusOptions,
|
|
||||||
siteStatusOptions,
|
siteStatusOptions,
|
||||||
contentTypeOptions,
|
contentTypeOptions,
|
||||||
contentStructureOptions,
|
contentStructureOptions,
|
||||||
@@ -401,6 +389,9 @@ export default function Review() {
|
|||||||
filters={pageConfig.filters}
|
filters={pageConfig.filters}
|
||||||
filterValues={{
|
filterValues={{
|
||||||
search: searchTerm,
|
search: searchTerm,
|
||||||
|
site_status: siteStatusFilter,
|
||||||
|
content_type: contentTypeFilter,
|
||||||
|
content_structure: contentStructureFilter,
|
||||||
}}
|
}}
|
||||||
primaryAction={{
|
primaryAction={{
|
||||||
label: 'Approve Selected',
|
label: 'Approve Selected',
|
||||||
@@ -412,6 +403,12 @@ export default function Review() {
|
|||||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||||
if (key === 'search') {
|
if (key === 'search') {
|
||||||
setSearchTerm(stringValue);
|
setSearchTerm(stringValue);
|
||||||
|
} else if (key === 'site_status') {
|
||||||
|
setSiteStatusFilter(stringValue);
|
||||||
|
} else if (key === 'content_type') {
|
||||||
|
setContentTypeFilter(stringValue);
|
||||||
|
} else if (key === 'content_structure') {
|
||||||
|
setContentStructureFilter(stringValue);
|
||||||
}
|
}
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
@@ -442,6 +439,9 @@ export default function Review() {
|
|||||||
headerMetrics={headerMetrics}
|
headerMetrics={headerMetrics}
|
||||||
onFilterReset={() => {
|
onFilterReset={() => {
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
|
setSiteStatusFilter('');
|
||||||
|
setContentTypeFilter('');
|
||||||
|
setContentStructureFilter('');
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
onRowAction={handleRowAction}
|
onRowAction={handleRowAction}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { DocumentTextIcon } from '@heroicons/react/24/outline';
|
|||||||
export default function Tasks() {
|
export default function Tasks() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { activeSite } = useSiteStore();
|
const { activeSite } = useSiteStore();
|
||||||
const { activeSector } = useSectorStore();
|
const { activeSector, sectors } = useSectorStore();
|
||||||
const { pageSize } = usePageSizeStore();
|
const { pageSize } = usePageSizeStore();
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
@@ -66,7 +66,6 @@ export default function Tasks() {
|
|||||||
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [contentTypeOptions, setContentTypeOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [contentStructureOptions, setContentStructureOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [clusterOptions, setClusterOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
const [clusterOptions, setClusterOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
||||||
const [sourceOptions, setSourceOptions] = useState<Array<{value: string; label: string}> | undefined>(undefined);
|
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
@@ -74,7 +73,6 @@ export default function Tasks() {
|
|||||||
const [clusterFilter, setClusterFilter] = useState('');
|
const [clusterFilter, setClusterFilter] = useState('');
|
||||||
const [structureFilter, setStructureFilter] = useState('');
|
const [structureFilter, setStructureFilter] = useState('');
|
||||||
const [typeFilter, setTypeFilter] = useState('');
|
const [typeFilter, setTypeFilter] = useState('');
|
||||||
const [sourceFilter, setSourceFilter] = useState('');
|
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
|
|
||||||
// Pagination state
|
// Pagination state
|
||||||
@@ -115,7 +113,6 @@ export default function Tasks() {
|
|||||||
content_type?: string;
|
content_type?: string;
|
||||||
content_structure?: string;
|
content_structure?: string;
|
||||||
cluster?: string;
|
cluster?: string;
|
||||||
source?: string;
|
|
||||||
search?: string;
|
search?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!activeSite) return;
|
if (!activeSite) return;
|
||||||
@@ -126,7 +123,6 @@ export default function Tasks() {
|
|||||||
setContentTypeOptions(options.content_types || []);
|
setContentTypeOptions(options.content_types || []);
|
||||||
setContentStructureOptions(options.content_structures || []);
|
setContentStructureOptions(options.content_structures || []);
|
||||||
setClusterOptions(options.clusters || []);
|
setClusterOptions(options.clusters || []);
|
||||||
setSourceOptions(options.sources || []);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading filter options:', error);
|
console.error('Error loading filter options:', error);
|
||||||
}
|
}
|
||||||
@@ -144,10 +140,9 @@ export default function Tasks() {
|
|||||||
content_type: typeFilter || undefined,
|
content_type: typeFilter || undefined,
|
||||||
content_structure: structureFilter || undefined,
|
content_structure: structureFilter || undefined,
|
||||||
cluster: clusterFilter || undefined,
|
cluster: clusterFilter || undefined,
|
||||||
source: sourceFilter || undefined,
|
|
||||||
search: searchTerm || undefined,
|
search: searchTerm || undefined,
|
||||||
});
|
});
|
||||||
}, [statusFilter, typeFilter, structureFilter, clusterFilter, sourceFilter, searchTerm, loadFilterOptions]);
|
}, [statusFilter, typeFilter, structureFilter, clusterFilter, searchTerm, loadFilterOptions]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -413,6 +408,7 @@ export default function Tasks() {
|
|||||||
return createTasksPageConfig({
|
return createTasksPageConfig({
|
||||||
clusters,
|
clusters,
|
||||||
activeSector,
|
activeSector,
|
||||||
|
sectors,
|
||||||
formData,
|
formData,
|
||||||
setFormData,
|
setFormData,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
@@ -420,16 +416,18 @@ export default function Tasks() {
|
|||||||
statusFilter,
|
statusFilter,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
clusterFilter,
|
clusterFilter,
|
||||||
sourceFilter,
|
|
||||||
setSourceFilter,
|
|
||||||
setClusterFilter,
|
setClusterFilter,
|
||||||
structureFilter,
|
structureFilter,
|
||||||
setStructureFilter,
|
setStructureFilter,
|
||||||
typeFilter,
|
typeFilter,
|
||||||
setTypeFilter,
|
setTypeFilter,
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
|
statusOptions,
|
||||||
|
contentTypeOptions,
|
||||||
|
contentStructureOptions,
|
||||||
|
clusterOptions,
|
||||||
});
|
});
|
||||||
}, [clusters, activeSector, formData, searchTerm, statusFilter, clusterFilter, structureFilter, typeFilter, sourceFilter]);
|
}, [clusters, activeSector, sectors, formData, searchTerm, statusFilter, clusterFilter, structureFilter, typeFilter, statusOptions, contentTypeOptions, contentStructureOptions, clusterOptions]);
|
||||||
|
|
||||||
// Calculate header metrics - use totals from API calls (not page data)
|
// Calculate header metrics - use totals from API calls (not page data)
|
||||||
// This ensures metrics show correct totals across all pages, not just current page
|
// This ensures metrics show correct totals across all pages, not just current page
|
||||||
@@ -524,7 +522,6 @@ export default function Tasks() {
|
|||||||
cluster_id: clusterFilter,
|
cluster_id: clusterFilter,
|
||||||
content_structure: structureFilter,
|
content_structure: structureFilter,
|
||||||
content_type: typeFilter,
|
content_type: typeFilter,
|
||||||
source: sourceFilter,
|
|
||||||
}}
|
}}
|
||||||
onFilterChange={(key, value) => {
|
onFilterChange={(key, value) => {
|
||||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||||
@@ -538,8 +535,6 @@ export default function Tasks() {
|
|||||||
setStructureFilter(stringValue);
|
setStructureFilter(stringValue);
|
||||||
} else if (key === 'content_type') {
|
} else if (key === 'content_type') {
|
||||||
setTypeFilter(stringValue);
|
setTypeFilter(stringValue);
|
||||||
} else if (key === 'source') {
|
|
||||||
setSourceFilter(stringValue);
|
|
||||||
}
|
}
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -856,7 +856,6 @@ export interface WriterTaskFilterOptions {
|
|||||||
content_types: FilterOption[];
|
content_types: FilterOption[];
|
||||||
content_structures: FilterOption[];
|
content_structures: FilterOption[];
|
||||||
clusters: FilterOption[];
|
clusters: FilterOption[];
|
||||||
sources: FilterOption[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskFilterOptionsRequest {
|
export interface TaskFilterOptionsRequest {
|
||||||
@@ -864,7 +863,6 @@ export interface TaskFilterOptionsRequest {
|
|||||||
content_type?: string;
|
content_type?: string;
|
||||||
content_structure?: string;
|
content_structure?: string;
|
||||||
cluster?: string;
|
cluster?: string;
|
||||||
source?: string;
|
|
||||||
search?: string;
|
search?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,7 +876,6 @@ export async function fetchWriterTaskFilterOptions(
|
|||||||
if (filters?.content_type) params.append('content_type', filters.content_type);
|
if (filters?.content_type) params.append('content_type', filters.content_type);
|
||||||
if (filters?.content_structure) params.append('content_structure', filters.content_structure);
|
if (filters?.content_structure) params.append('content_structure', filters.content_structure);
|
||||||
if (filters?.cluster) params.append('cluster', filters.cluster);
|
if (filters?.cluster) params.append('cluster', filters.cluster);
|
||||||
if (filters?.source) params.append('source', filters.source);
|
|
||||||
if (filters?.search) params.append('search', filters.search);
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
|
||||||
const queryString = params.toString();
|
const queryString = params.toString();
|
||||||
|
|||||||
70
frontend/src/utils/badgeColors.ts
Normal file
70
frontend/src/utils/badgeColors.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { ALL_CONTENT_STRUCTURES, STRUCTURE_LABELS } from '../config/structureMapping';
|
||||||
|
import type { Sector } from '../store/sectorStore';
|
||||||
|
|
||||||
|
const SECTOR_BADGE_PALETTE = ['brand', 'success', 'warning', 'info', 'purple'] as const;
|
||||||
|
const STRUCTURE_BADGE_PALETTE = ['brand', 'success', 'warning', 'info', 'purple'] as const;
|
||||||
|
|
||||||
|
const normalize = (value?: string | null) => (value || '').trim().toLowerCase();
|
||||||
|
|
||||||
|
const hashString = (value: string) => {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < value.length; i += 1) {
|
||||||
|
hash = (hash << 5) - hash + value.charCodeAt(i);
|
||||||
|
hash |= 0;
|
||||||
|
}
|
||||||
|
return Math.abs(hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSectorBadgeColor = (
|
||||||
|
sectorId?: number | null,
|
||||||
|
sectorName?: string | null,
|
||||||
|
sectors?: Sector[]
|
||||||
|
): typeof SECTOR_BADGE_PALETTE[number] | 'neutral' => {
|
||||||
|
let resolvedId = sectorId ?? null;
|
||||||
|
|
||||||
|
if (!resolvedId && sectorName && sectors && sectors.length > 0) {
|
||||||
|
const match = sectors.find((sector) => normalize(sector.name) === normalize(sectorName));
|
||||||
|
if (match) {
|
||||||
|
resolvedId = match.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderedIds = sectors && sectors.length > 0
|
||||||
|
? [...new Set(sectors.map((sector) => sector.id))].sort((a, b) => a - b)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (resolvedId && orderedIds.length > 0) {
|
||||||
|
const index = orderedIds.indexOf(resolvedId);
|
||||||
|
if (index >= 0) {
|
||||||
|
return SECTOR_BADGE_PALETTE[index % SECTOR_BADGE_PALETTE.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedId) {
|
||||||
|
return SECTOR_BADGE_PALETTE[Math.abs(resolvedId) % SECTOR_BADGE_PALETTE.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectorName) {
|
||||||
|
const index = hashString(sectorName) % SECTOR_BADGE_PALETTE.length;
|
||||||
|
return SECTOR_BADGE_PALETTE[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'neutral';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStructureBadgeColor = (
|
||||||
|
structure?: string | null
|
||||||
|
): typeof STRUCTURE_BADGE_PALETTE[number] | 'neutral' => {
|
||||||
|
const structureKey = structure || '';
|
||||||
|
const structureValues = ALL_CONTENT_STRUCTURES.map((item) => item.value);
|
||||||
|
const index = structureValues.indexOf(structureKey);
|
||||||
|
if (index >= 0) {
|
||||||
|
return STRUCTURE_BADGE_PALETTE[index % STRUCTURE_BADGE_PALETTE.length];
|
||||||
|
}
|
||||||
|
return 'neutral';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStructureLabel = (structure?: string | null) => {
|
||||||
|
if (!structure) return '-';
|
||||||
|
return STRUCTURE_LABELS[structure] || structure.replace(/_/g, ' ');
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user