Always version 0.5.4

This commit is contained in:
ajaysi
2025-08-07 20:06:26 +05:30
parent 3670d0b5a0
commit c5b54786f8
23 changed files with 2176 additions and 1496 deletions

View File

@@ -1021,27 +1021,30 @@ class EnhancedStrategyService:
}
# Audience Intelligence Fields
audience_data = research_data.get('audience_intelligence', {})
# Extract audience data from research_data structure
audience_research = research_data.get('audience_research', {})
content_prefs = research_data.get('content_preferences', {})
fields['content_preferences'] = {
'value': research_data.get('content_preferences', {}),
'value': content_prefs,
'source': 'research_preferences',
'confidence': research_data.get('confidence_level', 0.8)
}
fields['consumption_patterns'] = {
'value': audience_data.get('consumption_patterns', {}),
'value': audience_research.get('consumption_patterns', {}),
'source': 'research_preferences',
'confidence': research_data.get('confidence_level', 0.8)
}
fields['audience_pain_points'] = {
'value': audience_data.get('pain_points', []),
'value': audience_research.get('audience_pain_points', []),
'source': 'research_preferences',
'confidence': research_data.get('confidence_level', 0.8)
}
fields['buying_journey'] = {
'value': audience_data.get('buying_journey', {}),
'value': audience_research.get('buying_journey', {}),
'source': 'research_preferences',
'confidence': research_data.get('confidence_level', 0.8)
}
@@ -1064,7 +1067,11 @@ class EnhancedStrategyService:
# Competitive Intelligence Fields
fields['top_competitors'] = {
'value': website_data.get('competitors', []),
'value': website_data.get('competitors', [
'Competitor A - Industry Leader',
'Competitor B - Emerging Player',
'Competitor C - Niche Specialist'
]),
'source': 'website_analysis',
'confidence': website_data.get('confidence_level', 0.8)
}
@@ -1094,9 +1101,10 @@ class EnhancedStrategyService:
}
# Content Strategy Fields
content_prefs = research_data.get('content_preferences', {})
fields['preferred_formats'] = {
'value': content_prefs.get('preferred_formats', []),
'value': content_prefs.get('preferred_formats', [
'Blog posts', 'Whitepapers', 'Webinars', 'Case studies', 'Videos'
]),
'source': 'research_preferences',
'confidence': research_data.get('confidence_level', 0.8)
}

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "/static/css/main.c9966057.css",
"main.js": "/static/js/main.cb1b37a5.js",
"main.js": "/static/js/main.28afa9ad.js",
"index.html": "/index.html",
"main.c9966057.css.map": "/static/css/main.c9966057.css.map",
"main.cb1b37a5.js.map": "/static/js/main.cb1b37a5.js.map"
"main.28afa9ad.js.map": "/static/js/main.28afa9ad.js.map"
},
"entrypoints": [
"static/css/main.c9966057.css",
"static/js/main.cb1b37a5.js"
"static/js/main.28afa9ad.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.cb1b37a5.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.28afa9ad.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,705 @@
import React, { useState, useEffect, useRef } from 'react';
import {
Box,
Paper,
Typography,
Button,
LinearProgress,
Alert,
Chip,
IconButton,
Tooltip as MuiTooltip,
Card,
CardContent,
Grid,
Divider,
CircularProgress,
Badge,
Collapse,
Accordion,
AccordionSummary,
AccordionDetails,
List,
ListItem,
ListItemIcon,
ListItemText,
Dialog,
DialogTitle,
DialogContent,
DialogActions
} from '@mui/material';
import {
Business as BusinessIcon,
People as PeopleIcon,
TrendingUp as TrendingUpIcon,
ContentPaste as ContentIcon,
Analytics as AnalyticsIcon,
Help as HelpIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
AutoAwesome as AutoAwesomeIcon,
Refresh as RefreshIcon,
Save as SaveIcon,
ArrowForward as ArrowForwardIcon,
ArrowBack as ArrowBackIcon,
Assessment as AssessmentIcon,
ExpandMore as ExpandMoreIcon,
Info as InfoIcon,
Visibility as VisibilityIcon,
School as SchoolIcon,
Lightbulb as LightbulbIcon,
Psychology as PsychologyIcon,
Timeline as TimelineIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import { useEnhancedStrategyStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/enhancedStrategyStore';
import StrategicInputField from './ContentStrategyBuilder/StrategicInputField';
import EnhancedTooltip from './ContentStrategyBuilder/EnhancedTooltip';
import AIRecommendationsPanel from './AIRecommendationsPanel';
import DataSourceTransparency from './DataSourceTransparency';
// Import extracted hooks
import { useCategoryReview } from './ContentStrategyBuilder/hooks/useCategoryReview';
import { useProgressTracking } from './ContentStrategyBuilder/hooks/useProgressTracking';
import { useAutoPopulation } from './ContentStrategyBuilder/hooks/useAutoPopulation';
// Import extracted utilities
import { getCategoryIcon, getCategoryColor, getCategoryName, getCategoryStatus } from './ContentStrategyBuilder/utils/categoryHelpers';
import { getEducationalContent } from './ContentStrategyBuilder/utils/educationalContent';
// Import extracted components
import CategoryList from './ContentStrategyBuilder/components/CategoryList';
import ProgressTracker from './ContentStrategyBuilder/components/ProgressTracker';
import HeaderSection from './ContentStrategyBuilder/components/HeaderSection';
const ContentStrategyBuilder: React.FC = () => {
const {
formData,
formErrors,
autoPopulatedFields,
dataSources,
loading,
error,
saving,
aiGenerating,
currentStep,
completedSteps,
disclosureSteps,
currentStrategy,
updateFormField,
validateFormField,
validateAllFields,
completeStep,
getNextStep,
getPreviousStep,
setCurrentStep,
canProceedToStep,
resetForm,
autoPopulateFromOnboarding,
generateAIRecommendations,
createEnhancedStrategy,
calculateCompletionPercentage,
getCompletionStats,
setError,
setCurrentStrategy,
setAIGenerating,
setSaving
} = useEnhancedStrategyStore();
const [showTooltip, setShowTooltip] = useState<string | null>(null);
const [activeCategory, setActiveCategory] = useState<string | null>(null);
const [showEducationalInfo, setShowEducationalInfo] = useState<string | null>(null);
const [showAIRecommendations, setShowAIRecommendations] = useState(false);
const [showDataSourceTransparency, setShowDataSourceTransparency] = useState(false);
// Ref to track if we've already set the default category
const hasSetDefaultCategory = useRef(false);
const completionStats = getCompletionStats();
const completionPercentage = calculateCompletionPercentage();
// Use extracted hooks
const {
reviewedCategories,
isMarkingReviewed,
categoryCompletionMessage,
handleConfirmCategoryReview,
isCategoryReviewed,
getNextUnreviewedCategory,
setReviewedCategories
} = useCategoryReview({ completionStats, setError, setActiveCategory });
const {
totalCategories,
reviewedCategoriesCount,
reviewProgressPercentage,
getCategoryProgress,
getCategoryStatus: getCategoryStatusFromHook,
isNextInSequence
} = useProgressTracking({ completionStats, reviewedCategories });
const { autoPopulateAttempted, setAutoPopulateAttempted } = useAutoPopulation({
autoPopulateFromOnboarding,
completionStats
});
// Auto-populate from onboarding on first load
useEffect(() => {
if (!autoPopulateAttempted) {
autoPopulateFromOnboarding();
}
}, [autoPopulateAttempted, autoPopulateFromOnboarding]);
// Set default category selection
useEffect(() => {
// Only set default category once when component mounts and we have categories
if (hasSetDefaultCategory.current) {
console.log('🔍 Default category useEffect: SKIPPED - already set default');
return;
}
if (Object.keys(completionStats.category_completion).length > 0) {
const firstCategory = Object.keys(completionStats.category_completion)[0];
console.log('🎯 Setting default category:', firstCategory);
setActiveCategory(firstCategory);
hasSetDefaultCategory.current = true;
console.log('✅ hasSetDefaultCategory set to true');
}
}, [completionStats.category_completion]); // Removed activeCategory dependency
// Debug activeCategory changes
useEffect(() => {
console.log('🔄 activeCategory changed to:', activeCategory);
console.trace('📍 Stack trace for activeCategory change');
}, [activeCategory]);
// Add CSS keyframes for pulse animation
useEffect(() => {
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
return () => {
if (document.head.contains(style)) {
document.head.removeChild(style);
}
};
}, []);
const handleCreateStrategy = async () => {
try {
setAIGenerating(true);
setError(null);
console.log('Starting strategy creation...');
console.log('Current formData:', formData);
console.log('FormData ID:', formData.id);
// If we have a saved strategy, use its ID
if (formData.id) {
console.log('Using existing strategy ID:', formData.id);
await generateAIRecommendations(formData.id);
} else {
console.log('No strategy ID found, creating new strategy...');
// If no strategy is saved yet, save it first, then generate AI insights
const isValid = validateAllFields();
console.log('Form validation result:', isValid);
if (isValid) {
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1, // This would come from auth context
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
console.log('Attempting to create strategy with data:', strategyData);
const newStrategy = await createEnhancedStrategy(strategyData);
console.log('New strategy created:', newStrategy);
if (newStrategy && newStrategy.id) {
console.log('Generating AI recommendations for new strategy ID:', newStrategy.id);
await generateAIRecommendations(newStrategy.id);
// Set the current strategy and show success message
setCurrentStrategy(newStrategy);
setError(null); // Clear any previous errors
// Show success message
setTimeout(() => {
setError('Strategy created successfully! Check the Strategic Intelligence tab for detailed insights.');
}, 100);
// Auto-switch to Strategic Intelligence tab after creation
// This would need to be handled by the parent component
} else {
setError('Failed to create strategy or get strategy ID for AI generation.');
console.error('Failed to create strategy or get strategy ID for AI generation.');
}
} else {
setError('Please fill in all required fields before generating AI insights.');
console.error('Form validation failed. Cannot generate AI insights.');
}
}
} catch (err: any) {
setError(`Error generating AI recommendations: ${err.message || 'Unknown error'}`);
console.error('Error in handleCreateStrategy:', err);
} finally {
setAIGenerating(false);
}
};
const handleSaveStrategy = async () => {
try {
setSaving(true);
setError(null);
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1,
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
const newStrategy = await createEnhancedStrategy(strategyData);
setCurrentStrategy(newStrategy);
setError('Strategy saved successfully!');
} catch (err: any) {
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
} finally {
setSaving(false);
}
};
const handleReviewCategory = (categoryId: string) => {
setActiveCategory(activeCategory === categoryId ? null : categoryId);
};
const handleShowEducationalInfo = (categoryId: string) => {
setShowEducationalInfo(showEducationalInfo === categoryId ? null : categoryId);
};
// Wrapper for the hook function to maintain the same interface
const handleConfirmCategoryReviewWrapper = () => {
console.log('🔧 Wrapper called with activeCategory:', activeCategory);
handleConfirmCategoryReview(activeCategory);
};
return (
<Box sx={{ p: 3 }}>
{/* Header with Title (Region B) - Enhanced with Futuristic Styling */}
<HeaderSection autoPopulatedFields={autoPopulatedFields} />
{/* Error Alert */}
{error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
)}
{/* Success Alert */}
{!error && currentStrategy && (
<Alert severity="success" sx={{ mb: 3 }}>
Strategy "{currentStrategy.name}" created successfully! Check the Strategic Intelligence tab for detailed insights.
</Alert>
)}
{/* Strategy Display */}
{currentStrategy && (
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h5" gutterBottom>
Created Strategy: {currentStrategy.name}
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Industry: {currentStrategy.industry}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Completion: {currentStrategy.completion_percentage}%
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Created: {new Date(currentStrategy.created_at).toLocaleDateString()}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
ID: {currentStrategy.id}
</Typography>
</Grid>
</Grid>
<Box sx={{ mt: 2 }}>
<Button
variant="outlined"
onClick={() => window.location.href = '/content-planning?tab=strategic-intelligence'}
startIcon={<AssessmentIcon />}
>
View Strategic Intelligence
</Button>
</Box>
</Paper>
)}
{categoryCompletionMessage && (
<Alert
severity="success"
sx={{ mb: 3, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
{categoryCompletionMessage}
</Alert>
)}
<Grid container spacing={3}>
{/* Category Overview Panel */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, height: 'fit-content', position: 'sticky', top: 20 }}>
{/* Enhanced Completion Tracker - Integrated into Category List */}
<ProgressTracker
reviewProgressPercentage={reviewProgressPercentage}
reviewedCategoriesCount={reviewedCategoriesCount}
totalCategories={totalCategories}
autoPopulatedFields={autoPopulatedFields}
aiGenerating={aiGenerating}
onShowAIRecommendations={() => setShowAIRecommendations(true)}
onShowDataSourceTransparency={() => setShowDataSourceTransparency(true)}
onRefreshData={autoPopulateFromOnboarding}
/>
{/* Category Progress - Compact with Futuristic Styling */}
<Typography variant="h6" gutterBottom sx={{ mb: 1.5, fontSize: '1rem' }}>
Category Progress
</Typography>
<CategoryList
completionStats={completionStats}
formData={formData}
STRATEGIC_INPUT_FIELDS={STRATEGIC_INPUT_FIELDS}
activeCategory={activeCategory}
reviewedCategories={reviewedCategories}
isMarkingReviewed={isMarkingReviewed}
isNextInSequence={isNextInSequence}
onReviewCategory={handleReviewCategory}
onShowEducationalInfo={handleShowEducationalInfo}
/>
{/* Quick Actions */}
<Box sx={{ mt: 3, pt: 2, borderTop: 1, borderColor: 'divider' }}>
<Typography variant="subtitle2" gutterBottom>
Quick Actions
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Button
size="small"
variant="outlined"
startIcon={<AutoAwesomeIcon />}
onClick={() => setShowAIRecommendations(true)}
fullWidth
>
View AI Insights
</Button>
<Button
size="small"
variant="outlined"
startIcon={<InfoIcon />}
onClick={() => setShowDataSourceTransparency(true)}
fullWidth
>
View Data Sources
</Button>
<Button
size="small"
variant="outlined"
startIcon={<RefreshIcon />}
onClick={autoPopulateFromOnboarding}
fullWidth
>
Refresh Data
</Button>
</Box>
</Box>
</Paper>
</Grid>
{/* Main Content Area */}
<Grid item xs={12} md={8}>
<Paper sx={{ p: 3, minHeight: '600px' }}>
{activeCategory ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{/* Category Header */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
{getCategoryIcon(activeCategory)}
<Typography variant="h5" sx={{ ml: 1 }}>
{activeCategory.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}
</Typography>
<Chip
label={`${Math.round(completionStats.category_completion[activeCategory])}% Complete`}
color={getCategoryColor(activeCategory) as any}
sx={{ ml: 'auto' }}
/>
</Box>
{/* Educational Info Dialog */}
<Dialog
open={!!showEducationalInfo}
onClose={() => setShowEducationalInfo(null)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SchoolIcon />
{showEducationalInfo && getEducationalContent(showEducationalInfo).title}
</Box>
</DialogTitle>
<DialogContent>
<Typography variant="body1" paragraph>
{showEducationalInfo && getEducationalContent(showEducationalInfo).description}
</Typography>
<Typography variant="h6" gutterBottom>
Key Points:
</Typography>
<List>
{showEducationalInfo && getEducationalContent(showEducationalInfo).points.map((point, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<LightbulbIcon color="primary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={point} />
</ListItem>
))}
</List>
<Typography variant="h6" gutterBottom>
Pro Tips:
</Typography>
<List>
{showEducationalInfo && getEducationalContent(showEducationalInfo).tips.map((tip, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<PsychologyIcon color="secondary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={tip} />
</ListItem>
))}
</List>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowEducationalInfo(null)}>
Got it!
</Button>
</DialogActions>
</Dialog>
{/* Category Fields */}
<Grid container spacing={1.5}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field) => {
// Group number-based fields together
const isNumberField = field.type === 'number' ||
field.id.includes('budget') ||
field.id.includes('size') ||
field.id.includes('timeline') ||
field.id.includes('metrics');
// Determine grid size based on field type
const gridSize = isNumberField ? 6 : 12;
return (
<Grid item xs={12} md={gridSize} key={field.id}>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={autoPopulatedFields[field.id] ? 0.8 : undefined}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
onChange={(value: any) => updateFormField(field.id, value)}
onValidate={() => validateFormField(field.id)}
onShowTooltip={() => setShowTooltip(field.id)}
/>
</Grid>
);
})}
</Grid>
{/* Category Actions */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
{(() => {
const isReviewed = reviewedCategories.has(activeCategory);
console.log('🔍 Category review status:', {
activeCategory,
isReviewed,
reviewedCategories: Array.from(reviewedCategories)
});
return !isReviewed ? (
<Button
variant="contained"
onClick={() => {
console.log('🔘 Button clicked! activeCategory:', activeCategory);
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
handleConfirmCategoryReviewWrapper();
}}
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
disabled={isMarkingReviewed}
>
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
</Button>
) : (
<Chip
label="Category Reviewed"
color="success"
icon={<CheckCircleIcon />}
sx={{ px: 2, py: 1 }}
/>
);
})()}
<Button
variant="outlined"
onClick={() => setActiveCategory(null)}
>
Back to Overview
</Button>
</Box>
</motion.div>
) : (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<Box sx={{ textAlign: 'center', py: 8 }}>
<TimelineIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h5" gutterBottom>
Select a Category to Review
</Typography>
<Typography variant="body1" color="text.secondary">
Click on any category from the left panel to review and complete the fields.
</Typography>
</Box>
</motion.div>
)}
</Paper>
</Grid>
</Grid>
{/* Action Buttons */}
<Box sx={{ mt: 3, display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
<MuiTooltip
title={reviewProgressPercentage < 20 ? `Complete at least 20% of the form (currently ${Math.round(reviewProgressPercentage)}%)` : 'Create a comprehensive content strategy with AI insights'}
placement="top"
>
<span>
<Button
variant="outlined"
startIcon={<AutoAwesomeIcon />}
onClick={handleCreateStrategy}
disabled={aiGenerating || reviewProgressPercentage < 20}
>
{aiGenerating ? 'Creating...' : 'Create Strategy'}
</Button>
</span>
</MuiTooltip>
<Button
variant="contained"
startIcon={<SaveIcon />}
onClick={handleSaveStrategy}
disabled={saving || reviewProgressPercentage < 30}
>
{saving ? 'Saving...' : 'Save Strategy'}
</Button>
</Box>
{/* AI Recommendations Modal */}
<Dialog
open={showAIRecommendations}
onClose={() => setShowAIRecommendations(false)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon />
AI Recommendations & Insights
</Box>
</DialogTitle>
<DialogContent>
<AIRecommendationsPanel
aiGenerating={aiGenerating}
onGenerateRecommendations={handleCreateStrategy}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowAIRecommendations(false)}>
Close
</Button>
</DialogActions>
</Dialog>
{/* Data Source Transparency Modal */}
<Dialog
open={showDataSourceTransparency}
onClose={() => setShowDataSourceTransparency(false)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<InfoIcon />
Data Source Transparency
</Box>
</DialogTitle>
<DialogContent>
<DataSourceTransparency
autoPopulatedFields={autoPopulatedFields}
dataSources={dataSources}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowDataSourceTransparency(false)}>
Close
</Button>
</DialogActions>
</Dialog>
{/* Tooltip */}
{showTooltip && (
<EnhancedTooltip
fieldId={showTooltip}
open={!!showTooltip}
onClose={() => setShowTooltip(null)}
/>
)}
</Box>
);
};
export default ContentStrategyBuilder;

View File

@@ -27,7 +27,7 @@ import {
DataUsage as DataUsageIcon,
Close as CloseIcon
} from '@mui/icons-material';
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
interface EnhancedTooltipProps {
fieldId: string;

View File

@@ -21,9 +21,10 @@ import {
AutoAwesome as AutoAwesomeIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Edit as EditIcon
Edit as EditIcon,
Info as InfoIcon
} from '@mui/icons-material';
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
interface StrategicInputFieldProps {
fieldId: string;
@@ -31,6 +32,8 @@ interface StrategicInputFieldProps {
error?: string;
autoPopulated?: boolean;
dataSource?: string;
confidenceLevel?: number;
dataQuality?: string;
onChange: (value: any) => void;
onValidate: () => boolean;
onShowTooltip: () => void;
@@ -71,6 +74,8 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
error,
autoPopulated = false,
dataSource,
confidenceLevel,
dataQuality,
onChange,
onValidate,
onShowTooltip
@@ -198,10 +203,10 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
required: false
},
preferred_formats: {
type: 'json',
type: 'multiselect',
label: 'Preferred Formats',
placeholder: 'Define preferred content formats',
required: false
options: ['Blog Posts', 'Videos', 'Infographics', 'Webinars', 'Podcasts', 'Case Studies', 'Whitepapers', 'Social Media Posts'],
required: true
},
content_mix: {
type: 'json',
@@ -477,44 +482,129 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
};
return (
<Box sx={{ position: 'relative' }}>
{/* Auto-population indicator */}
{autoPopulated && (
<Box sx={{ mb: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip
icon={<AutoAwesomeIcon />}
label={`Auto-populated from ${dataSource}`}
color="info"
size="small"
variant="outlined"
/>
{!isEditing && (
<Tooltip title="Edit auto-populated value">
<IconButton size="small" onClick={() => setIsEditing(true)}>
<EditIcon fontSize="small" />
</IconButton>
</Tooltip>
<Box sx={{
position: 'relative',
mb: 1.5,
p: 1.5,
borderRadius: 1.5,
bgcolor: 'background.paper',
border: '1px solid',
borderColor: error ? 'error.main' : autoPopulated ? 'info.main' : 'divider',
'&:hover': {
borderColor: 'primary.main',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}
}}>
{/* Field input - Enhanced styling */}
<Box sx={{
'& .MuiTextField-root, & .MuiFormControl-root': {
'& .MuiInputBase-root': {
borderRadius: 1,
'&:hover': {
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'primary.main'
}
}
},
'& .MuiInputLabel-root': {
fontSize: '0.8rem',
fontWeight: 500
},
'& .MuiInputBase-input': {
fontSize: '0.85rem',
padding: '8px 12px'
}
}
}}>
{renderInput()}
</Box>
{/* Data Transparency and Auto-population Indicators */}
<Box sx={{
mt: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexWrap: 'wrap',
gap: 1
}}>
{/* Left side - Validation and Quality indicators */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{/* Validation status */}
{value && !error && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<CheckCircleIcon color="success" sx={{ fontSize: 14 }} />
<Typography variant="caption" color="success.main" sx={{ fontSize: '0.7rem' }}>
Valid
</Typography>
</Box>
)}
{/* Data Quality indicator */}
{dataQuality && (
<Chip
icon={<InfoIcon sx={{ fontSize: 12 }} />}
label={dataQuality}
size="small"
variant="outlined"
color="primary"
sx={{
fontSize: '0.6rem',
height: 20,
'& .MuiChip-label': { px: 1 }
}}
/>
)}
{/* Confidence Level indicator */}
{confidenceLevel && (
<Chip
label={`${Math.round(confidenceLevel * 100)}% confidence`}
size="small"
variant="outlined"
color={confidenceLevel > 0.8 ? 'success' : confidenceLevel > 0.6 ? 'warning' : 'error'}
sx={{
fontSize: '0.6rem',
height: 20,
'& .MuiChip-label': { px: 1 }
}}
/>
)}
</Box>
)}
{/* Field input */}
{renderInput()}
{/* Validation status */}
{value && !error && (
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
<CheckCircleIcon color="success" fontSize="small" />
<Typography variant="caption" color="success.main">
Valid
</Typography>
</Box>
)}
{/* Right side - Auto-population indicator */}
{autoPopulated && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Chip
icon={<AutoAwesomeIcon sx={{ fontSize: 12 }} />}
label={`Auto-populated from ${dataSource}`}
color="info"
size="small"
variant="outlined"
sx={{
fontSize: '0.6rem',
height: 20,
'& .MuiChip-label': { px: 1 }
}}
/>
{!isEditing && (
<Tooltip title="Edit auto-populated value">
<IconButton size="small" onClick={() => setIsEditing(true)} sx={{ width: 20, height: 20 }}>
<EditIcon sx={{ fontSize: 12 }} />
</IconButton>
</Tooltip>
)}
</Box>
)}
</Box>
{/* Error display */}
{error && (
<Alert severity="error" sx={{ mt: 1 }}>
<Typography variant="body2">{error}</Typography>
<Alert severity="error" sx={{ mt: 1, py: 0.5, '& .MuiAlert-message': { py: 0 } }}>
<Typography variant="caption" sx={{ fontSize: '0.7rem' }}>
{error}
</Typography>
</Alert>
)}
</Box>

View File

@@ -0,0 +1,255 @@
import React from 'react';
import {
List,
ListItem,
ListItemIcon,
ListItemText,
Chip,
Box,
Typography,
Button,
IconButton,
CircularProgress
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
Warning as WarningIcon,
Visibility as VisibilityIcon,
School as SchoolIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { getCategoryIcon, getCategoryName, getCategoryStatus } from '../utils/categoryHelpers';
interface CategoryListProps {
completionStats: any;
formData: any;
STRATEGIC_INPUT_FIELDS: any[];
activeCategory: string | null;
reviewedCategories: Set<string>;
isMarkingReviewed: boolean;
isNextInSequence: (categoryId: string, allCategories: string[]) => boolean;
onReviewCategory: (categoryId: string) => void;
onShowEducationalInfo: (categoryId: string) => void;
}
const CategoryList: React.FC<CategoryListProps> = ({
completionStats,
formData,
STRATEGIC_INPUT_FIELDS,
activeCategory,
reviewedCategories,
isMarkingReviewed,
isNextInSequence,
onReviewCategory,
onShowEducationalInfo
}) => {
return (
<List sx={{ p: 0 }}>
{Object.entries(completionStats.category_completion).map(([categoryId, percentage]) => {
const categoryName = getCategoryName(categoryId);
const percentageValue = percentage as number;
// Get category-specific stats
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(f => f.category === categoryId);
const filledFields = categoryFields.filter(field => formData[field.id]).length;
const totalFields = categoryFields.length;
const categoryStatus = getCategoryStatus(percentageValue);
const isSelected = activeCategory === categoryId;
const isDefault = Object.keys(completionStats.category_completion)[0] === categoryId;
const isReviewed = reviewedCategories.has(categoryId);
// Find the next category in sequence for guidance
const allCategories = Object.keys(completionStats.category_completion);
const isNextInSequenceCategory = isNextInSequence(categoryId, allCategories);
return (
<motion.div
key={categoryId}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<ListItem
sx={{
p: 1.5, // 50% more compact padding
mb: 0.5, // Reduced margin
borderRadius: 2,
bgcolor: isSelected ? 'action.hover' : isNextInSequenceCategory ? 'rgba(25, 118, 210, 0.08)' : 'transparent',
border: isSelected ? '2px solid' : isNextInSequenceCategory ? '1px solid' : '1px solid',
borderColor: isSelected ? 'primary.main' : isNextInSequenceCategory ? 'primary.main' : 'divider',
flexDirection: 'column',
alignItems: 'stretch',
position: 'relative',
overflow: 'hidden',
// Futuristic styling
background: isSelected ? 'linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)' : 'transparent',
backdropFilter: isSelected ? 'blur(10px)' : 'none',
// Shimmer animation for default category
'&::before': isDefault && !isSelected ? {
content: '""',
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
animation: 'shimmer 2s infinite',
zIndex: 0
} : {},
'&:hover': {
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}
}}
>
{/* Category Header - Compact */}
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', mb: 0.5, position: 'relative', zIndex: 1 }}>
<ListItemIcon sx={{ minWidth: 32 }}>
{getCategoryIcon(categoryId)}
</ListItemIcon>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{categoryName}
{isNextInSequenceCategory && (
<Chip
label="Next"
size="small"
color="primary"
sx={{
height: 16,
fontSize: '0.6rem',
'& .MuiChip-label': { px: 0.5 }
}}
/>
)}
</Box>
}
secondary={`${Math.round(percentageValue)}% complete`}
sx={{
flex: 1,
'& .MuiListItemText-primary': { fontSize: '0.9rem', fontWeight: 500 },
'& .MuiListItemText-secondary': { fontSize: '0.7rem' }
}}
/>
<Chip
label={isReviewed ? 'Reviewed' : categoryStatus.status}
color={isReviewed ? 'success' : categoryStatus.color}
size="small"
sx={{
mr: 0.5,
height: 20,
fontSize: '0.6rem',
'& .MuiChip-label': { px: 1 }
}}
/>
</Box>
{/* Category Progress Bar - Compact Circular */}
<Box sx={{ mb: 0.5, position: 'relative', zIndex: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={percentageValue}
size={24}
thickness={3}
sx={{
color: 'primary.main',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography
variant="caption"
component="div"
color="text.secondary"
sx={{ fontSize: '0.5rem', fontWeight: 'bold' }}
>
{`${Math.round(percentageValue)}%`}
</Typography>
</Box>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.6rem' }}>
{filledFields}/{totalFields} fields
</Typography>
</Box>
{/* Category Actions - Compact */}
<Box sx={{ display: 'flex', gap: 0.5, justifyContent: 'space-between', alignItems: 'center', position: 'relative', zIndex: 1 }}>
<Box sx={{ display: 'flex', gap: 0.5 }}>
{/* Review Button - Compact */}
<Button
size="small"
variant="outlined"
startIcon={
isMarkingReviewed && activeCategory === categoryId ?
<CircularProgress size={14} /> :
<VisibilityIcon sx={{ fontSize: 14 }} />
}
onClick={() => onReviewCategory(categoryId)}
disabled={isMarkingReviewed && activeCategory === categoryId}
sx={{
minWidth: 'auto',
height: 24,
fontSize: '0.6rem',
px: 1,
'& .MuiButton-startIcon': { mr: 0.5 }
}}
>
{isMarkingReviewed && activeCategory === categoryId ?
'Marking...' :
(isReviewed ? 'Reviewed' : 'Review')
}
</Button>
{/* Educational Info Button - Compact */}
<IconButton
size="small"
onClick={() => onShowEducationalInfo(categoryId)}
sx={{
color: 'primary.main',
width: 24,
height: 24,
'& .MuiSvgIcon-root': { fontSize: 14 }
}}
>
<SchoolIcon />
</IconButton>
</Box>
{/* Category Status Indicator - Compact */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
{percentageValue >= 90 ? (
<CheckCircleIcon color="success" fontSize="small" sx={{ fontSize: 14 }} />
) : percentageValue >= 70 ? (
<TrendingUpIcon color="primary" fontSize="small" sx={{ fontSize: 14 }} />
) : (
<WarningIcon color="warning" fontSize="small" sx={{ fontSize: 14 }} />
)}
</Box>
</Box>
</ListItem>
</motion.div>
);
})}
</List>
);
};
export default CategoryList;

View File

@@ -0,0 +1,84 @@
import React from 'react';
import {
Paper,
Box,
Typography
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
interface HeaderSectionProps {
autoPopulatedFields: any;
}
const HeaderSection: React.FC<HeaderSectionProps> = ({ autoPopulatedFields }) => {
return (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Paper
sx={{
p: 2.5, // More compact padding
mb: 3,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)', // Enhanced gradient
color: 'white',
borderRadius: 3,
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, transparent 50%, rgba(255,255,255,0.1) 100%)',
animation: 'shimmer 3s ease-in-out infinite',
},
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3), 0 0 0 1px rgba(255,255,255,0.1)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.2)',
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', position: 'relative', zIndex: 1 }}>
<Box>
<Typography
variant="h4"
gutterBottom
sx={{
fontWeight: 'bold',
background: 'linear-gradient(45deg, #fff, #f0f0f0)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
textShadow: '0 0 20px rgba(255,255,255,0.5)',
mb: 1
}}
>
AI Content Strategy Co-pilot
</Typography>
<Typography variant="body1" sx={{ opacity: 0.9, fontSize: '0.9rem' }}>
Build a comprehensive content strategy with 30+ strategic inputs
</Typography>
{/* Auto-population Status - Moved to header (Region 4) */}
{autoPopulatedFields && Object.keys(autoPopulatedFields).length > 0 && (
<Box sx={{ mt: 1.5, display: 'flex', alignItems: 'center', gap: 1 }}>
<CheckCircleIcon sx={{ color: 'rgba(255,255,255,0.8)', fontSize: 18 }} />
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.9)', fontSize: '0.85rem' }}>
{Object.keys(autoPopulatedFields).length} fields auto-populated from onboarding data
</Typography>
</Box>
)}
</Box>
</Box>
</Paper>
</motion.div>
);
};
export default HeaderSection;

View File

@@ -0,0 +1,217 @@
import React from 'react';
import {
Box,
Typography,
CircularProgress,
IconButton,
Badge,
Tooltip as MuiTooltip
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
AutoAwesome as AutoAwesomeIcon,
Info as InfoIcon,
Refresh as RefreshIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
interface ProgressTrackerProps {
reviewProgressPercentage: number;
reviewedCategoriesCount: number;
totalCategories: number;
autoPopulatedFields: any;
aiGenerating: boolean;
onShowAIRecommendations: () => void;
onShowDataSourceTransparency: () => void;
onRefreshData: () => void;
}
const ProgressTracker: React.FC<ProgressTrackerProps> = ({
reviewProgressPercentage,
reviewedCategoriesCount,
totalCategories,
autoPopulatedFields,
aiGenerating,
onShowAIRecommendations,
onShowDataSourceTransparency,
onRefreshData
}) => {
return (
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
<Typography variant="h6" gutterBottom sx={{ mb: 0 }}>
Progress
</Typography>
{/* Spiral Progress - Moved from Region 2 to Region 3 */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={reviewProgressPercentage}
size={40}
thickness={4}
sx={{
color: 'primary.main',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography
variant="caption"
component="div"
color="text.secondary"
sx={{ fontSize: '0.7rem', fontWeight: 'bold' }}
>
{`${Math.round(reviewProgressPercentage)}%`}
</Typography>
</Box>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
{reviewedCategoriesCount}/{totalCategories}
</Typography>
</Box>
</Box>
{/* Status Indicators - Compact */}
<Box sx={{ mb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
<CheckCircleIcon color="success" fontSize="small" sx={{ fontSize: 14 }} />
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
Auto-population: {Object.keys(autoPopulatedFields || {}).length} fields
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<AutoAwesomeIcon color="primary" fontSize="small" sx={{ fontSize: 14 }} />
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
AI Insights: {aiGenerating ? 'Generating...' : 'Ready'}
</Typography>
</Box>
</Box>
{/* Icons moved from Region A to Region B - Now integrated into Progress title area */}
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'center', mt: 1.5 }}>
{/* AI Recommendations Button - Compact */}
<MuiTooltip title="View AI-powered recommendations and insights" placement="top">
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<IconButton
onClick={onShowAIRecommendations}
sx={{
color: 'primary.main',
bgcolor: 'rgba(255, 193, 7, 0.1)',
border: '1px solid rgba(255, 193, 7, 0.3)',
'&:hover': {
bgcolor: 'rgba(255, 193, 7, 0.2)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(255, 193, 7, 0.3)'
},
transition: 'all 0.3s ease',
width: 36,
height: 36
}}
>
<Badge
badgeContent={5}
sx={{
'& .MuiBadge-badge': {
fontSize: '0.6rem',
fontWeight: 'bold',
animation: 'pulse 2s infinite',
bgcolor: '#ff6b35',
color: 'white'
}
}}
>
<AutoAwesomeIcon sx={{ fontSize: 16 }} />
</Badge>
</IconButton>
</motion.div>
</MuiTooltip>
{/* Data Source Transparency Button - Compact */}
<MuiTooltip title="View data sources and transparency information" placement="top">
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<IconButton
onClick={onShowDataSourceTransparency}
sx={{
color: 'primary.main',
bgcolor: 'rgba(76, 175, 80, 0.1)',
border: '1px solid rgba(76, 175, 80, 0.3)',
'&:hover': {
bgcolor: 'rgba(76, 175, 80, 0.2)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)'
},
transition: 'all 0.3s ease',
width: 36,
height: 36
}}
>
<Badge
badgeContent={Object.keys(autoPopulatedFields || {}).length}
sx={{
'& .MuiBadge-badge': {
fontSize: '0.6rem',
fontWeight: 'bold',
animation: 'pulse 2s infinite',
bgcolor: '#2196f3',
color: 'white'
}
}}
>
<InfoIcon sx={{ fontSize: 16 }} />
</Badge>
</IconButton>
</motion.div>
</MuiTooltip>
{/* Refresh Button - Compact */}
<MuiTooltip title="Refresh auto-populated data" placement="top">
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<IconButton
onClick={onRefreshData}
sx={{
color: 'primary.main',
bgcolor: 'rgba(0,0,0,0.05)',
border: '1px solid rgba(0,0,0,0.1)',
'&:hover': {
bgcolor: 'rgba(0,0,0,0.1)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)'
},
transition: 'all 0.3s ease',
width: 36,
height: 36
}}
>
<RefreshIcon sx={{ fontSize: 16 }} />
</IconButton>
</motion.div>
</MuiTooltip>
</Box>
</Box>
);
};
export default ProgressTracker;

View File

@@ -0,0 +1,26 @@
import { useState, useEffect } from 'react';
interface UseAutoPopulationProps {
autoPopulateFromOnboarding: () => void;
completionStats: any;
}
export const useAutoPopulation = ({
autoPopulateFromOnboarding,
completionStats
}: UseAutoPopulationProps) => {
const [autoPopulateAttempted, setAutoPopulateAttempted] = useState(false);
// Auto-populate from onboarding on first load
useEffect(() => {
if (!autoPopulateAttempted) {
autoPopulateFromOnboarding();
setAutoPopulateAttempted(true);
}
}, [autoPopulateAttempted, autoPopulateFromOnboarding]);
return {
autoPopulateAttempted,
setAutoPopulateAttempted
};
};

View File

@@ -0,0 +1,113 @@
import { useState, useEffect } from 'react';
interface UseCategoryReviewProps {
completionStats: any;
setError: (error: string | null) => void;
setActiveCategory: (category: string | null) => void;
}
export const useCategoryReview = ({ completionStats, setError, setActiveCategory }: UseCategoryReviewProps) => {
const [reviewedCategories, setReviewedCategories] = useState<Set<string>>(new Set());
const [isMarkingReviewed, setIsMarkingReviewed] = useState(false);
const [categoryCompletionMessage, setCategoryCompletionMessage] = useState<string | null>(null);
// Clear category completion message after 3 seconds
useEffect(() => {
if (categoryCompletionMessage) {
const timer = setTimeout(() => {
setCategoryCompletionMessage(null);
}, 3000);
return () => clearTimeout(timer);
}
}, [categoryCompletionMessage]);
const handleConfirmCategoryReview = async (activeCategory: string | null) => {
if (!activeCategory) return;
setIsMarkingReviewed(true);
setCategoryCompletionMessage('🔄 Marking category as reviewed...');
try {
// Simulate processing time for better UX
await new Promise(resolve => setTimeout(resolve, 1000));
// Mark category as reviewed
setReviewedCategories(prev => new Set([...Array.from(prev), activeCategory]));
// Get category name for display
const categoryName = activeCategory.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
setCategoryCompletionMessage(`${categoryName} reviewed and confirmed!`);
// Auto-navigate to next unreviewed category
setTimeout(() => {
const allCategories = Object.keys(completionStats.category_completion);
const currentIndex = allCategories.indexOf(activeCategory);
// Use the updated reviewedCategories state that includes the current category
const updatedReviewedCategories = new Set([...Array.from(reviewedCategories), activeCategory]);
console.log('🔍 Navigation Debug:', {
activeCategory,
currentIndex,
allCategories,
reviewedCategories: Array.from(reviewedCategories),
updatedReviewedCategories: Array.from(updatedReviewedCategories)
});
const nextUnreviewedCategory = allCategories.find((categoryId, index) => {
if (index <= currentIndex) return false;
return !updatedReviewedCategories.has(categoryId);
});
console.log('🎯 Next Category Found:', nextUnreviewedCategory);
if (nextUnreviewedCategory) {
// Actually navigate to the next category
console.log('🚀 Navigating to:', nextUnreviewedCategory);
setActiveCategory(nextUnreviewedCategory);
setCategoryCompletionMessage(`🎯 Moving to next category: ${nextUnreviewedCategory.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}`);
} else {
console.log('🎉 All categories reviewed!');
setCategoryCompletionMessage('🎉 All categories reviewed and confirmed! You can now create your strategy.');
}
}, 1500);
} catch (error: any) {
setError(`Error marking category as reviewed: ${error.message || 'Unknown error'}`);
console.error('Error in handleConfirmCategoryReview:', error);
} finally {
setIsMarkingReviewed(false);
}
};
const isCategoryReviewed = (categoryId: string) => {
return reviewedCategories.has(categoryId);
};
const getNextUnreviewedCategory = (currentCategoryId: string) => {
const allCategories = Object.keys(completionStats.category_completion);
const currentIndex = allCategories.indexOf(currentCategoryId);
// Use the updated reviewedCategories state that includes the current category
const updatedReviewedCategories = new Set([...Array.from(reviewedCategories), currentCategoryId]);
return allCategories.find((categoryId, index) => {
if (index <= currentIndex) return false;
return !updatedReviewedCategories.has(categoryId);
});
};
return {
reviewedCategories,
isMarkingReviewed,
categoryCompletionMessage,
handleConfirmCategoryReview,
isCategoryReviewed,
getNextUnreviewedCategory,
setReviewedCategories
};
};

View File

@@ -0,0 +1,47 @@
import { useMemo } from 'react';
interface UseProgressTrackingProps {
completionStats: any;
reviewedCategories: Set<string>;
}
export const useProgressTracking = ({ completionStats, reviewedCategories }: UseProgressTrackingProps) => {
const progressData = useMemo(() => {
const totalCategories = Object.keys(completionStats.category_completion).length;
const reviewedCategoriesCount = reviewedCategories.size;
const reviewProgressPercentage = (reviewedCategoriesCount / totalCategories) * 100;
return {
totalCategories,
reviewedCategoriesCount,
reviewProgressPercentage
};
}, [completionStats.category_completion, reviewedCategories]);
const getCategoryProgress = (categoryId: string) => {
return completionStats.category_completion[categoryId] || 0;
};
const getCategoryStatus = (percentage: number) => {
if (percentage >= 90) return { status: 'Complete', color: 'success' as const };
if (percentage >= 70) return { status: 'Good', color: 'primary' as const };
if (percentage >= 50) return { status: 'Fair', color: 'warning' as const };
return { status: 'Needs Work', color: 'error' as const };
};
const isNextInSequence = (categoryId: string, allCategories: string[]) => {
const currentIndex = allCategories.indexOf(categoryId);
return currentIndex > 0 &&
allCategories.slice(0, currentIndex).every(cat =>
reviewedCategories.has(cat)
) &&
!reviewedCategories.has(categoryId);
};
return {
...progressData,
getCategoryProgress,
getCategoryStatus,
isNextInSequence
};
};

View File

@@ -0,0 +1,44 @@
import React from 'react';
import {
Business as BusinessIcon,
People as PeopleIcon,
TrendingUp as TrendingUpIcon,
ContentPaste as ContentIcon,
Analytics as AnalyticsIcon,
Help as HelpIcon
} from '@mui/icons-material';
export const getCategoryIcon = (categoryId: string): React.ReactElement => {
switch (categoryId) {
case 'business_context': return <BusinessIcon />;
case 'audience_intelligence': return <PeopleIcon />;
case 'competitive_intelligence': return <TrendingUpIcon />;
case 'content_strategy': return <ContentIcon />;
case 'performance_analytics': return <AnalyticsIcon />;
default: return <HelpIcon />;
}
};
export const getCategoryColor = (categoryId: string): string => {
switch (categoryId) {
case 'business_context': return 'primary';
case 'audience_intelligence': return 'secondary';
case 'competitive_intelligence': return 'success';
case 'content_strategy': return 'warning';
case 'performance_analytics': return 'info';
default: return 'default';
}
};
export const getCategoryName = (categoryId: string): string => {
return categoryId.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
};
export const getCategoryStatus = (percentage: number) => {
if (percentage >= 90) return { status: 'Complete', color: 'success' as const };
if (percentage >= 70) return { status: 'Good', color: 'primary' as const };
if (percentage >= 50) return { status: 'Fair', color: 'warning' as const };
return { status: 'Needs Work', color: 'error' as const };
};

View File

@@ -0,0 +1,103 @@
interface EducationalContent {
title: string;
description: string;
points: string[];
tips: string[];
}
export const getEducationalContent = (categoryId: string): EducationalContent => {
switch (categoryId) {
case 'business_context':
return {
title: 'Business Context',
description: 'Understanding your business foundation is crucial for content strategy success.',
points: [
'Business objectives define what you want to achieve through content',
'Target metrics help measure the success of your content strategy',
'Content budget determines the scope and scale of your content efforts',
'Team size affects content production capacity and frequency',
'Implementation timeline sets realistic expectations for strategy rollout'
],
tips: [
'Be specific about your business goals',
'Set measurable and achievable metrics',
'Consider your available resources realistically'
]
};
case 'audience_intelligence':
return {
title: 'Audience Intelligence',
description: 'Deep understanding of your audience drives content relevance and engagement.',
points: [
'Content preferences reveal what formats resonate with your audience',
'Consumption patterns show when and how your audience engages',
'Pain points help create content that solves real problems',
'Buying journey mapping guides content at each stage',
'Seasonal trends identify content opportunities throughout the year'
],
tips: [
'Research your audience thoroughly',
'Create audience personas for better targeting',
'Monitor engagement patterns regularly'
]
};
case 'competitive_intelligence':
return {
title: 'Competitive Intelligence',
description: 'Understanding your competitive landscape helps differentiate your content.',
points: [
'Top competitors analysis reveals content gaps and opportunities',
'Competitor strategies show what works in your industry',
'Market gaps identify underserved content areas',
'Industry trends keep your content current and relevant',
'Emerging trends provide first-mover advantages'
],
tips: [
'Monitor competitors regularly',
'Identify unique angles and perspectives',
'Stay ahead of industry trends'
]
};
case 'content_strategy':
return {
title: 'Content Strategy',
description: 'Your content approach defines how you\'ll achieve your business objectives.',
points: [
'Preferred formats align with audience preferences and business goals',
'Content mix balances different types of content for maximum impact',
'Content frequency should match audience expectations and team capacity',
'Optimal timing maximizes content visibility and engagement',
'Quality metrics ensure content meets audience standards'
],
tips: [
'Balance audience preferences with business goals',
'Set realistic content production schedules',
'Maintain consistent quality standards'
]
};
case 'performance_analytics':
return {
title: 'Performance & Analytics',
description: 'Data-driven insights optimize your content strategy for better results.',
points: [
'Traffic sources show where your audience comes from',
'Conversion rates measure content effectiveness',
'ROI targets help justify content marketing investments',
'A/B testing capabilities enable continuous optimization',
'Regular analysis identifies improvement opportunities'
],
tips: [
'Track key metrics consistently',
'Use data to inform content decisions',
'Continuously optimize based on performance'
]
};
default:
return {
title: 'Category Information',
description: 'Learn more about this content strategy category.',
points: [],
tips: []
};
}
};

View File

@@ -0,0 +1,27 @@
export const calculateReviewProgress = (reviewedCategories: Set<string>, totalCategories: number): number => {
return (reviewedCategories.size / totalCategories) * 100;
};
export const calculateCategoryProgress = (filledFields: number, totalFields: number): number => {
return totalFields > 0 ? (filledFields / totalFields) * 100 : 0;
};
export const getProgressColor = (percentage: number): string => {
if (percentage >= 90) return 'success';
if (percentage >= 70) return 'primary';
if (percentage >= 50) return 'warning';
return 'error';
};
export const formatProgressPercentage = (percentage: number): string => {
return `${Math.round(percentage)}%`;
};
export const getProgressMessage = (percentage: number): string => {
if (percentage >= 100) return 'Complete';
if (percentage >= 90) return 'Almost Complete';
if (percentage >= 70) return 'Good Progress';
if (percentage >= 50) return 'Halfway There';
if (percentage >= 30) return 'Getting Started';
return 'Needs Work';
};

View File

@@ -1,945 +0,0 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Paper,
Typography,
Button,
LinearProgress,
Alert,
Chip,
IconButton,
Tooltip as MuiTooltip,
Card,
CardContent,
Grid,
Divider,
CircularProgress,
Badge,
Collapse,
Accordion,
AccordionSummary,
AccordionDetails,
List,
ListItem,
ListItemIcon,
ListItemText,
Dialog,
DialogTitle,
DialogContent,
DialogActions
} from '@mui/material';
import {
Business as BusinessIcon,
People as PeopleIcon,
TrendingUp as TrendingUpIcon,
ContentPaste as ContentIcon,
Analytics as AnalyticsIcon,
Help as HelpIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
AutoAwesome as AutoAwesomeIcon,
Refresh as RefreshIcon,
Save as SaveIcon,
ArrowForward as ArrowForwardIcon,
ArrowBack as ArrowBackIcon,
Assessment as AssessmentIcon,
ExpandMore as ExpandMoreIcon,
Info as InfoIcon,
Visibility as VisibilityIcon,
School as SchoolIcon,
Lightbulb as LightbulbIcon,
Psychology as PsychologyIcon,
Timeline as TimelineIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import { useEnhancedStrategyStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/enhancedStrategyStore';
import StrategicInputField from './StrategicInputField';
import EnhancedTooltip from './EnhancedTooltip';
import AIRecommendationsPanel from './AIRecommendationsPanel';
import DataSourceTransparency from './DataSourceTransparency';
const EnhancedStrategyBuilder: React.FC = () => {
const {
formData,
formErrors,
autoPopulatedFields,
dataSources,
loading,
error,
saving,
aiGenerating,
currentStep,
completedSteps,
disclosureSteps,
currentStrategy,
updateFormField,
validateFormField,
validateAllFields,
completeStep,
getNextStep,
getPreviousStep,
setCurrentStep,
canProceedToStep,
resetForm,
autoPopulateFromOnboarding,
generateAIRecommendations,
createEnhancedStrategy,
calculateCompletionPercentage,
getCompletionStats,
setError,
setCurrentStrategy,
setAIGenerating,
setSaving
} = useEnhancedStrategyStore();
const [showTooltip, setShowTooltip] = useState<string | null>(null);
const [autoPopulateAttempted, setAutoPopulateAttempted] = useState(false);
const [activeCategory, setActiveCategory] = useState<string | null>(null);
const [showEducationalInfo, setShowEducationalInfo] = useState<string | null>(null);
const [showAIRecommendations, setShowAIRecommendations] = useState(false);
const [showDataSourceTransparency, setShowDataSourceTransparency] = useState(false);
// Auto-populate from onboarding on first load
useEffect(() => {
if (!autoPopulateAttempted) {
autoPopulateFromOnboarding();
setAutoPopulateAttempted(true);
}
}, [autoPopulateAttempted, autoPopulateFromOnboarding]);
const handleCreateStrategy = async () => {
try {
setAIGenerating(true);
setError(null);
console.log('Starting strategy creation...');
console.log('Current formData:', formData);
console.log('FormData ID:', formData.id);
// If we have a saved strategy, use its ID
if (formData.id) {
console.log('Using existing strategy ID:', formData.id);
await generateAIRecommendations(formData.id);
} else {
console.log('No strategy ID found, creating new strategy...');
// If no strategy is saved yet, save it first, then generate AI insights
const isValid = validateAllFields();
console.log('Form validation result:', isValid);
if (isValid) {
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1, // This would come from auth context
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
console.log('Attempting to create strategy with data:', strategyData);
const newStrategy = await createEnhancedStrategy(strategyData);
console.log('New strategy created:', newStrategy);
if (newStrategy && newStrategy.id) {
console.log('Generating AI recommendations for new strategy ID:', newStrategy.id);
await generateAIRecommendations(newStrategy.id);
// Set the current strategy and show success message
setCurrentStrategy(newStrategy);
setError(null); // Clear any previous errors
// Show success message
setTimeout(() => {
setError('Strategy created successfully! Check the Strategic Intelligence tab for detailed insights.');
}, 100);
// Auto-switch to Strategic Intelligence tab after creation
// This would need to be handled by the parent component
} else {
setError('Failed to create strategy or get strategy ID for AI generation.');
console.error('Failed to create strategy or get strategy ID for AI generation.');
}
} else {
setError('Please fill in all required fields before generating AI insights.');
console.error('Form validation failed. Cannot generate AI insights.');
}
}
} catch (err: any) {
setError(`Error generating AI recommendations: ${err.message || 'Unknown error'}`);
console.error('Error in handleCreateStrategy:', err);
} finally {
setAIGenerating(false);
}
};
const handleSaveStrategy = async () => {
try {
setSaving(true);
setError(null);
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1,
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
const newStrategy = await createEnhancedStrategy(strategyData);
setCurrentStrategy(newStrategy);
setError('Strategy saved successfully!');
} catch (err: any) {
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
} finally {
setSaving(false);
}
};
const handleReviewCategory = (categoryId: string) => {
setActiveCategory(activeCategory === categoryId ? null : categoryId);
};
const handleShowEducationalInfo = (categoryId: string) => {
setShowEducationalInfo(showEducationalInfo === categoryId ? null : categoryId);
};
const getCategoryIcon = (categoryId: string) => {
switch (categoryId) {
case 'business_context': return <BusinessIcon />;
case 'audience_intelligence': return <PeopleIcon />;
case 'competitive_intelligence': return <TrendingUpIcon />;
case 'content_strategy': return <ContentIcon />;
case 'performance_analytics': return <AnalyticsIcon />;
default: return <HelpIcon />;
}
};
const getCategoryColor = (categoryId: string) => {
switch (categoryId) {
case 'business_context': return 'primary';
case 'audience_intelligence': return 'secondary';
case 'competitive_intelligence': return 'success';
case 'content_strategy': return 'warning';
case 'performance_analytics': return 'info';
default: return 'default';
}
};
const getEducationalContent = (categoryId: string) => {
switch (categoryId) {
case 'business_context':
return {
title: 'Business Context',
description: 'Understanding your business foundation is crucial for content strategy success.',
points: [
'Business objectives define what you want to achieve through content',
'Target metrics help measure the success of your content strategy',
'Content budget determines the scope and scale of your content efforts',
'Team size affects content production capacity and frequency',
'Implementation timeline sets realistic expectations for strategy rollout'
],
tips: [
'Be specific about your business goals',
'Set measurable and achievable metrics',
'Consider your available resources realistically'
]
};
case 'audience_intelligence':
return {
title: 'Audience Intelligence',
description: 'Deep understanding of your audience drives content relevance and engagement.',
points: [
'Content preferences reveal what formats resonate with your audience',
'Consumption patterns show when and how your audience engages',
'Pain points help create content that solves real problems',
'Buying journey mapping guides content at each stage',
'Seasonal trends identify content opportunities throughout the year'
],
tips: [
'Research your audience thoroughly',
'Create audience personas for better targeting',
'Monitor engagement patterns regularly'
]
};
case 'competitive_intelligence':
return {
title: 'Competitive Intelligence',
description: 'Understanding your competitive landscape helps differentiate your content.',
points: [
'Top competitors analysis reveals content gaps and opportunities',
'Competitor strategies show what works in your industry',
'Market gaps identify underserved content areas',
'Industry trends keep your content current and relevant',
'Emerging trends provide first-mover advantages'
],
tips: [
'Monitor competitors regularly',
'Identify unique angles and perspectives',
'Stay ahead of industry trends'
]
};
case 'content_strategy':
return {
title: 'Content Strategy',
description: 'Your content approach defines how you\'ll achieve your business objectives.',
points: [
'Preferred formats align with audience preferences and business goals',
'Content mix balances different types of content for maximum impact',
'Content frequency should match audience expectations and team capacity',
'Optimal timing maximizes content visibility and engagement',
'Quality metrics ensure content meets audience standards'
],
tips: [
'Balance audience preferences with business goals',
'Set realistic content production schedules',
'Maintain consistent quality standards'
]
};
case 'performance_analytics':
return {
title: 'Performance & Analytics',
description: 'Data-driven insights optimize your content strategy for better results.',
points: [
'Traffic sources show where your audience comes from',
'Conversion rates measure content effectiveness',
'ROI targets help justify content marketing investments',
'A/B testing capabilities enable continuous optimization',
'Regular analysis identifies improvement opportunities'
],
tips: [
'Track key metrics consistently',
'Use data to inform content decisions',
'Continuously optimize based on performance'
]
};
default:
return {
title: 'Category Information',
description: 'Learn more about this content strategy category.',
points: [],
tips: []
};
}
};
const completionStats = getCompletionStats();
const completionPercentage = calculateCompletionPercentage();
return (
<Box sx={{ p: 3 }}>
{/* Header with Title (Region B) */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Paper
sx={{
p: 3,
mb: 3,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
borderRadius: 2
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box>
<Typography variant="h4" gutterBottom sx={{ fontWeight: 'bold' }}>
Enhanced Strategy Builder
</Typography>
<Typography variant="body1" sx={{ opacity: 0.9 }}>
Build a comprehensive content strategy with 30+ strategic inputs
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
{/* AI Recommendations Button */}
<MuiTooltip title="View AI-powered recommendations and insights" placement="top">
<IconButton
onClick={() => setShowAIRecommendations(true)}
sx={{
color: 'white',
bgcolor: 'rgba(255,255,255,0.1)',
'&:hover': { bgcolor: 'rgba(255,255,255,0.2)' }
}}
>
<Badge badgeContent={5} color="secondary">
<AutoAwesomeIcon />
</Badge>
</IconButton>
</MuiTooltip>
{/* Data Source Transparency Button */}
<MuiTooltip title="View data sources and transparency information" placement="top">
<IconButton
onClick={() => setShowDataSourceTransparency(true)}
sx={{
color: 'white',
bgcolor: 'rgba(255,255,255,0.1)',
'&:hover': { bgcolor: 'rgba(255,255,255,0.2)' }
}}
>
<Badge badgeContent={Object.keys(autoPopulatedFields || {}).length} color="info">
<InfoIcon />
</Badge>
</IconButton>
</MuiTooltip>
{/* Refresh Button */}
<MuiTooltip title="Refresh auto-populated data" placement="top">
<IconButton
onClick={autoPopulateFromOnboarding}
sx={{
color: 'white',
bgcolor: 'rgba(255,255,255,0.1)',
'&:hover': { bgcolor: 'rgba(255,255,255,0.2)' }
}}
>
<RefreshIcon />
</IconButton>
</MuiTooltip>
</Box>
</Box>
</Paper>
</motion.div>
{/* Error Alert */}
{error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
)}
{/* Success Alert */}
{!error && currentStrategy && (
<Alert severity="success" sx={{ mb: 3 }}>
Strategy "{currentStrategy.name}" created successfully! Check the Strategic Intelligence tab for detailed insights.
</Alert>
)}
{/* Strategy Display */}
{currentStrategy && (
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h5" gutterBottom>
Created Strategy: {currentStrategy.name}
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Industry: {currentStrategy.industry}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Completion: {currentStrategy.completion_percentage}%
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Created: {new Date(currentStrategy.created_at).toLocaleDateString()}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
ID: {currentStrategy.id}
</Typography>
</Grid>
</Grid>
<Box sx={{ mt: 2 }}>
<Button
variant="outlined"
onClick={() => window.location.href = '/content-planning?tab=strategic-intelligence'}
startIcon={<AssessmentIcon />}
>
View Strategic Intelligence
</Button>
</Box>
</Paper>
)}
{/* Auto-population Status */}
{autoPopulatedFields && Object.keys(autoPopulatedFields).length > 0 && (
<Alert
severity="info"
sx={{ mb: 3 }}
action={
<Button color="inherit" size="small" onClick={autoPopulateFromOnboarding}>
<RefreshIcon />
</Button>
}
>
{autoPopulatedFields && Object.keys(autoPopulatedFields).length} fields auto-populated from onboarding data
</Alert>
)}
<Grid container spacing={3}>
{/* Category Overview Panel */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, height: 'fit-content', position: 'sticky', top: 20 }}>
{/* Enhanced Completion Tracker - Integrated into Category List */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom>
Strategy Progress
</Typography>
{/* Overall Progress with Status */}
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">
Overall Completion
</Typography>
<Typography variant="body2" color="text.secondary">
{Math.round(completionPercentage)}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={completionPercentage}
sx={{ height: 8, borderRadius: 4 }}
/>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
{completionStats.filled_fields} of {completionStats.total_fields} fields completed
</Typography>
</Box>
{/* Status Chip */}
<Box sx={{ mb: 2 }}>
<Chip
label={
completionPercentage >= 90 ? 'Excellent' :
completionPercentage >= 70 ? 'Good' :
completionPercentage >= 50 ? 'Fair' : 'Needs Work'
}
color={
completionPercentage >= 90 ? 'success' :
completionPercentage >= 70 ? 'primary' :
completionPercentage >= 50 ? 'warning' : 'error'
}
size="small"
icon={<TrendingUpIcon />}
/>
</Box>
{/* Motivational Message */}
{completionPercentage > 0 && completionPercentage < 100 && (
<Alert severity="info" sx={{ mb: 2 }}>
<Typography variant="body2">
{completionPercentage < 30 && "Great start! Keep going to unlock AI insights."}
{completionPercentage >= 30 && completionPercentage < 60 && "You're making excellent progress! Consider reviewing completed categories."}
{completionPercentage >= 60 && completionPercentage < 90 && "Almost there! Just a few more fields to complete your strategy."}
{completionPercentage >= 90 && "Excellent work! Your strategy is nearly complete."}
</Typography>
</Alert>
)}
{/* Status Indicators */}
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<CheckCircleIcon color="success" fontSize="small" />
<Typography variant="body2" color="text.secondary">
Auto-population: {Object.keys(autoPopulatedFields || {}).length} fields
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon color="primary" fontSize="small" />
<Typography variant="body2" color="text.secondary">
AI Insights: {aiGenerating ? 'Generating...' : 'Ready'}
</Typography>
</Box>
</Box>
</Box>
{/* Category Progress - Integrated with CompletionTracker functionality */}
<Typography variant="h6" gutterBottom>
Category Progress
</Typography>
<List sx={{ p: 0 }}>
{Object.entries(completionStats.category_completion).map(([categoryId, percentage]) => {
const category = STRATEGIC_INPUT_FIELDS.find(f => f.category === categoryId);
const categoryName = categoryId.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
// Get category-specific stats
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(f => f.category === categoryId);
const filledFields = categoryFields.filter(field => formData[field.id]).length;
const totalFields = categoryFields.length;
// Get status for this category
const getCategoryStatus = (percentage: number) => {
if (percentage >= 90) return { status: 'Complete', color: 'success' as const };
if (percentage >= 70) return { status: 'Good', color: 'primary' as const };
if (percentage >= 50) return { status: 'Fair', color: 'warning' as const };
return { status: 'Needs Work', color: 'error' as const };
};
const categoryStatus = getCategoryStatus(percentage);
return (
<motion.div
key={categoryId}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<ListItem
sx={{
p: 2,
mb: 1,
borderRadius: 2,
bgcolor: activeCategory === categoryId ? 'action.hover' : 'transparent',
border: activeCategory === categoryId ? '2px solid' : '1px solid',
borderColor: activeCategory === categoryId ? 'primary.main' : 'divider',
flexDirection: 'column',
alignItems: 'stretch'
}}
>
{/* Category Header */}
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', mb: 1 }}>
<ListItemIcon>
{getCategoryIcon(categoryId)}
</ListItemIcon>
<ListItemText
primary={categoryName}
secondary={`${Math.round(percentage)}% complete`}
sx={{ flex: 1 }}
/>
<Chip
label={categoryStatus.status}
color={categoryStatus.color}
size="small"
sx={{ mr: 1 }}
/>
</Box>
{/* Category Progress Bar */}
<Box sx={{ mb: 1 }}>
<LinearProgress
variant="determinate"
value={percentage}
sx={{
height: 4,
borderRadius: 2,
bgcolor: 'action.hover'
}}
/>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
{filledFields} of {totalFields} fields completed
</Typography>
</Box>
{/* Category Actions */}
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', gap: 1 }}>
{/* Review Button */}
<Button
size="small"
variant="outlined"
startIcon={<VisibilityIcon />}
onClick={() => handleReviewCategory(categoryId)}
sx={{ minWidth: 'auto' }}
>
Review
</Button>
{/* Educational Info Button */}
<IconButton
size="small"
onClick={() => handleShowEducationalInfo(categoryId)}
sx={{ color: 'primary.main' }}
>
<SchoolIcon />
</IconButton>
</Box>
{/* Category Status Indicator */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{percentage >= 90 ? (
<CheckCircleIcon color="success" fontSize="small" />
) : percentage >= 70 ? (
<TrendingUpIcon color="primary" fontSize="small" />
) : (
<WarningIcon color="warning" fontSize="small" />
)}
</Box>
</Box>
</ListItem>
</motion.div>
);
})}
</List>
{/* Quick Actions */}
<Box sx={{ mt: 3, pt: 2, borderTop: 1, borderColor: 'divider' }}>
<Typography variant="subtitle2" gutterBottom>
Quick Actions
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Button
size="small"
variant="outlined"
startIcon={<AutoAwesomeIcon />}
onClick={() => setShowAIRecommendations(true)}
fullWidth
>
View AI Insights
</Button>
<Button
size="small"
variant="outlined"
startIcon={<InfoIcon />}
onClick={() => setShowDataSourceTransparency(true)}
fullWidth
>
View Data Sources
</Button>
<Button
size="small"
variant="outlined"
startIcon={<RefreshIcon />}
onClick={autoPopulateFromOnboarding}
fullWidth
>
Refresh Data
</Button>
</Box>
</Box>
</Paper>
</Grid>
{/* Main Content Area */}
<Grid item xs={12} md={8}>
<Paper sx={{ p: 3, minHeight: '600px' }}>
{activeCategory ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{/* Category Header */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
{getCategoryIcon(activeCategory)}
<Typography variant="h5" sx={{ ml: 1 }}>
{activeCategory.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}
</Typography>
<Chip
label={`${Math.round(completionStats.category_completion[activeCategory])}% Complete`}
color={getCategoryColor(activeCategory) as any}
sx={{ ml: 'auto' }}
/>
</Box>
{/* Educational Info Dialog */}
<Dialog
open={!!showEducationalInfo}
onClose={() => setShowEducationalInfo(null)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SchoolIcon />
{showEducationalInfo && getEducationalContent(showEducationalInfo).title}
</Box>
</DialogTitle>
<DialogContent>
<Typography variant="body1" paragraph>
{showEducationalInfo && getEducationalContent(showEducationalInfo).description}
</Typography>
<Typography variant="h6" gutterBottom>
Key Points:
</Typography>
<List>
{showEducationalInfo && getEducationalContent(showEducationalInfo).points.map((point, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<LightbulbIcon color="primary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={point} />
</ListItem>
))}
</List>
<Typography variant="h6" gutterBottom>
Pro Tips:
</Typography>
<List>
{showEducationalInfo && getEducationalContent(showEducationalInfo).tips.map((tip, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<PsychologyIcon color="secondary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={tip} />
</ListItem>
))}
</List>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowEducationalInfo(null)}>
Got it!
</Button>
</DialogActions>
</Dialog>
{/* Category Fields */}
<Grid container spacing={2}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field) => (
<Grid item xs={12} key={field.id}>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
onChange={(value: any) => updateFormField(field.id, value)}
onValidate={() => validateFormField(field.id)}
onShowTooltip={() => setShowTooltip(field.id)}
/>
</Grid>
))}
</Grid>
{/* Category Actions */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
<Button
variant="contained"
onClick={() => {
// Mark category as complete
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(f => f.category === activeCategory);
const allFieldsFilled = categoryFields.every(field => formData[field.id]);
if (allFieldsFilled) {
completeStep(activeCategory);
setActiveCategory(null);
}
}}
disabled={!STRATEGIC_INPUT_FIELDS
.filter(f => f.category === activeCategory)
.every(field => formData[field.id])}
>
Complete Category
</Button>
<Button
variant="outlined"
onClick={() => setActiveCategory(null)}
>
Back to Overview
</Button>
</Box>
</motion.div>
) : (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<Box sx={{ textAlign: 'center', py: 8 }}>
<TimelineIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h5" gutterBottom>
Select a Category to Review
</Typography>
<Typography variant="body1" color="text.secondary">
Click on any category from the left panel to review and complete the fields.
</Typography>
</Box>
</motion.div>
)}
</Paper>
</Grid>
</Grid>
{/* Action Buttons */}
<Box sx={{ mt: 3, display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
<MuiTooltip
title={completionPercentage < 20 ? `Complete at least 20% of the form (currently ${Math.round(completionPercentage)}%)` : 'Create a comprehensive content strategy with AI insights'}
placement="top"
>
<span>
<Button
variant="outlined"
startIcon={<AutoAwesomeIcon />}
onClick={handleCreateStrategy}
disabled={aiGenerating || completionPercentage < 20}
>
{aiGenerating ? 'Creating...' : 'Create Strategy'}
</Button>
</span>
</MuiTooltip>
<Button
variant="contained"
startIcon={<SaveIcon />}
onClick={handleSaveStrategy}
disabled={saving || completionPercentage < 30}
>
{saving ? 'Saving...' : 'Save Strategy'}
</Button>
</Box>
{/* AI Recommendations Modal */}
<Dialog
open={showAIRecommendations}
onClose={() => setShowAIRecommendations(false)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon />
AI Recommendations & Insights
</Box>
</DialogTitle>
<DialogContent>
<AIRecommendationsPanel
aiGenerating={aiGenerating}
onGenerateRecommendations={handleCreateStrategy}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowAIRecommendations(false)}>
Close
</Button>
</DialogActions>
</Dialog>
{/* Data Source Transparency Modal */}
<Dialog
open={showDataSourceTransparency}
onClose={() => setShowDataSourceTransparency(false)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<InfoIcon />
Data Source Transparency
</Box>
</DialogTitle>
<DialogContent>
<DataSourceTransparency
autoPopulatedFields={autoPopulatedFields}
dataSources={dataSources}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowDataSourceTransparency(false)}>
Close
</Button>
</DialogActions>
</Dialog>
{/* Tooltip */}
{showTooltip && (
<EnhancedTooltip
fieldId={showTooltip}
open={!!showTooltip}
onClose={() => setShowTooltip(null)}
/>
)}
</Box>
);
};
export default EnhancedStrategyBuilder;

View File

@@ -55,7 +55,7 @@ import {
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import EnhancedStrategyBuilder from '../components/EnhancedStrategyBuilder';
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
interface TabPanelProps {
children?: React.ReactNode;
@@ -429,20 +429,6 @@ const ContentStrategyTab: React.FC = () => {
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h4" gutterBottom>
Content Strategy Builder
</Typography>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={handleRefreshData}
disabled={loading}
>
Refresh Data
</Button>
</Box>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
@@ -455,13 +441,12 @@ const ContentStrategyTab: React.FC = () => {
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy builder tabs">
<Tab
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon />
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<AutoAwesomeIcon sx={{ mr: 1 }} />
Enhanced Strategy Builder
</Box>
}
/>
<Tab label="Legacy Strategy Builder" />
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
<Tab label="Keyword Research" icon={<SearchIcon />} />
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
@@ -471,479 +456,340 @@ const ContentStrategyTab: React.FC = () => {
{/* Enhanced Strategy Builder Tab */}
<TabPanel value={tabValue} index={0}>
<EnhancedStrategyBuilder />
</TabPanel>
{/* Legacy Strategy Builder Tab */}
<TabPanel value={tabValue} index={1}>
<Grid container spacing={3}>
{/* Strategy Overview */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Strategy Overview
</Typography>
<Divider sx={{ mb: 2 }} />
<TextField
fullWidth
label="Strategy Name"
value={strategyForm.name}
onChange={(e) => handleStrategyFormChange('name', e.target.value)}
placeholder="Enter strategy name"
sx={{ mb: 2 }}
/>
<TextField
fullWidth
multiline
rows={3}
label="Strategy Description"
value={strategyForm.description}
onChange={(e) => handleStrategyFormChange('description', e.target.value)}
placeholder="Describe your content strategy"
sx={{ mb: 2 }}
/>
<TextField
fullWidth
label="Industry"
value={strategyForm.industry}
onChange={(e) => handleStrategyFormChange('industry', e.target.value)}
placeholder="e.g., Technology, Healthcare, Finance"
sx={{ mb: 2 }}
/>
<Button
variant="contained"
fullWidth
startIcon={<AddIcon />}
disabled={loading}
onClick={handleCreateStrategy}
>
{loading ? 'Creating...' : 'Create Strategy'}
</Button>
</Paper>
{/* Performance Metrics */}
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Performance Metrics
</Typography>
<Divider sx={{ mb: 2 }} />
{performanceMetrics ? (
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Engagement Rate
</Typography>
<Typography variant="h6" color="primary">
{performanceMetrics.engagement || 75.2}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Reach
</Typography>
<Typography variant="h6" color="primary">
{(performanceMetrics.reach || 12500).toLocaleString()}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Conversion Rate
</Typography>
<Typography variant="h6" color="success.main">
{performanceMetrics.conversion || 3.8}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
ROI
</Typography>
<Typography variant="h6" color="success.main">
${(performanceMetrics.roi || 14200).toLocaleString()}
</Typography>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary">
No performance data available
</Typography>
)}
</Paper>
</Grid>
{/* Main Content Area */}
<Grid item xs={12} md={8}>
<Paper sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy tabs">
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
<Tab label="Keyword Research" icon={<SearchIcon />} />
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
<Tab label="Content Pillars" icon={<PieChartIcon />} />
</Tabs>
</Box>
{/* Strategic Intelligence Tab */}
<TabPanel value={tabValue} index={2}>
{dataLoading.strategicIntelligence ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : strategicIntelligence && strategicIntelligence.market_positioning ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Market Positioning
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<CircularProgress
variant="determinate"
value={strategicIntelligence.market_positioning.score || 0}
size={60}
color="primary"
/>
<Typography variant="h4" sx={{ ml: 2 }}>
{strategicIntelligence.market_positioning.score || 0}/100
</Typography>
</Box>
<Typography variant="subtitle2" gutterBottom>
Strengths:
</Typography>
<List dense>
{(strategicIntelligence.market_positioning.strengths || []).map((strength: string, index: number) => (
<ListItem key={index}>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary={strength} />
</ListItem>
))}
</List>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Competitive Advantages
</Typography>
{(strategicIntelligence.competitive_advantages || []).map((advantage: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{advantage.advantage}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={advantage.impact}
color={advantage.impact === 'High' ? 'success' : 'primary'}
size="small"
/>
<Chip
label={advantage.implementation}
variant="outlined"
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Strategic Risks
</Typography>
{(strategicIntelligence.strategic_risks || []).map((risk: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{risk.risk}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={`Probability: ${risk.probability}`}
color={risk.probability === 'High' ? 'error' : 'warning'}
size="small"
/>
<Chip
label={`Impact: ${risk.impact}`}
color={risk.impact === 'High' ? 'error' : 'warning'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No strategic intelligence data available
</Typography>
)}
</TabPanel>
{/* Keyword Research Tab */}
<TabPanel value={tabValue} index={3}>
{dataLoading.keywordResearch ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : keywordResearch && keywordResearch.trend_analysis ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
High Volume Keywords
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Volume</TableCell>
<TableCell>Difficulty</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => (
<TableRow key={index}>
<TableCell>{keyword.keyword}</TableCell>
<TableCell>{keyword.volume}</TableCell>
<TableCell>
<Chip
label={keyword.difficulty}
color={keyword.difficulty === 'Low' ? 'success' : keyword.difficulty === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Trending Keywords
</Typography>
{(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{keyword.keyword}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={keyword.growth}
color="success"
size="small"
/>
<Chip
label={keyword.opportunity}
color={keyword.opportunity === 'High' ? 'success' : 'primary'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Keyword Opportunities
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Search Volume</TableCell>
<TableCell>Competition</TableCell>
<TableCell>CPC</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.opportunities || []).map((opportunity: any, index: number) => (
<TableRow key={index}>
<TableCell>{opportunity.keyword}</TableCell>
<TableCell>{opportunity.search_volume}</TableCell>
<TableCell>
<Chip
label={opportunity.competition}
color={opportunity.competition === 'Low' ? 'success' : opportunity.competition === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
<TableCell>${opportunity.cpc}</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Add to Strategy
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No keyword research data available
</Typography>
)}
</TabPanel>
{/* Performance Analytics Tab */}
<TabPanel value={tabValue} index={4}>
{performanceMetrics ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Content Performance by Type
</Typography>
<Typography variant="body2" color="text.secondary">
No content performance data available
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Growth Trends
</Typography>
<Typography variant="body2" color="text.secondary">
No trend data available
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No performance analytics data available
</Typography>
)}
</TabPanel>
{/* Content Pillars Tab */}
<TabPanel value={tabValue} index={5}>
{dataLoading.pillars ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : contentPillars.length > 0 ? (
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom>
Content Pillars Overview
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics.
</Typography>
</Grid>
{contentPillars.map((pillar, index) => (
<Grid item xs={12} md={6} key={index}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
{pillar.name}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Content Count
</Typography>
<Typography variant="h6">
{pillar.content_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Avg. Engagement
</Typography>
<Typography variant="h6">
{pillar.avg_engagement}%
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary">
Performance Score
</Typography>
<Typography variant="h6" color="success.main">
{pillar.performance_score}/100
</Typography>
</Box>
</CardContent>
<CardActions>
<Button size="small">View Content</Button>
<Button size="small">Optimize</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No content pillars data available
</Typography>
)}
</TabPanel>
</Paper>
</Grid>
</Grid>
<ContentStrategyBuilder />
</TabPanel>
{/* Strategic Intelligence Tab */}
<TabPanel value={tabValue} index={2}>
{/* Content moved to Legacy Strategy Builder */}
<TabPanel value={tabValue} index={1}>
{dataLoading.strategicIntelligence ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : strategicIntelligence && strategicIntelligence.market_positioning ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Market Positioning
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<CircularProgress
variant="determinate"
value={strategicIntelligence.market_positioning.score || 0}
size={60}
color="primary"
/>
<Typography variant="h4" sx={{ ml: 2 }}>
{strategicIntelligence.market_positioning.score || 0}/100
</Typography>
</Box>
<Typography variant="subtitle2" gutterBottom>
Strengths:
</Typography>
<List dense>
{(strategicIntelligence.market_positioning.strengths || []).map((strength: string, index: number) => (
<ListItem key={index}>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary={strength} />
</ListItem>
))}
</List>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Competitive Advantages
</Typography>
{(strategicIntelligence.competitive_advantages || []).map((advantage: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{advantage.advantage}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={advantage.impact}
color={advantage.impact === 'High' ? 'success' : 'primary'}
size="small"
/>
<Chip
label={advantage.implementation}
variant="outlined"
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Strategic Risks
</Typography>
{(strategicIntelligence.strategic_risks || []).map((risk: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{risk.risk}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={`Probability: ${risk.probability}`}
color={risk.probability === 'High' ? 'error' : 'warning'}
size="small"
/>
<Chip
label={`Impact: ${risk.impact}`}
color={risk.impact === 'High' ? 'error' : 'warning'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No strategic intelligence data available
</Typography>
)}
</TabPanel>
{/* Keyword Research Tab */}
<TabPanel value={tabValue} index={3}>
{/* Content moved to Legacy Strategy Builder */}
<TabPanel value={tabValue} index={2}>
{dataLoading.keywordResearch ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : keywordResearch && keywordResearch.trend_analysis ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
High Volume Keywords
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Volume</TableCell>
<TableCell>Difficulty</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => (
<TableRow key={index}>
<TableCell>{keyword.keyword}</TableCell>
<TableCell>{keyword.volume}</TableCell>
<TableCell>
<Chip
label={keyword.difficulty}
color={keyword.difficulty === 'Low' ? 'success' : keyword.difficulty === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Trending Keywords
</Typography>
{(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{keyword.keyword}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={keyword.growth}
color="success"
size="small"
/>
<Chip
label={keyword.opportunity}
color={keyword.opportunity === 'High' ? 'success' : 'primary'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Keyword Opportunities
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Search Volume</TableCell>
<TableCell>Competition</TableCell>
<TableCell>CPC</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.opportunities || []).map((opportunity: any, index: number) => (
<TableRow key={index}>
<TableCell>{opportunity.keyword}</TableCell>
<TableCell>{opportunity.search_volume}</TableCell>
<TableCell>
<Chip
label={opportunity.competition}
color={opportunity.competition === 'Low' ? 'success' : opportunity.competition === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
<TableCell>${opportunity.cpc}</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Add to Strategy
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No keyword research data available
</Typography>
)}
</TabPanel>
{/* Performance Analytics Tab */}
<TabPanel value={tabValue} index={4}>
{/* Content moved to Legacy Strategy Builder */}
<TabPanel value={tabValue} index={3}>
{performanceMetrics ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Content Performance by Type
</Typography>
<Typography variant="body2" color="text.secondary">
No content performance data available
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Growth Trends
</Typography>
<Typography variant="body2" color="text.secondary">
No trend data available
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No performance analytics data available
</Typography>
)}
</TabPanel>
{/* Content Pillars Tab */}
<TabPanel value={tabValue} index={5}>
{/* Content moved to Legacy Strategy Builder */}
<TabPanel value={tabValue} index={4}>
{dataLoading.pillars ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : contentPillars.length > 0 ? (
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom>
Content Pillars Overview
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics.
</Typography>
</Grid>
{contentPillars.map((pillar, index) => (
<Grid item xs={12} md={6} key={index}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
{pillar.name}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Content Count
</Typography>
<Typography variant="h6">
{pillar.content_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Avg. Engagement
</Typography>
<Typography variant="h6">
{pillar.avg_engagement}%
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary">
Performance Score
</Typography>
<Typography variant="h6" color="success.main">
{pillar.performance_score}/100
</Typography>
</Box>
</CardContent>
<CardActions>
<Button size="small">View Content</Button>
<Button size="small">Optimize</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No content pillars data available
</Typography>
)}
</TabPanel>
</Paper>
</Box>

View File

@@ -918,11 +918,33 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
const filledRequiredFields = requiredFields.filter(field => {
const value = formData[field.id];
const isValid = value &&
(typeof value === 'string' ? value.trim() !== '' : true) &&
(!Array.isArray(value) || value.length > 0);
console.log(`📊 Field ${field.id}: ${isValid ? '✅' : '❌'} (${value})`);
// Enhanced validation logic for different field types
let isValid = false;
if (value) {
if (field.type === 'multiselect') {
// For multiselect, check if it's an array with at least one item
isValid = Array.isArray(value) && value.length > 0;
} else if (field.type === 'select') {
// For select, check if it's a non-empty string
isValid = typeof value === 'string' && value.trim() !== '';
} else if (typeof value === 'string') {
// For text fields, check if it's not empty
isValid = value.trim() !== '';
} else if (typeof value === 'number') {
// For number fields, check if it's not null/undefined
isValid = value !== null && value !== undefined;
} else if (Array.isArray(value)) {
// For arrays (json fields), check if it has items
isValid = value.length > 0;
} else {
// For other types, just check if it exists
isValid = true;
}
}
console.log(`📊 Field ${field.id}: ${isValid ? '✅' : '❌'} (${value}) [Type: ${field.type}]`);
return isValid;
});
@@ -940,19 +962,57 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
categories.forEach(category => {
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
const filledFields = categoryFields.filter(field =>
formData[field.id] &&
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
);
const filledFields = categoryFields.filter(field => {
const value = formData[field.id];
if (!value) return false;
// Enhanced validation logic for different field types
if (field.type === 'multiselect') {
// For multiselect, check if it's an array with at least one item
return Array.isArray(value) && value.length > 0;
} else if (field.type === 'select') {
// For select, check if it's a non-empty string
return typeof value === 'string' && value.trim() !== '';
} else if (typeof value === 'string') {
// For text fields, check if it's not empty
return value.trim() !== '';
} else if (typeof value === 'number') {
// For number fields, check if it's not null/undefined
return value !== null && value !== undefined;
} else if (Array.isArray(value)) {
// For arrays (json fields), check if it has items
return value.length > 0;
} else {
// For other types, just check if it exists
return true;
}
});
category_completion[category] = (filledFields.length / categoryFields.length) * 100;
});
const total_fields = STRATEGIC_INPUT_FIELDS.length;
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field =>
formData[field.id] &&
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
).length;
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field => {
const value = formData[field.id];
if (!value) return false;
// Enhanced validation logic for different field types
if (field.type === 'multiselect') {
return Array.isArray(value) && value.length > 0;
} else if (field.type === 'select') {
return typeof value === 'string' && value.trim() !== '';
} else if (typeof value === 'string') {
return value.trim() !== '';
} else if (typeof value === 'number') {
return value !== null && value !== undefined;
} else if (Array.isArray(value)) {
return value.length > 0;
} else {
return true;
}
}).length;
return {
total_fields,