/** * BoostMedia AI Content Generator Admin - Steps Progress Component * * @package BoostMedia_AI * @license GPL-2.0-or-later */ import { useEffect, useMemo, useState } from 'react' import { Check } from 'lucide-react' import type { GenerationConfig } from './SelectPostTypeStep' import { endpoints } from '../../api/client' import { t, tf } from '../../lib/i18n' export type GeneratorStep = | 'settings' | 'select' | 'reporter' | 'configure' | 'technical' | 'content' | 'intent' | 'sprint' | 'article-review' | 'generate' | 'review' interface Step { id: GeneratorStep label: string description: string } interface StepsProgressProps { currentStep: GeneratorStep config?: GenerationConfig reporterLabel?: string | null useSprintMode?: boolean } const lengthLabels: Record = { auto: 'AI decides', short: 'Short', medium: 'Medium', long: 'Long', } interface SummaryPart { key: string label: string value: string expandable?: boolean } export function StepsProgress({ currentStep, config, reporterLabel, useSprintMode = false }: StepsProgressProps) { const steps: Step[] = useMemo(() => { const base: Step[] = [ { id: 'settings', label: t('Settings'), description: t('Post type, reporter, plan') }, { id: 'technical', label: t('Technical'), description: t('Field structure') }, ] if (useSprintMode) { return [ ...base, { id: 'intent', label: t('Intent'), description: t('Goals & scope') }, ] } return [ ...base, { id: 'content', label: t('Content Planning'), description: t('Plan topics') }, ] }, [useSprintMode]) const normalizedStep = (['select', 'reporter', 'configure'] as string[]).includes(currentStep) ? 'settings' : currentStep const currentIndex = steps.findIndex((s) => s.id === normalizedStep) const [selectedTermNames, setSelectedTermNames] = useState([]) const [showAllCategories, setShowAllCategories] = useState(false) const [showCategoriesPopover, setShowCategoriesPopover] = useState(false) useEffect(() => { let cancelled = false if (!config?.postType || !config.taxonomy || !config.term) { setSelectedTermNames([]) return () => { cancelled = true } } void endpoints .getTaxonomyTerms(config.postType, config.taxonomy) .then((res) => { if (cancelled) { return } const selectedSlugs = config.term.split(',').filter(Boolean) const names = ((res.data as Array<{ slug: string; name: string }>) || []) .filter((term) => selectedSlugs.includes(term.slug)) .map((term) => term.name) setSelectedTermNames(names) }) .catch(() => { if (!cancelled) { setSelectedTermNames(config.term.split(',').filter(Boolean)) } }) return () => { cancelled = true } }, [config?.postType, config?.taxonomy, config?.term]) const summaryParts: SummaryPart[] = [] if (config) { if (config.postType && currentIndex > 0) { summaryParts.push({ key: 'postType', label: t('Type'), value: config.postType }) } if (config.term && currentIndex > 0) { const termCount = config.term.split(',').filter(Boolean).length const categoryNames = selectedTermNames.length > 0 ? selectedTermNames : config.term.split(',').filter(Boolean) summaryParts.push({ key: 'categories', label: termCount === 1 ? t('Category') : t('Categories'), value: categoryNames.join(', '), expandable: termCount > 1, }) } if (config.planName && currentIndex > 0) { summaryParts.push({ key: 'planName', label: t('Plan'), value: config.planName }) } if (reporterLabel && currentIndex > 0) { summaryParts.push({ key: 'reporter', label: t('Reporter'), value: reporterLabel }) } if (config.topic && currentIndex > 0) { summaryParts.push({ key: 'topic', label: t('Topic'), value: config.topic }) } if (config.keywords.length > 0 && currentIndex > 0) { summaryParts.push({ key: 'keywords', label: t('Keywords'), value: config.keywords.join(', ') }) } if (config.count && currentIndex > 0) { summaryParts.push({ key: 'count', label: t('Posts'), value: tf('%s posts', config.count) }) } if (config.length && currentIndex > 0) { summaryParts.push({ key: 'length', label: t('Length'), value: t(lengthLabels[config.length] || config.length) }) } if (config.generationType && currentIndex > 0) { summaryParts.push({ key: 'generationType', label: t('Plan Type'), value: config.generationType === 'repeating' ? t('Repeating plan') : t('One-time plan'), }) } if (config.generationType === 'repeating' && currentIndex > 0) { summaryParts.push({ key: 'repeatFrequency', label: t('Frequency'), value: `${t('Every')} ${config.repeatEvery > 1 ? `${config.repeatEvery} ` : ''}${t( config.repeatUnit === 'month' ? (config.repeatEvery === 1 ? 'month' : 'months') : config.repeatUnit === 'day' ? (config.repeatEvery === 1 ? 'day' : 'days') : (config.repeatEvery === 1 ? 'week' : 'weeks'), )} • ${String(config.repeatHour).padStart(2, '0')}:00`, }) summaryParts.push({ key: 'isActive', label: t('Status'), value: config.isActive ? t('Active') : t('Inactive'), }) if (config.remainingRuns !== null) { summaryParts.push({ key: 'remainingRuns', label: t('Runs Left'), value: String(config.remainingRuns), }) } if (config.remainingPosts !== null) { summaryParts.push({ key: 'remainingPosts', label: t('Posts Left'), value: String(config.remainingPosts), }) } } } return (
{/* Config summary */} {summaryParts.length > 0 && (
{summaryParts.map((part) => (
{part.key === 'categories' && showCategoriesPopover && part.expandable && (
{t('Selected categories')}
{selectedTermNames.length > 5 ? ( ) : null}
{(showAllCategories ? selectedTermNames : selectedTermNames.slice(0, 5)).map((name) => (
{name}
))}
)}
))}
)}
) }