Always version 0.5.4
This commit is contained in:
@@ -1021,27 +1021,30 @@ class EnhancedStrategyService:
|
||||
}
|
||||
|
||||
# Audience Intelligence Fields
|
||||
audience_data = research_data.get('audience_intelligence', {})
|
||||
# Extract audience data from research_data structure
|
||||
audience_research = research_data.get('audience_research', {})
|
||||
content_prefs = research_data.get('content_preferences', {})
|
||||
|
||||
fields['content_preferences'] = {
|
||||
'value': research_data.get('content_preferences', {}),
|
||||
'value': content_prefs,
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['consumption_patterns'] = {
|
||||
'value': audience_data.get('consumption_patterns', {}),
|
||||
'value': audience_research.get('consumption_patterns', {}),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['audience_pain_points'] = {
|
||||
'value': audience_data.get('pain_points', []),
|
||||
'value': audience_research.get('audience_pain_points', []),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['buying_journey'] = {
|
||||
'value': audience_data.get('buying_journey', {}),
|
||||
'value': audience_research.get('buying_journey', {}),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
@@ -1064,7 +1067,11 @@ class EnhancedStrategyService:
|
||||
|
||||
# Competitive Intelligence Fields
|
||||
fields['top_competitors'] = {
|
||||
'value': website_data.get('competitors', []),
|
||||
'value': website_data.get('competitors', [
|
||||
'Competitor A - Industry Leader',
|
||||
'Competitor B - Emerging Player',
|
||||
'Competitor C - Niche Specialist'
|
||||
]),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
@@ -1094,9 +1101,10 @@ class EnhancedStrategyService:
|
||||
}
|
||||
|
||||
# Content Strategy Fields
|
||||
content_prefs = research_data.get('content_preferences', {})
|
||||
fields['preferred_formats'] = {
|
||||
'value': content_prefs.get('preferred_formats', []),
|
||||
'value': content_prefs.get('preferred_formats', [
|
||||
'Blog posts', 'Whitepapers', 'Webinars', 'Case studies', 'Videos'
|
||||
]),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.c9966057.css",
|
||||
"main.js": "/static/js/main.cb1b37a5.js",
|
||||
"main.js": "/static/js/main.28afa9ad.js",
|
||||
"index.html": "/index.html",
|
||||
"main.c9966057.css.map": "/static/css/main.c9966057.css.map",
|
||||
"main.cb1b37a5.js.map": "/static/js/main.cb1b37a5.js.map"
|
||||
"main.28afa9ad.js.map": "/static/js/main.28afa9ad.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.c9966057.css",
|
||||
"static/js/main.cb1b37a5.js"
|
||||
"static/js/main.28afa9ad.js"
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
Close as CloseIcon
|
||||
} from '@mui/icons-material';
|
||||
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
|
||||
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
|
||||
|
||||
interface EnhancedTooltipProps {
|
||||
fieldId: string;
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
Warning as WarningIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Edit as EditIcon
|
||||
Edit as EditIcon,
|
||||
Info as InfoIcon
|
||||
} from '@mui/icons-material';
|
||||
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
|
||||
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
|
||||
|
||||
interface StrategicInputFieldProps {
|
||||
fieldId: string;
|
||||
@@ -31,6 +32,8 @@ interface StrategicInputFieldProps {
|
||||
error?: string;
|
||||
autoPopulated?: boolean;
|
||||
dataSource?: string;
|
||||
confidenceLevel?: number;
|
||||
dataQuality?: string;
|
||||
onChange: (value: any) => void;
|
||||
onValidate: () => boolean;
|
||||
onShowTooltip: () => void;
|
||||
@@ -71,6 +74,8 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
||||
error,
|
||||
autoPopulated = false,
|
||||
dataSource,
|
||||
confidenceLevel,
|
||||
dataQuality,
|
||||
onChange,
|
||||
onValidate,
|
||||
onShowTooltip
|
||||
@@ -198,10 +203,10 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
||||
required: false
|
||||
},
|
||||
preferred_formats: {
|
||||
type: 'json',
|
||||
type: 'multiselect',
|
||||
label: 'Preferred Formats',
|
||||
placeholder: 'Define preferred content formats',
|
||||
required: false
|
||||
options: ['Blog Posts', 'Videos', 'Infographics', 'Webinars', 'Podcasts', 'Case Studies', 'Whitepapers', 'Social Media Posts'],
|
||||
required: true
|
||||
},
|
||||
content_mix: {
|
||||
type: 'json',
|
||||
@@ -477,44 +482,129 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
{/* Auto-population indicator */}
|
||||
{autoPopulated && (
|
||||
<Box sx={{ mb: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Chip
|
||||
icon={<AutoAwesomeIcon />}
|
||||
label={`Auto-populated from ${dataSource}`}
|
||||
color="info"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
{!isEditing && (
|
||||
<Tooltip title="Edit auto-populated value">
|
||||
<IconButton size="small" onClick={() => setIsEditing(true)}>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Box sx={{
|
||||
position: 'relative',
|
||||
mb: 1.5,
|
||||
p: 1.5,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: 'background.paper',
|
||||
border: '1px solid',
|
||||
borderColor: error ? 'error.main' : autoPopulated ? 'info.main' : 'divider',
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
transition: 'all 0.2s ease'
|
||||
}
|
||||
}}>
|
||||
{/* Field input - Enhanced styling */}
|
||||
<Box sx={{
|
||||
'& .MuiTextField-root, & .MuiFormControl-root': {
|
||||
'& .MuiInputBase-root': {
|
||||
borderRadius: 1,
|
||||
'&:hover': {
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'primary.main'
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .MuiInputLabel-root': {
|
||||
fontSize: '0.8rem',
|
||||
fontWeight: 500
|
||||
},
|
||||
'& .MuiInputBase-input': {
|
||||
fontSize: '0.85rem',
|
||||
padding: '8px 12px'
|
||||
}
|
||||
}
|
||||
}}>
|
||||
{renderInput()}
|
||||
</Box>
|
||||
|
||||
{/* Data Transparency and Auto-population Indicators */}
|
||||
<Box sx={{
|
||||
mt: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1
|
||||
}}>
|
||||
{/* Left side - Validation and Quality indicators */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{/* Validation status */}
|
||||
{value && !error && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<CheckCircleIcon color="success" sx={{ fontSize: 14 }} />
|
||||
<Typography variant="caption" color="success.main" sx={{ fontSize: '0.7rem' }}>
|
||||
Valid
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Data Quality indicator */}
|
||||
{dataQuality && (
|
||||
<Chip
|
||||
icon={<InfoIcon sx={{ fontSize: 12 }} />}
|
||||
label={dataQuality}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
sx={{
|
||||
fontSize: '0.6rem',
|
||||
height: 20,
|
||||
'& .MuiChip-label': { px: 1 }
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Confidence Level indicator */}
|
||||
{confidenceLevel && (
|
||||
<Chip
|
||||
label={`${Math.round(confidenceLevel * 100)}% confidence`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color={confidenceLevel > 0.8 ? 'success' : confidenceLevel > 0.6 ? 'warning' : 'error'}
|
||||
sx={{
|
||||
fontSize: '0.6rem',
|
||||
height: 20,
|
||||
'& .MuiChip-label': { px: 1 }
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Field input */}
|
||||
{renderInput()}
|
||||
|
||||
{/* Validation status */}
|
||||
{value && !error && (
|
||||
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircleIcon color="success" fontSize="small" />
|
||||
<Typography variant="caption" color="success.main">
|
||||
Valid
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{/* Right side - Auto-population indicator */}
|
||||
{autoPopulated && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<Chip
|
||||
icon={<AutoAwesomeIcon sx={{ fontSize: 12 }} />}
|
||||
label={`Auto-populated from ${dataSource}`}
|
||||
color="info"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
fontSize: '0.6rem',
|
||||
height: 20,
|
||||
'& .MuiChip-label': { px: 1 }
|
||||
}}
|
||||
/>
|
||||
{!isEditing && (
|
||||
<Tooltip title="Edit auto-populated value">
|
||||
<IconButton size="small" onClick={() => setIsEditing(true)} sx={{ width: 20, height: 20 }}>
|
||||
<EditIcon sx={{ fontSize: 12 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Error display */}
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mt: 1 }}>
|
||||
<Typography variant="body2">{error}</Typography>
|
||||
<Alert severity="error" sx={{ mt: 1, py: 0.5, '& .MuiAlert-message': { py: 0 } }}>
|
||||
<Typography variant="caption" sx={{ fontSize: '0.7rem' }}>
|
||||
{error}
|
||||
</Typography>
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
@@ -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';
|
||||
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
|
||||
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
||||
import EnhancedStrategyBuilder from '../components/EnhancedStrategyBuilder';
|
||||
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -429,20 +429,6 @@ const ContentStrategyTab: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Content Strategy Builder
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={handleRefreshData}
|
||||
disabled={loading}
|
||||
>
|
||||
Refresh Data
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
@@ -455,13 +441,12 @@ const ContentStrategyTab: React.FC = () => {
|
||||
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy builder tabs">
|
||||
<Tab
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<AutoAwesomeIcon />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<AutoAwesomeIcon sx={{ mr: 1 }} />
|
||||
Enhanced Strategy Builder
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<Tab label="Legacy Strategy Builder" />
|
||||
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
|
||||
<Tab label="Keyword Research" icon={<SearchIcon />} />
|
||||
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
|
||||
@@ -471,479 +456,340 @@ const ContentStrategyTab: React.FC = () => {
|
||||
|
||||
{/* Enhanced Strategy Builder Tab */}
|
||||
<TabPanel value={tabValue} index={0}>
|
||||
<EnhancedStrategyBuilder />
|
||||
</TabPanel>
|
||||
|
||||
{/* Legacy Strategy Builder Tab */}
|
||||
<TabPanel value={tabValue} index={1}>
|
||||
<Grid container spacing={3}>
|
||||
{/* Strategy Overview */}
|
||||
<Grid item xs={12} md={4}>
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Strategy Overview
|
||||
</Typography>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strategy Name"
|
||||
value={strategyForm.name}
|
||||
onChange={(e) => handleStrategyFormChange('name', e.target.value)}
|
||||
placeholder="Enter strategy name"
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
label="Strategy Description"
|
||||
value={strategyForm.description}
|
||||
onChange={(e) => handleStrategyFormChange('description', e.target.value)}
|
||||
placeholder="Describe your content strategy"
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Industry"
|
||||
value={strategyForm.industry}
|
||||
onChange={(e) => handleStrategyFormChange('industry', e.target.value)}
|
||||
placeholder="e.g., Technology, Healthcare, Finance"
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
startIcon={<AddIcon />}
|
||||
disabled={loading}
|
||||
onClick={handleCreateStrategy}
|
||||
>
|
||||
{loading ? 'Creating...' : 'Create Strategy'}
|
||||
</Button>
|
||||
</Paper>
|
||||
|
||||
{/* Performance Metrics */}
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Performance Metrics
|
||||
</Typography>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
{performanceMetrics ? (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Engagement Rate
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary">
|
||||
{performanceMetrics.engagement || 75.2}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Reach
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary">
|
||||
{(performanceMetrics.reach || 12500).toLocaleString()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Conversion Rate
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main">
|
||||
{performanceMetrics.conversion || 3.8}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
ROI
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main">
|
||||
${(performanceMetrics.roi || 14200).toLocaleString()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No performance data available
|
||||
</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<Grid item xs={12} md={8}>
|
||||
<Paper sx={{ width: '100%' }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy tabs">
|
||||
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
|
||||
<Tab label="Keyword Research" icon={<SearchIcon />} />
|
||||
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
|
||||
<Tab label="Content Pillars" icon={<PieChartIcon />} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{/* Strategic Intelligence Tab */}
|
||||
<TabPanel value={tabValue} index={2}>
|
||||
{dataLoading.strategicIntelligence ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : strategicIntelligence && strategicIntelligence.market_positioning ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Market Positioning
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={strategicIntelligence.market_positioning.score || 0}
|
||||
size={60}
|
||||
color="primary"
|
||||
/>
|
||||
<Typography variant="h4" sx={{ ml: 2 }}>
|
||||
{strategicIntelligence.market_positioning.score || 0}/100
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Strengths:
|
||||
</Typography>
|
||||
<List dense>
|
||||
{(strategicIntelligence.market_positioning.strengths || []).map((strength: string, index: number) => (
|
||||
<ListItem key={index}>
|
||||
<ListItemIcon>
|
||||
<CheckCircleIcon color="success" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={strength} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Competitive Advantages
|
||||
</Typography>
|
||||
{(strategicIntelligence.competitive_advantages || []).map((advantage: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{advantage.advantage}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
|
||||
<Chip
|
||||
label={advantage.impact}
|
||||
color={advantage.impact === 'High' ? 'success' : 'primary'}
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={advantage.implementation}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Strategic Risks
|
||||
</Typography>
|
||||
{(strategicIntelligence.strategic_risks || []).map((risk: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{risk.risk}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
|
||||
<Chip
|
||||
label={`Probability: ${risk.probability}`}
|
||||
color={risk.probability === 'High' ? 'error' : 'warning'}
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={`Impact: ${risk.impact}`}
|
||||
color={risk.impact === 'High' ? 'error' : 'warning'}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No strategic intelligence data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Keyword Research Tab */}
|
||||
<TabPanel value={tabValue} index={3}>
|
||||
{dataLoading.keywordResearch ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : keywordResearch && keywordResearch.trend_analysis ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
High Volume Keywords
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Keyword</TableCell>
|
||||
<TableCell>Volume</TableCell>
|
||||
<TableCell>Difficulty</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{keyword.keyword}</TableCell>
|
||||
<TableCell>{keyword.volume}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={keyword.difficulty}
|
||||
color={keyword.difficulty === 'Low' ? 'success' : keyword.difficulty === 'Medium' ? 'warning' : 'error'}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Trending Keywords
|
||||
</Typography>
|
||||
{(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{keyword.keyword}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label={keyword.growth}
|
||||
color="success"
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={keyword.opportunity}
|
||||
color={keyword.opportunity === 'High' ? 'success' : 'primary'}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Keyword Opportunities
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Keyword</TableCell>
|
||||
<TableCell>Search Volume</TableCell>
|
||||
<TableCell>Competition</TableCell>
|
||||
<TableCell>CPC</TableCell>
|
||||
<TableCell>Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{(keywordResearch.opportunities || []).map((opportunity: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{opportunity.keyword}</TableCell>
|
||||
<TableCell>{opportunity.search_volume}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={opportunity.competition}
|
||||
color={opportunity.competition === 'Low' ? 'success' : opportunity.competition === 'Medium' ? 'warning' : 'error'}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>${opportunity.cpc}</TableCell>
|
||||
<TableCell>
|
||||
<Button size="small" variant="outlined">
|
||||
Add to Strategy
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No keyword research data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Performance Analytics Tab */}
|
||||
<TabPanel value={tabValue} index={4}>
|
||||
{performanceMetrics ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Content Performance by Type
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No content performance data available
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Growth Trends
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No trend data available
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No performance analytics data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Content Pillars Tab */}
|
||||
<TabPanel value={tabValue} index={5}>
|
||||
{dataLoading.pillars ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : contentPillars.length > 0 ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Content Pillars Overview
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics.
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
{contentPillars.map((pillar, index) => (
|
||||
<Grid item xs={12} md={6} key={index}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{pillar.name}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Content Count
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{pillar.content_count}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Avg. Engagement
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{pillar.avg_engagement}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Performance Score
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main">
|
||||
{pillar.performance_score}/100
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small">View Content</Button>
|
||||
<Button size="small">Optimize</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No content pillars data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ContentStrategyBuilder />
|
||||
</TabPanel>
|
||||
|
||||
{/* Strategic Intelligence Tab */}
|
||||
<TabPanel value={tabValue} index={2}>
|
||||
{/* Content moved to Legacy Strategy Builder */}
|
||||
<TabPanel value={tabValue} index={1}>
|
||||
{dataLoading.strategicIntelligence ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : strategicIntelligence && strategicIntelligence.market_positioning ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Market Positioning
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={strategicIntelligence.market_positioning.score || 0}
|
||||
size={60}
|
||||
color="primary"
|
||||
/>
|
||||
<Typography variant="h4" sx={{ ml: 2 }}>
|
||||
{strategicIntelligence.market_positioning.score || 0}/100
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Strengths:
|
||||
</Typography>
|
||||
<List dense>
|
||||
{(strategicIntelligence.market_positioning.strengths || []).map((strength: string, index: number) => (
|
||||
<ListItem key={index}>
|
||||
<ListItemIcon>
|
||||
<CheckCircleIcon color="success" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={strength} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Competitive Advantages
|
||||
</Typography>
|
||||
{(strategicIntelligence.competitive_advantages || []).map((advantage: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{advantage.advantage}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
|
||||
<Chip
|
||||
label={advantage.impact}
|
||||
color={advantage.impact === 'High' ? 'success' : 'primary'}
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={advantage.implementation}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Strategic Risks
|
||||
</Typography>
|
||||
{(strategicIntelligence.strategic_risks || []).map((risk: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{risk.risk}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
|
||||
<Chip
|
||||
label={`Probability: ${risk.probability}`}
|
||||
color={risk.probability === 'High' ? 'error' : 'warning'}
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={`Impact: ${risk.impact}`}
|
||||
color={risk.impact === 'High' ? 'error' : 'warning'}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No strategic intelligence data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Keyword Research Tab */}
|
||||
<TabPanel value={tabValue} index={3}>
|
||||
{/* Content moved to Legacy Strategy Builder */}
|
||||
<TabPanel value={tabValue} index={2}>
|
||||
{dataLoading.keywordResearch ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : keywordResearch && keywordResearch.trend_analysis ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
High Volume Keywords
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Keyword</TableCell>
|
||||
<TableCell>Volume</TableCell>
|
||||
<TableCell>Difficulty</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{keyword.keyword}</TableCell>
|
||||
<TableCell>{keyword.volume}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={keyword.difficulty}
|
||||
color={keyword.difficulty === 'Low' ? 'success' : keyword.difficulty === 'Medium' ? 'warning' : 'error'}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Trending Keywords
|
||||
</Typography>
|
||||
{(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{keyword.keyword}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label={keyword.growth}
|
||||
color="success"
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={keyword.opportunity}
|
||||
color={keyword.opportunity === 'High' ? 'success' : 'primary'}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Keyword Opportunities
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Keyword</TableCell>
|
||||
<TableCell>Search Volume</TableCell>
|
||||
<TableCell>Competition</TableCell>
|
||||
<TableCell>CPC</TableCell>
|
||||
<TableCell>Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{(keywordResearch.opportunities || []).map((opportunity: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{opportunity.keyword}</TableCell>
|
||||
<TableCell>{opportunity.search_volume}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={opportunity.competition}
|
||||
color={opportunity.competition === 'Low' ? 'success' : opportunity.competition === 'Medium' ? 'warning' : 'error'}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>${opportunity.cpc}</TableCell>
|
||||
<TableCell>
|
||||
<Button size="small" variant="outlined">
|
||||
Add to Strategy
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No keyword research data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Performance Analytics Tab */}
|
||||
<TabPanel value={tabValue} index={4}>
|
||||
{/* Content moved to Legacy Strategy Builder */}
|
||||
<TabPanel value={tabValue} index={3}>
|
||||
{performanceMetrics ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Content Performance by Type
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No content performance data available
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Growth Trends
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No trend data available
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No performance analytics data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Content Pillars Tab */}
|
||||
<TabPanel value={tabValue} index={5}>
|
||||
{/* Content moved to Legacy Strategy Builder */}
|
||||
<TabPanel value={tabValue} index={4}>
|
||||
{dataLoading.pillars ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : contentPillars.length > 0 ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Content Pillars Overview
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics.
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
{contentPillars.map((pillar, index) => (
|
||||
<Grid item xs={12} md={6} key={index}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{pillar.name}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Content Count
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{pillar.content_count}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Avg. Engagement
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{pillar.avg_engagement}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Performance Score
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main">
|
||||
{pillar.performance_score}/100
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small">View Content</Button>
|
||||
<Button size="small">Optimize</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No content pillars data available
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -918,11 +918,33 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
|
||||
const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
|
||||
const filledRequiredFields = requiredFields.filter(field => {
|
||||
const value = formData[field.id];
|
||||
const isValid = value &&
|
||||
(typeof value === 'string' ? value.trim() !== '' : true) &&
|
||||
(!Array.isArray(value) || value.length > 0);
|
||||
|
||||
console.log(`📊 Field ${field.id}: ${isValid ? '✅' : '❌'} (${value})`);
|
||||
// Enhanced validation logic for different field types
|
||||
let isValid = false;
|
||||
|
||||
if (value) {
|
||||
if (field.type === 'multiselect') {
|
||||
// For multiselect, check if it's an array with at least one item
|
||||
isValid = Array.isArray(value) && value.length > 0;
|
||||
} else if (field.type === 'select') {
|
||||
// For select, check if it's a non-empty string
|
||||
isValid = typeof value === 'string' && value.trim() !== '';
|
||||
} else if (typeof value === 'string') {
|
||||
// For text fields, check if it's not empty
|
||||
isValid = value.trim() !== '';
|
||||
} else if (typeof value === 'number') {
|
||||
// For number fields, check if it's not null/undefined
|
||||
isValid = value !== null && value !== undefined;
|
||||
} else if (Array.isArray(value)) {
|
||||
// For arrays (json fields), check if it has items
|
||||
isValid = value.length > 0;
|
||||
} else {
|
||||
// For other types, just check if it exists
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`📊 Field ${field.id}: ${isValid ? '✅' : '❌'} (${value}) [Type: ${field.type}]`);
|
||||
return isValid;
|
||||
});
|
||||
|
||||
@@ -940,19 +962,57 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
|
||||
|
||||
categories.forEach(category => {
|
||||
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
|
||||
const filledFields = categoryFields.filter(field =>
|
||||
formData[field.id] &&
|
||||
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
|
||||
);
|
||||
const filledFields = categoryFields.filter(field => {
|
||||
const value = formData[field.id];
|
||||
|
||||
if (!value) return false;
|
||||
|
||||
// Enhanced validation logic for different field types
|
||||
if (field.type === 'multiselect') {
|
||||
// For multiselect, check if it's an array with at least one item
|
||||
return Array.isArray(value) && value.length > 0;
|
||||
} else if (field.type === 'select') {
|
||||
// For select, check if it's a non-empty string
|
||||
return typeof value === 'string' && value.trim() !== '';
|
||||
} else if (typeof value === 'string') {
|
||||
// For text fields, check if it's not empty
|
||||
return value.trim() !== '';
|
||||
} else if (typeof value === 'number') {
|
||||
// For number fields, check if it's not null/undefined
|
||||
return value !== null && value !== undefined;
|
||||
} else if (Array.isArray(value)) {
|
||||
// For arrays (json fields), check if it has items
|
||||
return value.length > 0;
|
||||
} else {
|
||||
// For other types, just check if it exists
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
category_completion[category] = (filledFields.length / categoryFields.length) * 100;
|
||||
});
|
||||
|
||||
const total_fields = STRATEGIC_INPUT_FIELDS.length;
|
||||
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field =>
|
||||
formData[field.id] &&
|
||||
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
|
||||
).length;
|
||||
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field => {
|
||||
const value = formData[field.id];
|
||||
|
||||
if (!value) return false;
|
||||
|
||||
// Enhanced validation logic for different field types
|
||||
if (field.type === 'multiselect') {
|
||||
return Array.isArray(value) && value.length > 0;
|
||||
} else if (field.type === 'select') {
|
||||
return typeof value === 'string' && value.trim() !== '';
|
||||
} else if (typeof value === 'string') {
|
||||
return value.trim() !== '';
|
||||
} else if (typeof value === 'number') {
|
||||
return value !== null && value !== undefined;
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).length;
|
||||
|
||||
return {
|
||||
total_fields,
|
||||
|
||||
Reference in New Issue
Block a user