Always version 0.5.4
This commit is contained in:
@@ -1021,27 +1021,30 @@ class EnhancedStrategyService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Audience Intelligence Fields
|
# 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'] = {
|
fields['content_preferences'] = {
|
||||||
'value': research_data.get('content_preferences', {}),
|
'value': content_prefs,
|
||||||
'source': 'research_preferences',
|
'source': 'research_preferences',
|
||||||
'confidence': research_data.get('confidence_level', 0.8)
|
'confidence': research_data.get('confidence_level', 0.8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields['consumption_patterns'] = {
|
fields['consumption_patterns'] = {
|
||||||
'value': audience_data.get('consumption_patterns', {}),
|
'value': audience_research.get('consumption_patterns', {}),
|
||||||
'source': 'research_preferences',
|
'source': 'research_preferences',
|
||||||
'confidence': research_data.get('confidence_level', 0.8)
|
'confidence': research_data.get('confidence_level', 0.8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields['audience_pain_points'] = {
|
fields['audience_pain_points'] = {
|
||||||
'value': audience_data.get('pain_points', []),
|
'value': audience_research.get('audience_pain_points', []),
|
||||||
'source': 'research_preferences',
|
'source': 'research_preferences',
|
||||||
'confidence': research_data.get('confidence_level', 0.8)
|
'confidence': research_data.get('confidence_level', 0.8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields['buying_journey'] = {
|
fields['buying_journey'] = {
|
||||||
'value': audience_data.get('buying_journey', {}),
|
'value': audience_research.get('buying_journey', {}),
|
||||||
'source': 'research_preferences',
|
'source': 'research_preferences',
|
||||||
'confidence': research_data.get('confidence_level', 0.8)
|
'confidence': research_data.get('confidence_level', 0.8)
|
||||||
}
|
}
|
||||||
@@ -1064,7 +1067,11 @@ class EnhancedStrategyService:
|
|||||||
|
|
||||||
# Competitive Intelligence Fields
|
# Competitive Intelligence Fields
|
||||||
fields['top_competitors'] = {
|
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',
|
'source': 'website_analysis',
|
||||||
'confidence': website_data.get('confidence_level', 0.8)
|
'confidence': website_data.get('confidence_level', 0.8)
|
||||||
}
|
}
|
||||||
@@ -1094,9 +1101,10 @@ class EnhancedStrategyService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Content Strategy Fields
|
# Content Strategy Fields
|
||||||
content_prefs = research_data.get('content_preferences', {})
|
|
||||||
fields['preferred_formats'] = {
|
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',
|
'source': 'research_preferences',
|
||||||
'confidence': research_data.get('confidence_level', 0.8)
|
'confidence': research_data.get('confidence_level', 0.8)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.c9966057.css",
|
"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",
|
"index.html": "/index.html",
|
||||||
"main.c9966057.css.map": "/static/css/main.c9966057.css.map",
|
"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": [
|
"entrypoints": [
|
||||||
"static/css/main.c9966057.css",
|
"static/css/main.c9966057.css",
|
||||||
"static/js/main.cb1b37a5.js"
|
"static/js/main.28afa9ad.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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
File diff suppressed because one or more lines are too long
@@ -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;
|
||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
DataUsage as DataUsageIcon,
|
DataUsage as DataUsageIcon,
|
||||||
Close as CloseIcon
|
Close as CloseIcon
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
|
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
|
||||||
|
|
||||||
interface EnhancedTooltipProps {
|
interface EnhancedTooltipProps {
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
@@ -21,9 +21,10 @@ import {
|
|||||||
AutoAwesome as AutoAwesomeIcon,
|
AutoAwesome as AutoAwesomeIcon,
|
||||||
Warning as WarningIcon,
|
Warning as WarningIcon,
|
||||||
CheckCircle as CheckCircleIcon,
|
CheckCircle as CheckCircleIcon,
|
||||||
Edit as EditIcon
|
Edit as EditIcon,
|
||||||
|
Info as InfoIcon
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
|
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
|
||||||
|
|
||||||
interface StrategicInputFieldProps {
|
interface StrategicInputFieldProps {
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
@@ -31,6 +32,8 @@ interface StrategicInputFieldProps {
|
|||||||
error?: string;
|
error?: string;
|
||||||
autoPopulated?: boolean;
|
autoPopulated?: boolean;
|
||||||
dataSource?: string;
|
dataSource?: string;
|
||||||
|
confidenceLevel?: number;
|
||||||
|
dataQuality?: string;
|
||||||
onChange: (value: any) => void;
|
onChange: (value: any) => void;
|
||||||
onValidate: () => boolean;
|
onValidate: () => boolean;
|
||||||
onShowTooltip: () => void;
|
onShowTooltip: () => void;
|
||||||
@@ -71,6 +74,8 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
|||||||
error,
|
error,
|
||||||
autoPopulated = false,
|
autoPopulated = false,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
confidenceLevel,
|
||||||
|
dataQuality,
|
||||||
onChange,
|
onChange,
|
||||||
onValidate,
|
onValidate,
|
||||||
onShowTooltip
|
onShowTooltip
|
||||||
@@ -198,10 +203,10 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
|||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
preferred_formats: {
|
preferred_formats: {
|
||||||
type: 'json',
|
type: 'multiselect',
|
||||||
label: 'Preferred Formats',
|
label: 'Preferred Formats',
|
||||||
placeholder: 'Define preferred content formats',
|
options: ['Blog Posts', 'Videos', 'Infographics', 'Webinars', 'Podcasts', 'Case Studies', 'Whitepapers', 'Social Media Posts'],
|
||||||
required: false
|
required: true
|
||||||
},
|
},
|
||||||
content_mix: {
|
content_mix: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
@@ -477,44 +482,129 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'relative' }}>
|
<Box sx={{
|
||||||
{/* Auto-population indicator */}
|
position: 'relative',
|
||||||
{autoPopulated && (
|
mb: 1.5,
|
||||||
<Box sx={{ mb: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
p: 1.5,
|
||||||
<Chip
|
borderRadius: 1.5,
|
||||||
icon={<AutoAwesomeIcon />}
|
bgcolor: 'background.paper',
|
||||||
label={`Auto-populated from ${dataSource}`}
|
border: '1px solid',
|
||||||
color="info"
|
borderColor: error ? 'error.main' : autoPopulated ? 'info.main' : 'divider',
|
||||||
size="small"
|
'&:hover': {
|
||||||
variant="outlined"
|
borderColor: 'primary.main',
|
||||||
/>
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||||
{!isEditing && (
|
transition: 'all 0.2s ease'
|
||||||
<Tooltip title="Edit auto-populated value">
|
}
|
||||||
<IconButton size="small" onClick={() => setIsEditing(true)}>
|
}}>
|
||||||
<EditIcon fontSize="small" />
|
{/* Field input - Enhanced styling */}
|
||||||
</IconButton>
|
<Box sx={{
|
||||||
</Tooltip>
|
'& .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>
|
</Box>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Field input */}
|
{/* Right side - Auto-population indicator */}
|
||||||
{renderInput()}
|
{autoPopulated && (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
{/* Validation status */}
|
<Chip
|
||||||
{value && !error && (
|
icon={<AutoAwesomeIcon sx={{ fontSize: 12 }} />}
|
||||||
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
label={`Auto-populated from ${dataSource}`}
|
||||||
<CheckCircleIcon color="success" fontSize="small" />
|
color="info"
|
||||||
<Typography variant="caption" color="success.main">
|
size="small"
|
||||||
Valid
|
variant="outlined"
|
||||||
</Typography>
|
sx={{
|
||||||
</Box>
|
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 display */}
|
||||||
{error && (
|
{error && (
|
||||||
<Alert severity="error" sx={{ mt: 1 }}>
|
<Alert severity="error" sx={{ mt: 1, py: 0.5, '& .MuiAlert-message': { py: 0 } }}>
|
||||||
<Typography variant="body2">{error}</Typography>
|
<Typography variant="caption" sx={{ fontSize: '0.7rem' }}>
|
||||||
|
{error}
|
||||||
|
</Typography>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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 };
|
||||||
|
};
|
||||||
@@ -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: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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';
|
||||||
|
};
|
||||||
@@ -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;
|
|
||||||
@@ -55,7 +55,7 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
|
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
|
||||||
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
||||||
import EnhancedStrategyBuilder from '../components/EnhancedStrategyBuilder';
|
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -429,20 +429,6 @@ const ContentStrategyTab: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3 }}>
|
<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 && (
|
{error && (
|
||||||
<Alert severity="error" sx={{ mb: 2 }}>
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
{error}
|
{error}
|
||||||
@@ -455,13 +441,12 @@ const ContentStrategyTab: React.FC = () => {
|
|||||||
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy builder tabs">
|
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy builder tabs">
|
||||||
<Tab
|
<Tab
|
||||||
label={
|
label={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<AutoAwesomeIcon />
|
<AutoAwesomeIcon sx={{ mr: 1 }} />
|
||||||
Enhanced Strategy Builder
|
Enhanced Strategy Builder
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Tab label="Legacy Strategy Builder" />
|
|
||||||
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
|
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
|
||||||
<Tab label="Keyword Research" icon={<SearchIcon />} />
|
<Tab label="Keyword Research" icon={<SearchIcon />} />
|
||||||
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
|
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
|
||||||
@@ -471,479 +456,340 @@ const ContentStrategyTab: React.FC = () => {
|
|||||||
|
|
||||||
{/* Enhanced Strategy Builder Tab */}
|
{/* Enhanced Strategy Builder Tab */}
|
||||||
<TabPanel value={tabValue} index={0}>
|
<TabPanel value={tabValue} index={0}>
|
||||||
<EnhancedStrategyBuilder />
|
<ContentStrategyBuilder />
|
||||||
</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>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
{/* Strategic Intelligence Tab */}
|
{/* Strategic Intelligence Tab */}
|
||||||
<TabPanel value={tabValue} index={2}>
|
<TabPanel value={tabValue} index={1}>
|
||||||
{/* Content moved to Legacy Strategy Builder */}
|
{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>
|
</TabPanel>
|
||||||
|
|
||||||
{/* Keyword Research Tab */}
|
{/* Keyword Research Tab */}
|
||||||
<TabPanel value={tabValue} index={3}>
|
<TabPanel value={tabValue} index={2}>
|
||||||
{/* Content moved to Legacy Strategy Builder */}
|
{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>
|
</TabPanel>
|
||||||
|
|
||||||
{/* Performance Analytics Tab */}
|
{/* Performance Analytics Tab */}
|
||||||
<TabPanel value={tabValue} index={4}>
|
<TabPanel value={tabValue} index={3}>
|
||||||
{/* Content moved to Legacy Strategy Builder */}
|
{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>
|
</TabPanel>
|
||||||
|
|
||||||
{/* Content Pillars Tab */}
|
{/* Content Pillars Tab */}
|
||||||
<TabPanel value={tabValue} index={5}>
|
<TabPanel value={tabValue} index={4}>
|
||||||
{/* Content moved to Legacy Strategy Builder */}
|
{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>
|
</TabPanel>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -918,11 +918,33 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
|
|||||||
const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
|
const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
|
||||||
const filledRequiredFields = requiredFields.filter(field => {
|
const filledRequiredFields = requiredFields.filter(field => {
|
||||||
const value = formData[field.id];
|
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;
|
return isValid;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -940,19 +962,57 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
|
|||||||
|
|
||||||
categories.forEach(category => {
|
categories.forEach(category => {
|
||||||
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
|
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
|
||||||
const filledFields = categoryFields.filter(field =>
|
const filledFields = categoryFields.filter(field => {
|
||||||
formData[field.id] &&
|
const value = formData[field.id];
|
||||||
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
|
|
||||||
);
|
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;
|
category_completion[category] = (filledFields.length / categoryFields.length) * 100;
|
||||||
});
|
});
|
||||||
|
|
||||||
const total_fields = STRATEGIC_INPUT_FIELDS.length;
|
const total_fields = STRATEGIC_INPUT_FIELDS.length;
|
||||||
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field =>
|
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field => {
|
||||||
formData[field.id] &&
|
const value = formData[field.id];
|
||||||
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
|
|
||||||
).length;
|
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 {
|
return {
|
||||||
total_fields,
|
total_fields,
|
||||||
|
|||||||
Reference in New Issue
Block a user