ALwrity version 0.5.5

This commit is contained in:
ajaysi
2025-08-19 21:48:33 +05:30
parent 5f104bf427
commit 74e22b421a
97 changed files with 16770 additions and 5000 deletions

View File

@@ -38,6 +38,7 @@ import {
ServiceStatus,
DashboardData
} from '../../services/contentPlanningOrchestrator';
import { StrategyCalendarProvider } from '../../contexts/StrategyCalendarContext';
interface TabPanelProps {
children?: React.ReactNode;
@@ -188,7 +189,8 @@ const ContentPlanningDashboard: React.FC = () => {
const totalAIItems = (dashboardData.aiInsights?.length || 0) + (dashboardData.aiRecommendations?.length || 0);
return (
<Container maxWidth={false} sx={{ height: '100vh', p: 0 }}>
<StrategyCalendarProvider>
<Container maxWidth={false} sx={{ height: '100vh', p: 0 }}>
<AppBar position="static" color="default" elevation={1}>
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
@@ -325,7 +327,8 @@ const ContentPlanningDashboard: React.FC = () => {
</AnimatePresence>
</Box>
</Drawer>
</Container>
</Container>
</StrategyCalendarProvider>
);
};

View File

@@ -0,0 +1,493 @@
import React from 'react';
import {
Box,
Typography,
Grid,
Card,
CardContent,
CardHeader,
FormControl,
InputLabel,
Select,
MenuItem,
TextField,
Slider,
FormControlLabel,
Checkbox,
Alert,
IconButton,
Tooltip,
Chip,
Switch,
Divider
} from '@mui/material';
import {
AutoAwesome as AIIcon,
Speed as SpeedIcon,
Analytics as AnalyticsIcon,
TrendingUp as TrendingIcon,
Psychology as PsychologyIcon,
Info as InfoIcon,
Settings as SettingsIcon,
Assessment as AssessmentIcon
} from '@mui/icons-material';
interface AdvancedOptionsStepProps {
calendarConfig: any;
onConfigUpdate: (updates: any) => void;
strategyContext?: any;
}
const AdvancedOptionsStep: React.FC<AdvancedOptionsStepProps> = ({
calendarConfig,
onConfigUpdate,
strategyContext
}) => {
const handlePerformancePredictionChange = (metric: string, value: number) => {
const newPredictions = { ...calendarConfig.performancePredictions, [metric]: value };
onConfigUpdate({ performancePredictions: newPredictions });
};
const handleAdvancedSettingChange = (setting: string, value: any) => {
const newAdvancedSettings = { ...calendarConfig.advancedSettings, [setting]: value };
onConfigUpdate({ advancedSettings: newAdvancedSettings });
};
return (
<Box>
<Typography variant="h6" gutterBottom>
Advanced Options & Optimization
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Configure advanced settings for AI-powered optimization, performance predictions, and content strategy enhancement.
</Typography>
<Grid container spacing={3}>
{/* AI Optimization Settings */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<AIIcon color="primary" />}
title="AI Optimization Settings"
action={
<Tooltip title="Configure AI-powered optimization features">
<IconButton size="small">
<InfoIcon />
</IconButton>
</Tooltip>
}
/>
<CardContent>
<Box sx={{ mb: 3 }}>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.aiOptimization || false}
onChange={(e) => handleAdvancedSettingChange('aiOptimization', e.target.checked)}
/>
}
label="Enable AI Content Optimization"
/>
<Typography variant="body2" color="text.secondary">
AI will automatically optimize content titles, descriptions, and timing for maximum engagement
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.smartScheduling || false}
onChange={(e) => handleAdvancedSettingChange('smartScheduling', e.target.checked)}
/>
}
label="Smart Scheduling"
/>
<Typography variant="body2" color="text.secondary">
Automatically adjust posting times based on audience behavior and engagement patterns
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.trendIntegration || false}
onChange={(e) => handleAdvancedSettingChange('trendIntegration', e.target.checked)}
/>
}
label="Trend Integration"
/>
<Typography variant="body2" color="text.secondary">
Incorporate trending topics and hashtags into your content calendar
</Typography>
</Box>
<Box>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.competitiveAnalysis || false}
onChange={(e) => handleAdvancedSettingChange('competitiveAnalysis', e.target.checked)}
/>
}
label="Competitive Analysis"
/>
<Typography variant="body2" color="text.secondary">
Monitor competitor content and adjust strategy based on their performance
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
{/* Performance Predictions */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<AnalyticsIcon color="primary" />}
title="Performance Predictions"
action={
<Tooltip title="Set performance targets and predictions">
<IconButton size="small">
<InfoIcon />
</IconButton>
</Tooltip>
}
/>
<CardContent>
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" gutterBottom>
Expected Traffic Growth
</Typography>
<Slider
value={calendarConfig.performancePredictions?.trafficGrowth || 25}
onChange={(_, value) => handlePerformancePredictionChange('trafficGrowth', value as number)}
min={0}
max={100}
valueLabelDisplay="auto"
marks={[
{ value: 0, label: '0%' },
{ value: 25, label: '25%' },
{ value: 50, label: '50%' },
{ value: 75, label: '75%' },
{ value: 100, label: '100%' }
]}
/>
<Typography variant="body2" color="text.secondary">
Predicted increase in website traffic from content calendar
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" gutterBottom>
Engagement Rate Target
</Typography>
<Slider
value={calendarConfig.performancePredictions?.engagementRate || 15}
onChange={(_, value) => handlePerformancePredictionChange('engagementRate', value as number)}
min={0}
max={50}
valueLabelDisplay="auto"
marks={[
{ value: 0, label: '0%' },
{ value: 10, label: '10%' },
{ value: 20, label: '20%' },
{ value: 30, label: '30%' },
{ value: 40, label: '40%' },
{ value: 50, label: '50%' }
]}
/>
<Typography variant="body2" color="text.secondary">
Target engagement rate for social media content
</Typography>
</Box>
<Box>
<Typography variant="subtitle2" gutterBottom>
Conversion Rate Target
</Typography>
<Slider
value={calendarConfig.performancePredictions?.conversionRate || 10}
onChange={(_, value) => handlePerformancePredictionChange('conversionRate', value as number)}
min={0}
max={25}
valueLabelDisplay="auto"
marks={[
{ value: 0, label: '0%' },
{ value: 5, label: '5%' },
{ value: 10, label: '10%' },
{ value: 15, label: '15%' },
{ value: 20, label: '20%' },
{ value: 25, label: '25%' }
]}
/>
<Typography variant="body2" color="text.secondary">
Target conversion rate from content to leads/sales
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
{/* Content Strategy Enhancement */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<TrendingIcon color="primary" />}
title="Content Strategy Enhancement"
action={
<Tooltip title="Advanced content strategy settings">
<IconButton size="small">
<InfoIcon />
</IconButton>
</Tooltip>
}
/>
<CardContent>
<Box sx={{ mb: 3 }}>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Content Repurposing Strategy</InputLabel>
<Select
value={calendarConfig.advancedSettings?.repurposingStrategy || 'moderate'}
label="Content Repurposing Strategy"
onChange={(e) => handleAdvancedSettingChange('repurposingStrategy', e.target.value)}
>
<MenuItem value="minimal">Minimal (Original content only)</MenuItem>
<MenuItem value="moderate">Moderate (Some repurposing)</MenuItem>
<MenuItem value="aggressive">Aggressive (Maximum repurposing)</MenuItem>
</Select>
</FormControl>
<Typography variant="body2" color="text.secondary">
How much content should be repurposed across different formats and platforms
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Content Personalization Level</InputLabel>
<Select
value={calendarConfig.advancedSettings?.personalizationLevel || 'medium'}
label="Content Personalization Level"
onChange={(e) => handleAdvancedSettingChange('personalizationLevel', e.target.value)}
>
<MenuItem value="low">Low (Generic content)</MenuItem>
<MenuItem value="medium">Medium (Some personalization)</MenuItem>
<MenuItem value="high">High (Highly personalized)</MenuItem>
</Select>
</FormControl>
<Typography variant="body2" color="text.secondary">
Level of audience personalization in content creation
</Typography>
</Box>
<Box>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Content Innovation Level</InputLabel>
<Select
value={calendarConfig.advancedSettings?.innovationLevel || 'balanced'}
label="Content Innovation Level"
onChange={(e) => handleAdvancedSettingChange('innovationLevel', e.target.value)}
>
<MenuItem value="conservative">Conservative (Proven formats)</MenuItem>
<MenuItem value="balanced">Balanced (Mix of proven and new)</MenuItem>
<MenuItem value="innovative">Innovative (Experimental content)</MenuItem>
</Select>
</FormControl>
<Typography variant="body2" color="text.secondary">
Balance between proven content formats and experimental approaches
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
{/* Audience Behavior Optimization */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<PsychologyIcon color="primary" />}
title="Audience Behavior Optimization"
action={
<Tooltip title="Optimize for audience behavior patterns">
<IconButton size="small">
<InfoIcon />
</IconButton>
</Tooltip>
}
/>
<CardContent>
<Box sx={{ mb: 3 }}>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.audienceSegmentation || false}
onChange={(e) => handleAdvancedSettingChange('audienceSegmentation', e.target.checked)}
/>
}
label="Audience Segmentation"
/>
<Typography variant="body2" color="text.secondary">
Create different content for different audience segments
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.behavioralTargeting || false}
onChange={(e) => handleAdvancedSettingChange('behavioralTargeting', e.target.checked)}
/>
}
label="Behavioral Targeting"
/>
<Typography variant="body2" color="text.secondary">
Target content based on user behavior and preferences
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.journeyMapping || false}
onChange={(e) => handleAdvancedSettingChange('journeyMapping', e.target.checked)}
/>
}
label="Customer Journey Mapping"
/>
<Typography variant="body2" color="text.secondary">
Align content with different stages of the customer journey
</Typography>
</Box>
<Box>
<FormControlLabel
control={
<Switch
checked={calendarConfig.advancedSettings?.sentimentAnalysis || false}
onChange={(e) => handleAdvancedSettingChange('sentimentAnalysis', e.target.checked)}
/>
}
label="Sentiment Analysis"
/>
<Typography variant="body2" color="text.secondary">
Monitor and respond to audience sentiment in content planning
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
{/* Performance Monitoring */}
<Grid item xs={12}>
<Card>
<CardHeader
avatar={<AssessmentIcon color="primary" />}
title="Performance Monitoring & Analytics"
action={
<Tooltip title="Configure performance monitoring settings">
<IconButton size="small">
<InfoIcon />
</IconButton>
</Tooltip>
}
/>
<CardContent>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<FormControl fullWidth>
<InputLabel>Monitoring Frequency</InputLabel>
<Select
value={calendarConfig.advancedSettings?.monitoringFrequency || 'weekly'}
label="Monitoring Frequency"
onChange={(e) => handleAdvancedSettingChange('monitoringFrequency', e.target.value)}
>
<MenuItem value="daily">Daily</MenuItem>
<MenuItem value="weekly">Weekly</MenuItem>
<MenuItem value="monthly">Monthly</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} md={4}>
<FormControl fullWidth>
<InputLabel>Alert Threshold</InputLabel>
<Select
value={calendarConfig.advancedSettings?.alertThreshold || 'medium'}
label="Alert Threshold"
onChange={(e) => handleAdvancedSettingChange('alertThreshold', e.target.value)}
>
<MenuItem value="low">Low (Minimal alerts)</MenuItem>
<MenuItem value="medium">Medium (Balanced alerts)</MenuItem>
<MenuItem value="high">High (Frequent alerts)</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} md={4}>
<FormControl fullWidth>
<InputLabel>Optimization Frequency</InputLabel>
<Select
value={calendarConfig.advancedSettings?.optimizationFrequency || 'bi-weekly'}
label="Optimization Frequency"
onChange={(e) => handleAdvancedSettingChange('optimizationFrequency', e.target.value)}
>
<MenuItem value="weekly">Weekly</MenuItem>
<MenuItem value="bi-weekly">Bi-weekly</MenuItem>
<MenuItem value="monthly">Monthly</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
<Divider sx={{ my: 2 }} />
<Box>
<Typography variant="subtitle2" gutterBottom>
Key Performance Indicators (KPIs)
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{[
'Traffic Growth',
'Engagement Rate',
'Conversion Rate',
'Brand Awareness',
'Lead Generation',
'Social Reach',
'Content Quality Score',
'ROI'
].map((kpi) => (
<Chip
key={kpi}
label={kpi}
color="primary"
variant="outlined"
size="small"
/>
))}
</Box>
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Advanced Settings Summary */}
<Alert severity="info" sx={{ mt: 3 }}>
<Typography variant="subtitle2" gutterBottom>
Advanced Configuration Summary
</Typography>
<Typography variant="body2">
AI optimization is {calendarConfig.advancedSettings?.aiOptimization ? 'enabled' : 'disabled'},
smart scheduling is {calendarConfig.advancedSettings?.smartScheduling ? 'enabled' : 'disabled'},
and performance monitoring is set to {calendarConfig.advancedSettings?.monitoringFrequency || 'weekly'} frequency.
Expected traffic growth: {calendarConfig.performancePredictions?.trafficGrowth || 25}%.
</Typography>
</Alert>
</Box>
);
};
export default AdvancedOptionsStep;

View File

@@ -0,0 +1,547 @@
import React from 'react';
import {
Box,
Typography,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
FormControlLabel,
Checkbox,
Grid,
Card,
CardContent,
Tooltip,
IconButton,
Alert,
FormHelperText
} from '@mui/material';
import {
CalendarToday as CalendarIcon,
Schedule as ScheduleIcon,
Help as HelpIcon,
TrendingUp as TrendingUpIcon,
Public as PublicIcon,
AccessTime as AccessTimeIcon,
ContentPaste as ContentPasteIcon
} from '@mui/icons-material';
// Import calendar-specific types
import { type CalendarConfig } from './types';
interface CalendarConfigurationStepProps {
calendarConfig: CalendarConfig;
onConfigUpdate: (updates: Partial<CalendarConfig>) => void;
strategyContext?: any;
isFromStrategyActivation?: boolean; // Strategy context available for generation
}
// Enhanced styling with better input prominence and readability
const ENHANCED_STYLES = {
card: {
borderRadius: 2,
background: 'rgba(255, 255, 255, 0.95)',
color: '#333',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.1)',
border: '1px solid rgba(0, 0, 0, 0.1)',
position: 'relative' as const,
overflow: 'hidden',
'&:hover': {
boxShadow: '0 6px 25px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
},
cardContent: {
p: 3,
position: 'relative' as const,
zIndex: 1
},
sectionHeader: {
display: 'flex',
alignItems: 'center',
mb: 3,
'& .MuiTypography-root': {
fontWeight: 600,
color: '#2c3e50'
}
},
iconContainer: {
p: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
mr: 2,
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
formControl: {
'& .MuiInputLabel-root': {
color: '#555',
fontWeight: 500,
'&.Mui-focused': {
color: '#667eea'
}
},
'& .MuiOutlinedInput-root': {
color: '#333',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
'& fieldset': {
borderColor: 'rgba(0, 0, 0, 0.2)',
borderWidth: '2px'
},
'&:hover fieldset': {
borderColor: 'rgba(102, 126, 234, 0.5)'
},
'&.Mui-focused fieldset': {
borderColor: '#667eea',
borderWidth: '2px'
}
},
'& .MuiSelect-icon': {
color: '#555'
}
},
textField: {
'& .MuiInputLabel-root': {
color: '#555',
fontWeight: 500,
'&.Mui-focused': {
color: '#667eea'
}
},
'& .MuiOutlinedInput-root': {
color: '#333',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
'& fieldset': {
borderColor: 'rgba(0, 0, 0, 0.2)',
borderWidth: '2px'
},
'&:hover fieldset': {
borderColor: 'rgba(102, 126, 234, 0.5)'
},
'&.Mui-focused fieldset': {
borderColor: '#667eea',
borderWidth: '2px'
}
}
},
platformCard: {
background: 'rgba(255, 255, 255, 0.9)',
border: '2px solid rgba(0, 0, 0, 0.1)',
cursor: 'pointer',
transition: 'all 0.3s ease',
'&:hover': {
background: 'rgba(102, 126, 234, 0.05)',
border: '2px solid rgba(102, 126, 234, 0.3)',
transform: 'translateY(-2px)'
},
'&.selected': {
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)',
border: '2px solid rgba(102, 126, 234, 0.5)',
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.2)'
}
},
checkbox: {
color: '#b0b0b0',
'&.Mui-checked': {
color: '#667eea'
}
}
};
const CalendarConfigurationStep: React.FC<CalendarConfigurationStepProps> = ({
calendarConfig,
onConfigUpdate,
strategyContext,
isFromStrategyActivation = false
}) => {
// Enhanced calendar-specific handlers
const handleCalendarTypeChange = (type: 'weekly' | 'monthly' | 'quarterly') => {
onConfigUpdate({ calendarType: type });
};
const handleDurationChange = (duration: number) => {
onConfigUpdate({ calendarDuration: duration });
};
const handleStartDateChange = (date: string) => {
onConfigUpdate({ startDate: date });
};
const handlePostingFrequencyChange = (frequency: number) => {
onConfigUpdate({ postingFrequency: frequency });
};
const handleContentVolumeChange = (volume: number) => {
onConfigUpdate({ contentVolume: volume });
};
const handlePlatformChange = (platform: string, checked: boolean) => {
let newPlatforms = [...(calendarConfig.priorityPlatforms || [])];
if (checked) {
newPlatforms.push(platform);
} else {
newPlatforms = newPlatforms.filter(p => p !== platform);
}
onConfigUpdate({ priorityPlatforms: newPlatforms });
};
const handleTimeZoneChange = (timezone: string) => {
onConfigUpdate({ timeZone: timezone });
};
const handleContentDistributionChange = (distribution: 'even' | 'frontloaded' | 'backloaded') => {
onConfigUpdate({ contentDistribution: distribution });
};
const handleReviewCycleChange = (cycle: 'weekly' | 'monthly' | 'quarterly') => {
onConfigUpdate({ reviewCycle: cycle });
};
const availablePlatforms = [
{ value: 'LinkedIn', label: 'LinkedIn', icon: '💼', description: 'Professional networking and B2B content' },
{ value: 'Twitter', label: 'Twitter/X', icon: '🐦', description: 'Real-time updates and engagement' },
{ value: 'Facebook', label: 'Facebook', icon: '📘', description: 'Community building and brand awareness' },
{ value: 'Instagram', label: 'Instagram', icon: '📸', description: 'Visual content and storytelling' },
{ value: 'YouTube', label: 'YouTube', icon: '📺', description: 'Video content and tutorials' },
{ value: 'Blog', label: 'Blog/Website', icon: '📝', description: 'Long-form content and SEO' },
{ value: 'Email', label: 'Email Newsletter', icon: '📧', description: 'Direct communication and nurturing' }
];
const timeZones = [
{ value: 'America/New_York', label: 'Eastern Time (ET)' },
{ value: 'America/Chicago', label: 'Central Time (CT)' },
{ value: 'America/Denver', label: 'Mountain Time (MT)' },
{ value: 'America/Los_Angeles', label: 'Pacific Time (PT)' },
{ value: 'Europe/London', label: 'London (GMT/BST)' },
{ value: 'Europe/Paris', label: 'Paris (CET/CEST)' },
{ value: 'Asia/Tokyo', label: 'Tokyo (JST)' },
{ value: 'Asia/Shanghai', label: 'Shanghai (CST)' },
{ value: 'Australia/Sydney', label: 'Sydney (AEST/AEDT)' }
];
return (
<Box sx={{ p: 2 }}>
{/* Header with Strategy Context */}
<Box sx={{ mb: 4 }}>
<Typography variant="h4" gutterBottom sx={{
fontWeight: 700,
color: '#2c3e50',
mb: 1
}}>
Calendar Configuration
</Typography>
<Typography variant="body1" color="#555" sx={{ mb: 2 }}>
Configure your content calendar settings to create an optimized publishing schedule.
</Typography>
{isFromStrategyActivation && (
<Alert severity="success" sx={{
background: 'rgba(76, 175, 80, 0.1)',
border: '1px solid rgba(76, 175, 80, 0.3)',
color: '#2e7d32'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TrendingUpIcon />
<Typography variant="body2">
Strategy context available - your activated strategy will enhance calendar generation
</Typography>
</Box>
</Alert>
)}
</Box>
{/* Basic Calendar Setup */}
<Card sx={{ ...ENHANCED_STYLES.card, mb: 3 }}>
<CardContent sx={ENHANCED_STYLES.cardContent}>
<Box sx={ENHANCED_STYLES.sectionHeader}>
<Box sx={ENHANCED_STYLES.iconContainer}>
<CalendarIcon sx={{ color: 'white', fontSize: 24 }} />
</Box>
<Typography variant="h5">
Basic Calendar Setup
</Typography>
<Tooltip title="Configure the fundamental structure of your content calendar">
<IconButton size="small" sx={{ ml: 1, color: '#555' }}>
<HelpIcon />
</IconButton>
</Tooltip>
</Box>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<FormControl fullWidth sx={ENHANCED_STYLES.formControl}>
<InputLabel>Calendar Type</InputLabel>
<Select
value={calendarConfig.calendarType}
onChange={(e) => handleCalendarTypeChange(e.target.value as 'weekly' | 'monthly' | 'quarterly')}
label="Calendar Type"
>
<MenuItem value="weekly">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<span>📅</span>
<Box>
<Typography variant="body2" fontWeight={600}>Weekly</Typography>
<Typography variant="caption" color="#666">7-day planning cycles</Typography>
</Box>
</Box>
</MenuItem>
<MenuItem value="monthly">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<span>📆</span>
<Box>
<Typography variant="body2" fontWeight={600}>Monthly</Typography>
<Typography variant="caption" color="#666">30-day planning cycles</Typography>
</Box>
</Box>
</MenuItem>
<MenuItem value="quarterly">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<span>📊</span>
<Box>
<Typography variant="body2" fontWeight={600}>Quarterly</Typography>
<Typography variant="caption" color="#666">90-day strategic planning</Typography>
</Box>
</Box>
</MenuItem>
</Select>
<FormHelperText sx={{ color: '#666' }}>
Choose your planning cycle - affects content volume and scheduling
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<TextField
fullWidth
label="Start Date"
type="date"
value={calendarConfig.startDate}
onChange={(e) => handleStartDateChange(e.target.value)}
InputLabelProps={{ shrink: true }}
sx={ENHANCED_STYLES.textField}
InputProps={{
startAdornment: <AccessTimeIcon sx={{ mr: 1, color: '#666' }} />
}}
/>
<FormHelperText sx={{ color: '#666' }}>
When should your calendar begin?
</FormHelperText>
</Grid>
<Grid item xs={12} md={4}>
<TextField
fullWidth
label="Duration (periods)"
type="number"
value={calendarConfig.calendarDuration}
onChange={(e) => handleDurationChange(parseInt(e.target.value) || 1)}
inputProps={{ min: 1, max: 52 }}
sx={ENHANCED_STYLES.textField}
InputProps={{
startAdornment: <ContentPasteIcon sx={{ mr: 1, color: '#666' }} />
}}
/>
<FormHelperText sx={{ color: '#666' }}>
Number of {calendarConfig.calendarType === 'weekly' ? 'weeks' :
calendarConfig.calendarType === 'monthly' ? 'months' : 'quarters'} to generate
</FormHelperText>
</Grid>
<Grid item xs={12} md={4}>
<TextField
fullWidth
label="Posts per Week"
type="number"
value={calendarConfig.postingFrequency}
onChange={(e) => handlePostingFrequencyChange(parseInt(e.target.value) || 1)}
inputProps={{ min: 1, max: 7 }}
sx={ENHANCED_STYLES.textField}
InputProps={{
startAdornment: <ScheduleIcon sx={{ mr: 1, color: '#666' }} />
}}
/>
<FormHelperText sx={{ color: '#666' }}>
How many posts should be published weekly?
</FormHelperText>
</Grid>
<Grid item xs={12} md={4}>
<TextField
fullWidth
label="Content Volume per Period"
type="number"
value={calendarConfig.contentVolume}
onChange={(e) => handleContentVolumeChange(parseInt(e.target.value) || 1)}
inputProps={{ min: 1 }}
sx={ENHANCED_STYLES.textField}
InputProps={{
startAdornment: <ContentPasteIcon sx={{ mr: 1, color: '#666' }} />
}}
/>
<FormHelperText sx={{ color: '#666' }}>
Total content pieces per {calendarConfig.calendarType === 'weekly' ? 'week' :
calendarConfig.calendarType === 'monthly' ? 'month' : 'quarter'}
</FormHelperText>
</Grid>
</Grid>
</CardContent>
</Card>
{/* Platform & Scheduling Preferences */}
<Card sx={{ ...ENHANCED_STYLES.card, mb: 3 }}>
<CardContent sx={ENHANCED_STYLES.cardContent}>
<Box sx={ENHANCED_STYLES.sectionHeader}>
<Box sx={ENHANCED_STYLES.iconContainer}>
<PublicIcon sx={{ color: 'white', fontSize: 24 }} />
</Box>
<Typography variant="h5">
Platform & Scheduling Preferences
</Typography>
<Tooltip title="Select your content distribution platforms and scheduling preferences">
<IconButton size="small" sx={{ ml: 1, color: '#555' }}>
<HelpIcon />
</IconButton>
</Tooltip>
</Box>
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom sx={{ color: '#2c3e50', mb: 2 }}>
Priority Platforms
</Typography>
<Typography variant="body2" color="#666" sx={{ mb: 2 }}>
Select the platforms where you'll publish your content. Choose platforms that align with your audience and content strategy.
</Typography>
<Grid container spacing={2}>
{availablePlatforms.map((platform) => (
<Grid item xs={12} sm={6} md={4} key={platform.value}>
<Card
sx={{
...ENHANCED_STYLES.platformCard,
...(calendarConfig.priorityPlatforms.includes(platform.value) && { className: 'selected' })
}}
onClick={() => handlePlatformChange(platform.value, !calendarConfig.priorityPlatforms.includes(platform.value))}
>
<CardContent sx={{ p: 2 }}>
<FormControlLabel
control={
<Checkbox
checked={calendarConfig.priorityPlatforms.includes(platform.value)}
onChange={(e) => handlePlatformChange(platform.value, e.target.checked)}
sx={ENHANCED_STYLES.checkbox}
/>
}
label={
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
<span style={{ fontSize: '1.2rem' }}>{platform.icon}</span>
<Typography variant="body2" fontWeight={600} color="#2c3e50">
{platform.label}
</Typography>
</Box>
<Typography variant="caption" color="#666">
{platform.description}
</Typography>
</Box>
}
sx={{ m: 0, width: '100%' }}
/>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Grid>
<Grid item xs={12} md={6}>
<FormControl fullWidth sx={ENHANCED_STYLES.formControl}>
<InputLabel>Time Zone</InputLabel>
<Select
value={calendarConfig.timeZone}
onChange={(e) => handleTimeZoneChange(e.target.value)}
label="Time Zone"
>
{timeZones.map((tz) => (
<MenuItem key={tz.value} value={tz.value}>
{tz.label}
</MenuItem>
))}
</Select>
<FormHelperText sx={{ color: '#666' }}>Your local timezone for accurate scheduling</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl fullWidth sx={ENHANCED_STYLES.formControl}>
<InputLabel>Content Distribution</InputLabel>
<Select
value={calendarConfig.contentDistribution}
onChange={(e) => handleContentDistributionChange(e.target.value as 'even' | 'frontloaded' | 'backloaded')}
label="Content Distribution"
>
<MenuItem value="even">
<Box>
<Typography variant="body2" fontWeight={600}>Even Distribution</Typography>
<Typography variant="caption" color="#666">Consistent posting throughout the period</Typography>
</Box>
</MenuItem>
<MenuItem value="frontloaded">
<Box>
<Typography variant="body2" fontWeight={600}>Front-loaded</Typography>
<Typography variant="caption" color="#666">More content at the beginning</Typography>
</Box>
</MenuItem>
<MenuItem value="backloaded">
<Box>
<Typography variant="body2" fontWeight={600}>Back-loaded</Typography>
<Typography variant="caption" color="#666">More content towards the end</Typography>
</Box>
</MenuItem>
</Select>
<FormHelperText sx={{ color: '#666' }}>How should content be distributed across the period?</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl fullWidth sx={ENHANCED_STYLES.formControl}>
<InputLabel>Review Cycle</InputLabel>
<Select
value={calendarConfig.reviewCycle}
onChange={(e) => handleReviewCycleChange(e.target.value as 'weekly' | 'monthly' | 'quarterly')}
label="Review Cycle"
>
<MenuItem value="weekly">
<Box>
<Typography variant="body2" fontWeight={600}>Weekly</Typography>
<Typography variant="caption" color="#666">Review and adjust every week</Typography>
</Box>
</MenuItem>
<MenuItem value="monthly">
<Box>
<Typography variant="body2" fontWeight={600}>Monthly</Typography>
<Typography variant="caption" color="#666">Review and adjust every month</Typography>
</Box>
</MenuItem>
<MenuItem value="quarterly">
<Box>
<Typography variant="body2" fontWeight={600}>Quarterly</Typography>
<Typography variant="caption" color="#666">Review and adjust every quarter</Typography>
</Box>
</MenuItem>
</Select>
<FormHelperText sx={{ color: '#666' }}>How often should you review and adjust your calendar?</FormHelperText>
</FormControl>
</Grid>
</Grid>
</CardContent>
</Card>
</Box>
);
};
export default CalendarConfigurationStep;

View File

@@ -0,0 +1,216 @@
import React from 'react';
import {
Box,
Typography,
Paper,
Grid,
Card,
CardContent,
Chip,
Divider
} from '@mui/material';
import {
CalendarToday as CalendarIcon,
Schedule as ScheduleIcon,
Settings as SettingsIcon,
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
// Import calendar-specific types
import { type CalendarConfig } from './types';
interface DataReviewStepProps {
calendarConfig: CalendarConfig;
userData: any;
strategyContext?: any;
onConfigUpdate: (updates: Partial<CalendarConfig>) => void;
}
const DataReviewStep: React.FC<DataReviewStepProps> = ({
calendarConfig,
userData,
strategyContext,
onConfigUpdate
}) => {
return (
<Box sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Calendar Setup Review
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Review your calendar configuration before proceeding to generation.
</Typography>
{/* Strategy Context Status */}
{strategyContext && (
<Paper sx={{ p: 3, mb: 3, bgcolor: 'success.light', color: 'success.contrastText' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<CheckCircleIcon />
<Typography variant="h6">Strategy Context Available</Typography>
</Box>
<Typography variant="body2" sx={{ mt: 1 }}>
Your activated strategy will be used internally during calendar generation for enhanced results.
</Typography>
</Paper>
)}
{/* Calendar Configuration Summary */}
<Grid container spacing={3}>
{/* Basic Setup */}
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CalendarIcon />
Basic Setup
</Typography>
<Divider sx={{ mb: 2 }} />
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Calendar Type:</Typography>
<Chip label={calendarConfig.calendarType} size="small" color="primary" />
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Duration:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.calendarDuration} {calendarConfig.calendarType === 'weekly' ? 'weeks' :
calendarConfig.calendarType === 'monthly' ? 'months' : 'quarters'}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Start Date:</Typography>
<Typography variant="body2" fontWeight="bold">
{new Date(calendarConfig.startDate).toLocaleDateString()}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Posts per Week:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.postingFrequency}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Content Volume:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.contentVolume} pieces per period
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Grid>
{/* Platform & Scheduling */}
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<ScheduleIcon />
Platform & Scheduling
</Typography>
<Divider sx={{ mb: 2 }} />
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Priority Platforms:</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{calendarConfig.priorityPlatforms.map((platform) => (
<Chip key={platform} label={platform} size="small" variant="outlined" />
))}
</Box>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Time Zone:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.timeZone}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Content Distribution:</Typography>
<Chip label={calendarConfig.contentDistribution} size="small" color="secondary" />
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Review Cycle:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.reviewCycle}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Grid>
{/* Generation Options */}
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SettingsIcon />
Generation Options
</Typography>
<Divider sx={{ mb: 2 }} />
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Include Weekends:</Typography>
<Chip
label={calendarConfig.includeWeekends ? 'Yes' : 'No'}
size="small"
color={calendarConfig.includeWeekends ? 'success' : 'default'}
/>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Auto-Schedule:</Typography>
<Chip
label={calendarConfig.autoSchedule ? 'Yes' : 'No'}
size="small"
color={calendarConfig.autoSchedule ? 'success' : 'default'}
/>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Generate Topics:</Typography>
<Chip
label={calendarConfig.generateTopics ? 'Yes' : 'No'}
size="small"
color={calendarConfig.generateTopics ? 'success' : 'default'}
/>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Summary */}
<Paper sx={{ p: 3, mt: 3, bgcolor: 'info.light', color: 'info.contrastText' }}>
<Typography variant="h6" gutterBottom>
Ready to Generate
</Typography>
<Typography variant="body2">
Your calendar will be generated with {calendarConfig.contentVolume} pieces of content over{' '}
{calendarConfig.calendarDuration} {calendarConfig.calendarType === 'weekly' ? 'weeks' :
calendarConfig.calendarType === 'monthly' ? 'months' : 'quarters'} with{' '}
{calendarConfig.postingFrequency} posts per week on {calendarConfig.priorityPlatforms.length} platforms.
</Typography>
</Paper>
</Box>
);
};
export default DataReviewStep;

View File

@@ -0,0 +1,471 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Button,
Card,
CardContent,
FormControlLabel,
Switch,
Alert,
Chip,
Grid,
Accordion,
AccordionSummary,
AccordionDetails,
LinearProgress
} from '@mui/material';
import {
PlayArrow as PlayIcon,
ExpandMore as ExpandMoreIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Info as InfoIcon
} from '@mui/icons-material';
// Import calendar-specific types
import { type CalendarConfig } from './types';
interface GenerateCalendarStepProps {
calendarConfig: CalendarConfig;
onGenerateCalendar: (config: CalendarConfig) => void;
loading?: boolean;
strategyContext?: any;
isFromStrategyActivation?: boolean; // Strategy context available for generation
}
const GenerateCalendarStep: React.FC<GenerateCalendarStepProps> = ({
calendarConfig,
onGenerateCalendar,
loading = false,
strategyContext,
isFromStrategyActivation = false
}) => {
const [validationErrors, setValidationErrors] = useState<string[]>([]);
const [showAdvanced, setShowAdvanced] = useState(false);
const [generationOptions, setGenerationOptions] = useState({
includeAIOptimization: true,
includeSmartScheduling: true,
includeTrendIntegration: true,
includeCompetitiveAnalysis: true,
includePerformanceTracking: true,
// Calendar-specific options moved from Step 1
includeWeekends: calendarConfig.includeWeekends || false,
autoSchedule: calendarConfig.autoSchedule || false,
generateTopics: calendarConfig.generateTopics || false
});
// Validate calendar configuration
useEffect(() => {
const errors: string[] = [];
// Validate essential calendar configuration
if (!calendarConfig.calendarType) {
errors.push('Calendar type is required');
}
if (!calendarConfig.startDate) {
errors.push('Start date is required');
}
if (calendarConfig.calendarDuration <= 0) {
errors.push('Calendar duration must be greater than 0');
}
if (calendarConfig.postingFrequency <= 0) {
errors.push('Posting frequency must be greater than 0');
}
if (!calendarConfig.priorityPlatforms || calendarConfig.priorityPlatforms.length === 0) {
errors.push('At least one platform must be selected');
}
if (!calendarConfig.timeZone) {
errors.push('Time zone is required');
}
setValidationErrors(errors);
}, [calendarConfig]);
const handleGenerate = () => {
if (validationErrors.length > 0) {
return; // Don't proceed if there are validation errors
}
// Enhanced calendar config with strategy context and generation options
const enhancedConfig = {
...calendarConfig,
// Include calendar-specific options from generation options
includeWeekends: generationOptions.includeWeekends,
autoSchedule: generationOptions.autoSchedule,
generateTopics: generationOptions.generateTopics,
strategyContext: isFromStrategyActivation ? {
strategyId: strategyContext?.strategyId,
strategyData: strategyContext?.strategyData,
available: true
} : undefined,
generationOptions,
metadata: {
generatedFrom: isFromStrategyActivation ? 'strategy_activation' : 'manual_config',
timestamp: new Date().toISOString(),
version: '3.0'
}
};
onGenerateCalendar(enhancedConfig);
};
const canGenerate = validationErrors.length === 0 && !loading;
return (
<Box sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Generate Your Content Calendar
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Review your configuration and generate your optimized content calendar.
</Typography>
{/* Strategy Context Status */}
{isFromStrategyActivation && (
<Alert severity="success" sx={{ mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<CheckCircleIcon />
<Box>
<Typography variant="subtitle2">Strategy Context Available</Typography>
<Typography variant="body2">
Your activated strategy will be used internally during generation for enhanced results.
</Typography>
</Box>
</Box>
</Alert>
)}
{/* Validation Errors */}
{validationErrors.length > 0 && (
<Alert severity="error" sx={{ mb: 3 }}>
<Typography variant="subtitle2" gutterBottom>
Please fix the following issues:
</Typography>
<ul style={{ margin: 0, paddingLeft: '20px' }}>
{validationErrors.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</Alert>
)}
{/* Configuration Summary */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Configuration Summary
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Calendar Type:</Typography>
<Chip label={calendarConfig.calendarType} size="small" color="primary" />
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Duration:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.calendarDuration} {calendarConfig.calendarType === 'weekly' ? 'weeks' :
calendarConfig.calendarType === 'monthly' ? 'months' : 'quarters'}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Posts per Week:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.postingFrequency}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Content Volume:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.contentVolume} pieces per period
</Typography>
</Box>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Platforms:</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{calendarConfig.priorityPlatforms.map((platform) => (
<Chip key={platform} label={platform} size="small" variant="outlined" />
))}
</Box>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Time Zone:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.timeZone}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Distribution:</Typography>
<Chip label={calendarConfig.contentDistribution} size="small" color="secondary" />
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2">Review Cycle:</Typography>
<Typography variant="body2" fontWeight="bold">
{calendarConfig.reviewCycle}
</Typography>
</Box>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
{/* Generation Options */}
<Accordion expanded={showAdvanced} onChange={() => setShowAdvanced(!showAdvanced)}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<InfoIcon />
Generation Options
</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.includeAIOptimization}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
includeAIOptimization: e.target.checked
}))}
/>
}
label="AI Content Optimization"
/>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.includeSmartScheduling}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
includeSmartScheduling: e.target.checked
}))}
/>
}
label="Smart Scheduling"
/>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.includeTrendIntegration}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
includeTrendIntegration: e.target.checked
}))}
/>
}
label="Trend Integration"
/>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.includeCompetitiveAnalysis}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
includeCompetitiveAnalysis: e.target.checked
}))}
/>
}
label="Competitive Analysis"
/>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.includePerformanceTracking}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
includePerformanceTracking: e.target.checked
}))}
/>
}
label="Performance Tracking"
/>
</Grid>
{/* Calendar-specific generation options moved from Step 1 */}
<Grid item xs={12}>
<Typography variant="subtitle2" sx={{ mb: 2, color: 'text.secondary', borderBottom: '1px solid #e0e0e0', pb: 1 }}>
Calendar-Specific Options
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.includeWeekends}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
includeWeekends: e.target.checked
}))}
/>
}
label={
<Box>
<Typography variant="body2" fontWeight={600}>Include Weekends</Typography>
<Typography variant="caption" color="text.secondary">
Schedule content on weekends for better engagement
</Typography>
</Box>
}
/>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.autoSchedule}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
autoSchedule: e.target.checked
}))}
/>
}
label={
<Box>
<Typography variant="body2" fontWeight={600}>Auto-Schedule Posts</Typography>
<Typography variant="caption" color="text.secondary">
Automatically assign optimal posting times
</Typography>
</Box>
}
/>
</Grid>
<Grid item xs={12} md={6}>
<FormControlLabel
control={
<Switch
checked={generationOptions.generateTopics}
onChange={(e) => setGenerationOptions(prev => ({
...prev,
generateTopics: e.target.checked
}))}
/>
}
label={
<Box>
<Typography variant="body2" fontWeight={600}>Generate Topics</Typography>
<Typography variant="caption" color="text.secondary">
AI-powered topic suggestions for each post
</Typography>
</Box>
}
/>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
{/* What You'll Get */}
<Card sx={{ mt: 3, mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
What You'll Get
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Box sx={{ textAlign: 'center', p: 2 }}>
<Typography variant="h4" color="primary" gutterBottom>
{calendarConfig.contentVolume}
</Typography>
<Typography variant="body2">
Content Pieces
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ textAlign: 'center', p: 2 }}>
<Typography variant="h4" color="primary" gutterBottom>
{calendarConfig.calendarDuration}
</Typography>
<Typography variant="body2">
{calendarConfig.calendarType === 'weekly' ? 'Weeks' :
calendarConfig.calendarType === 'monthly' ? 'Months' : 'Quarters'}
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ textAlign: 'center', p: 2 }}>
<Typography variant="h4" color="primary" gutterBottom>
{calendarConfig.priorityPlatforms.length}
</Typography>
<Typography variant="body2">
Platforms
</Typography>
</Box>
</Grid>
</Grid>
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
Your calendar will include optimized content scheduling, AI-powered topic suggestions,
and performance predictions based on your configuration.
</Typography>
</CardContent>
</Card>
{/* Generate Button */}
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
<Button
variant="contained"
size="large"
startIcon={<PlayIcon />}
onClick={handleGenerate}
disabled={!canGenerate}
sx={{ minWidth: 200 }}
>
{loading ? 'Generating...' : 'Generate Calendar'}
</Button>
</Box>
{/* Loading Progress */}
{loading && (
<Box sx={{ mt: 3 }}>
<LinearProgress />
<Typography variant="body2" color="text.secondary" sx={{ mt: 1, textAlign: 'center' }}>
Generating your optimized content calendar...
</Typography>
</Box>
)}
</Box>
);
};
export default GenerateCalendarStep;

View File

@@ -0,0 +1,217 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Button,
Alert,
AlertTitle,
Divider,
Chip
} from '@mui/material';
import {
Error as ErrorIcon,
Refresh as RefreshIcon,
BugReport as BugReportIcon,
Home as HomeIcon
} from '@mui/icons-material';
interface Props {
children: ReactNode;
onReset?: () => void;
onGoHome?: () => void;
stepName?: string;
}
interface State {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
errorId: string;
}
export class WizardErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorId: ''
};
}
static getDerivedStateFromError(error: Error): Partial<State> {
// Generate a unique error ID for tracking
const errorId = `wizard-error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
return {
hasError: true,
error,
errorId
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log error to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Wizard Error Boundary caught an error:', error, errorInfo);
}
this.setState({
errorInfo
});
// In a real application, you would send this to your error reporting service
// Example: Sentry.captureException(error, { extra: errorInfo });
}
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
errorId: ''
});
if (this.props.onReset) {
this.props.onReset();
}
};
handleGoHome = () => {
if (this.props.onGoHome) {
this.props.onGoHome();
}
};
render() {
if (this.state.hasError) {
const { error, errorInfo, errorId } = this.state;
const { stepName } = this.props;
return (
<Box sx={{ p: 3, maxWidth: 800, mx: 'auto' }}>
<Card sx={{
border: '2px solid #f44336',
boxShadow: '0 8px 32px rgba(244, 67, 54, 0.2)'
}}>
<CardContent sx={{ p: 4 }}>
{/* Error Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}>
<ErrorIcon color="error" sx={{ fontSize: 40 }} />
<Box>
<Typography variant="h5" color="error" gutterBottom>
Something went wrong
</Typography>
<Typography variant="body2" color="text.secondary">
{stepName ? `Error in ${stepName} step` : 'Error in Calendar Wizard'}
</Typography>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
{/* Error Details */}
<Alert severity="error" sx={{ mb: 3 }}>
<AlertTitle>Error Details</AlertTitle>
<Typography variant="body2" sx={{ mb: 1 }}>
{error?.message || 'An unexpected error occurred'}
</Typography>
<Chip
label={`Error ID: ${errorId}`}
size="small"
variant="outlined"
icon={<BugReportIcon />}
/>
</Alert>
{/* Recovery Options */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom>
What you can do:
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
<Typography component="li" variant="body2" sx={{ mb: 1 }}>
Try refreshing the wizard to start over
</Typography>
<Typography component="li" variant="body2" sx={{ mb: 1 }}>
Go back to the main dashboard
</Typography>
<Typography component="li" variant="body2" sx={{ mb: 1 }}>
Check your internet connection
</Typography>
<Typography component="li" variant="body2">
Contact support if the problem persists
</Typography>
</Box>
</Box>
{/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Button
variant="contained"
startIcon={<RefreshIcon />}
onClick={this.handleReset}
sx={{ minWidth: 140 }}
>
Try Again
</Button>
<Button
variant="outlined"
startIcon={<HomeIcon />}
onClick={this.handleGoHome}
sx={{ minWidth: 140 }}
>
Go to Dashboard
</Button>
</Box>
{/* Development Error Stack */}
{process.env.NODE_ENV === 'development' && errorInfo && (
<Box sx={{ mt: 4 }}>
<Typography variant="h6" gutterBottom>
Error Stack (Development Only)
</Typography>
<Box
component="pre"
sx={{
p: 2,
bgcolor: 'grey.100',
borderRadius: 1,
fontSize: '0.75rem',
overflow: 'auto',
maxHeight: 200
}}
>
{errorInfo.componentStack}
</Box>
</Box>
)}
</CardContent>
</Card>
</Box>
);
}
return this.props.children;
}
}
// Higher-order component for wrapping individual steps
export const withErrorBoundary = <P extends object>(
WrappedComponent: React.ComponentType<P>,
stepName?: string
) => {
return class WithErrorBoundary extends Component<P> {
render() {
return (
<WizardErrorBoundary stepName={stepName}>
<WrappedComponent {...this.props} />
</WizardErrorBoundary>
);
}
};
};

View File

@@ -0,0 +1,294 @@
import React from 'react';
import {
Box,
Card,
CardContent,
Typography,
LinearProgress,
CircularProgress,
Chip,
Alert
} from '@mui/material';
import {
HourglassEmpty as HourglassIcon,
TrendingUp as TrendingIcon,
CheckCircle as CheckIcon,
Error as ErrorIcon
} from '@mui/icons-material';
interface LoadingStep {
id: string;
label: string;
status: 'pending' | 'loading' | 'completed' | 'error';
progress?: number;
message?: string;
}
interface WizardLoadingStateProps {
title: string;
subtitle?: string;
steps?: LoadingStep[];
overallProgress?: number;
isGenerating?: boolean;
error?: string | null;
onRetry?: () => void;
}
const LoadingStepItem: React.FC<{ step: LoadingStep }> = ({ step }) => {
const getStatusIcon = () => {
switch (step.status) {
case 'completed':
return <CheckIcon color="success" fontSize="small" />;
case 'loading':
return <CircularProgress size={16} />;
case 'error':
return <ErrorIcon color="error" fontSize="small" />;
default:
return <HourglassIcon color="disabled" fontSize="small" />;
}
};
const getStatusColor = () => {
switch (step.status) {
case 'completed':
return 'success';
case 'loading':
return 'primary';
case 'error':
return 'error';
default:
return 'default';
}
};
return (
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
p: 2,
borderRadius: 1,
bgcolor: step.status === 'loading' ? 'action.hover' : 'transparent',
border: step.status === 'loading' ? '1px solid' : 'none',
borderColor: 'primary.main'
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{getStatusIcon()}
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" sx={{ fontWeight: 500 }}>
{step.label}
</Typography>
{step.message && (
<Typography variant="caption" color="text.secondary">
{step.message}
</Typography>
)}
</Box>
<Chip
label={step.status}
size="small"
color={getStatusColor() as any}
variant={step.status === 'pending' ? 'outlined' : 'filled'}
/>
{step.progress !== undefined && step.status === 'loading' && (
<Box sx={{ width: 100 }}>
<LinearProgress
variant="determinate"
value={step.progress}
/>
</Box>
)}
</Box>
);
};
export const WizardLoadingState: React.FC<WizardLoadingStateProps> = ({
title,
subtitle,
steps = [],
overallProgress,
isGenerating = false,
error,
onRetry
}) => {
const defaultSteps: LoadingStep[] = [
{
id: 'analyzing',
label: 'Analyzing your strategy data',
status: 'loading',
progress: 25,
message: 'Processing content pillars and target audience'
},
{
id: 'configuring',
label: 'Configuring calendar settings',
status: 'pending',
message: 'Setting up content mix and timing preferences'
},
{
id: 'generating',
label: 'Generating content calendar',
status: 'pending',
message: 'Creating optimized content schedule'
},
{
id: 'optimizing',
label: 'Optimizing for performance',
status: 'pending',
message: 'Applying AI-driven optimization'
}
];
const displaySteps = steps.length > 0 ? steps : defaultSteps;
return (
<Box sx={{ p: 3, maxWidth: 800, mx: 'auto' }}>
<Card sx={{
boxShadow: '0 8px 32px rgba(0,0,0,0.1)',
border: '1px solid',
borderColor: 'divider'
}}>
<CardContent sx={{ p: 4 }}>
{/* Header */}
<Box sx={{ textAlign: 'center', mb: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
{isGenerating ? (
<CircularProgress size={60} thickness={4} />
) : (
<TrendingIcon sx={{ fontSize: 60, color: 'primary.main' }} />
)}
</Box>
<Typography variant="h5" gutterBottom>
{title}
</Typography>
{subtitle && (
<Typography variant="body1" color="text.secondary">
{subtitle}
</Typography>
)}
</Box>
{/* Error Display */}
{error && (
<Alert
severity="error"
sx={{ mb: 3 }}
action={
onRetry && (
<Box component="button" onClick={onRetry} sx={{
border: 'none',
bgcolor: 'transparent',
color: 'inherit',
cursor: 'pointer',
textDecoration: 'underline'
}}>
Retry
</Box>
)
}
>
{error}
</Alert>
)}
{/* Overall Progress */}
{overallProgress !== undefined && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">
Overall Progress
</Typography>
<Typography variant="body2" color="text.secondary">
{Math.round(overallProgress)}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={overallProgress}
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
)}
{/* Loading Steps */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{displaySteps.map((step, index) => (
<LoadingStepItem key={step.id} step={step} />
))}
</Box>
{/* Additional Info */}
<Box sx={{ mt: 4, p: 2, bgcolor: 'grey.50', borderRadius: 1 }}>
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center' }}>
{isGenerating
? 'Please wait while we generate your content calendar. This may take a few moments.'
: 'We\'re preparing your content calendar with the latest AI-powered optimizations.'
}
</Typography>
</Box>
</CardContent>
</Card>
</Box>
);
};
// Specialized loading states for different wizard operations
export const CalendarGenerationLoading: React.FC<{ progress?: number; error?: string }> = ({
progress,
error
}) => (
<WizardLoadingState
title="Generating Your Content Calendar"
subtitle="AI is creating an optimized content schedule based on your strategy"
overallProgress={progress}
isGenerating={true}
error={error}
steps={[
{
id: 'validating',
label: 'Validating configuration',
status: progress && progress > 0 ? 'completed' : 'loading',
progress: progress && progress > 0 ? 100 : 50
},
{
id: 'processing',
label: 'Processing strategy data',
status: progress && progress > 20 ? 'completed' : progress && progress > 10 ? 'loading' : 'pending',
progress: progress && progress > 10 ? Math.min(100, (progress - 10) * 5) : 0
},
{
id: 'generating',
label: 'Generating content schedule',
status: progress && progress > 50 ? 'completed' : progress && progress > 30 ? 'loading' : 'pending',
progress: progress && progress > 30 ? Math.min(100, (progress - 30) * 5) : 0
},
{
id: 'optimizing',
label: 'Optimizing for performance',
status: progress && progress > 80 ? 'completed' : progress && progress > 60 ? 'loading' : 'pending',
progress: progress && progress > 60 ? Math.min(100, (progress - 60) * 5) : 0
}
]}
/>
);
export const DataProcessingLoading: React.FC<{ message?: string }> = ({ message }) => (
<WizardLoadingState
title="Processing Your Data"
subtitle="Analyzing your strategy and preparing calendar configuration"
steps={[
{
id: 'loading',
label: message || 'Loading and validating data',
status: 'loading',
progress: 75
}
]}
/>
);

View File

@@ -0,0 +1,289 @@
import { useState, useCallback, useMemo, useRef } from 'react';
import { CalendarConfig, WizardStep, ValidationError, WizardState, WizardActions } from '../types';
// All interfaces are now imported from types.ts
export const useCalendarWizardState = (
onGenerateCalendar: (calendarConfig: CalendarConfig) => void
): [WizardState, WizardActions] => {
// Store the callback in a ref to prevent it from causing re-renders
const onGenerateCalendarRef = useRef(onGenerateCalendar);
onGenerateCalendarRef.current = onGenerateCalendar;
const [activeStep, setActiveStep] = useState(0);
const [calendarConfig, setCalendarConfig] = useState<CalendarConfig>(() => createDefaultConfig());
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isGenerating, setIsGenerating] = useState(false);
const [generationProgress, setGenerationProgress] = useState(0);
// Define steps with validation - streamlined 2-step wizard
const steps: WizardStep[] = useMemo(() => [
{
label: 'Calendar Configuration',
icon: null, // Will be set by parent component
description: 'Configure your calendar settings and preferences',
isCompleted: false,
hasErrors: false
},
{
label: 'Generate Calendar',
icon: null,
description: 'Review and generate your optimized content calendar',
isCompleted: false,
hasErrors: false
}
], []);
// Validation rules for each step - streamlined 2-step validation
const validationRules = useMemo(() => ({
0: (config: CalendarConfig): ValidationError[] => {
const errors: ValidationError[] = [];
// Basic calendar setup validation
if (!config.calendarType) {
errors.push({ field: 'calendarType', message: 'Calendar type is required', step: 0 });
}
if (!config.startDate) {
errors.push({ field: 'startDate', message: 'Start date is required', step: 0 });
}
if (config.calendarDuration <= 0) {
errors.push({ field: 'calendarDuration', message: 'Calendar duration must be greater than 0', step: 0 });
}
if (config.postingFrequency <= 0) {
errors.push({ field: 'postingFrequency', message: 'Posting frequency must be greater than 0', step: 0 });
}
if (config.contentVolume <= 0) {
errors.push({ field: 'contentVolume', message: 'Content volume must be greater than 0', step: 0 });
}
// Platform and scheduling validation
if (config.priorityPlatforms.length === 0) {
errors.push({ field: 'priorityPlatforms', message: 'At least one platform is required', step: 0 });
}
if (!config.timeZone) {
errors.push({ field: 'timeZone', message: 'Time zone is required', step: 0 });
}
if (!config.contentDistribution) {
errors.push({ field: 'contentDistribution', message: 'Content distribution is required', step: 0 });
}
if (!config.reviewCycle) {
errors.push({ field: 'reviewCycle', message: 'Review cycle is required', step: 0 });
}
return errors;
},
1: (config: CalendarConfig): ValidationError[] => {
// Step 1 is the generation step, no additional validation needed
return [];
}
}), []);
// Update calendar configuration
const updateCalendarConfig = useCallback((updates: Partial<CalendarConfig>) => {
setCalendarConfig(prev => ({ ...prev, ...updates }));
// Clear validation errors for updated fields
setValidationErrors(prev => prev.filter(error =>
!Object.keys(updates).some(key => error.field.startsWith(key))
));
}, []);
// Validate a specific step
const validateStep = useCallback((step: number): boolean => {
const validator = validationRules[step as keyof typeof validationRules];
if (!validator) return true;
const errors = validator(calendarConfig);
setValidationErrors(prev => {
const filtered = prev.filter(error => error.step !== step);
return [...filtered, ...errors];
});
return errors.length === 0;
}, [calendarConfig, validationRules]);
// Validate all steps
const validateAllSteps = useCallback((): boolean => {
const allErrors: ValidationError[] = [];
Object.keys(validationRules).forEach(stepKey => {
const step = parseInt(stepKey);
const validator = validationRules[step as keyof typeof validationRules];
if (validator) {
allErrors.push(...validator(calendarConfig));
}
});
setValidationErrors(allErrors);
return allErrors.length === 0;
}, [calendarConfig, validationRules]);
// Clear all errors
const clearErrors = useCallback(() => {
setValidationErrors([]);
setError(null);
}, []);
// Check if user can proceed to a specific step
const canProceedToStep = useCallback((step: number): boolean => {
// Can always go back
if (step < activeStep) return true;
// Can't skip steps
if (step > activeStep + 1) return false;
// Validate current step before proceeding
if (step === activeStep + 1) {
// Inline validation to avoid circular dependency
const validator = validationRules[activeStep as keyof typeof validationRules];
if (!validator) return true;
const errors = validator(calendarConfig);
return errors.length === 0;
}
return true;
}, [activeStep, calendarConfig, validationRules]);
// Get step status
const getStepStatus = useCallback((step: number): 'completed' | 'current' | 'pending' | 'error' => {
if (step === activeStep) return 'current';
if (step < activeStep) return 'completed';
if (validationErrors.some(error => error.step === step)) return 'error';
return 'pending';
}, [activeStep, validationErrors]);
// Reset wizard to initial state
const resetWizard = useCallback(() => {
setActiveStep(0);
setCalendarConfig(createDefaultConfig());
setValidationErrors([]);
setIsLoading(false);
setError(null);
setIsGenerating(false);
setGenerationProgress(0);
}, []); // Remove initialConfig from dependencies to prevent infinite loop
// Enhanced step navigation with validation
const setActiveStepWithValidation = useCallback((step: number) => {
if (canProceedToStep(step)) {
setActiveStep(step);
clearErrors();
}
}, [canProceedToStep, clearErrors]);
// Generate calendar with progress tracking
const generateCalendar = useCallback(async () => {
if (!validateAllSteps()) {
setError('Please fix validation errors before generating calendar');
return;
}
setIsGenerating(true);
setGenerationProgress(0);
setError(null);
try {
// Simulate progress updates
const progressInterval = setInterval(() => {
setGenerationProgress(prev => {
if (prev >= 90) {
clearInterval(progressInterval);
return 90;
}
return prev + 10;
});
}, 200);
await onGenerateCalendarRef.current(calendarConfig);
clearInterval(progressInterval);
setGenerationProgress(100);
// Reset after successful generation
setTimeout(() => {
setIsGenerating(false);
setGenerationProgress(0);
}, 1000);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to generate calendar');
setIsGenerating(false);
setGenerationProgress(0);
}
}, [calendarConfig, validateAllSteps]); // Remove onGenerateCalendar dependency
const state: WizardState = useMemo(() => ({
activeStep,
calendarConfig,
steps,
validationErrors,
isLoading,
error,
isGenerating,
generationProgress
}), [
activeStep,
calendarConfig,
steps,
validationErrors,
isLoading,
error,
isGenerating,
generationProgress
]);
const actions: WizardActions = useMemo(() => ({
setActiveStep: setActiveStepWithValidation,
updateCalendarConfig,
validateStep,
validateAllSteps,
clearErrors,
setError,
setLoading: setIsLoading,
setGenerating: setIsGenerating,
setGenerationProgress,
resetWizard,
canProceedToStep,
getStepStatus
}), [
setActiveStepWithValidation,
updateCalendarConfig,
validateStep,
validateAllSteps,
clearErrors,
setError,
setIsLoading,
setIsGenerating,
setGenerationProgress,
resetWizard,
canProceedToStep,
getStepStatus
]);
return [state, actions];
};
// Helper function to create default calendar config
const createDefaultConfig = (): CalendarConfig => {
return {
// Calendar Structure
calendarType: 'monthly',
calendarDuration: 4, // 4 weeks/months
startDate: new Date().toISOString().split('T')[0], // Today's date
// Posting Configuration
postingFrequency: 3, // 3 posts per week
contentVolume: 12, // 12 pieces per period
// Platform Scheduling
priorityPlatforms: ['LinkedIn', 'Twitter'],
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, // User's timezone
// Calendar Preferences
excludeDates: [],
contentDistribution: 'even',
reviewCycle: 'weekly',
// Generation Options
includeWeekends: false,
autoSchedule: true,
generateTopics: true
};
};

View File

@@ -0,0 +1,18 @@
export { default as DataReviewStep } from './DataReviewStep';
export { default as CalendarConfigurationStep } from './CalendarConfigurationStep';
export { default as AdvancedOptionsStep } from './AdvancedOptionsStep';
export { default as GenerateCalendarStep } from './GenerateCalendarStep';
// State management and utilities
export { useCalendarWizardState } from './hooks/useCalendarWizardState';
export type { CalendarConfig, WizardState, WizardActions, ValidationError } from './types';
// Error handling
export { WizardErrorBoundary, withErrorBoundary } from './components/WizardErrorBoundary';
// Loading states
export {
WizardLoadingState,
CalendarGenerationLoading,
DataProcessingLoading
} from './components/WizardLoadingState';

View File

@@ -0,0 +1,69 @@
/**
* Calendar Wizard Types
* Phase 3A: Simplified Calendar-Specific Configuration
*/
export interface CalendarConfig {
// Calendar Structure
calendarType: 'weekly' | 'monthly' | 'quarterly';
calendarDuration: number; // Number of periods to generate
startDate: string; // ISO date string
// Posting Configuration
postingFrequency: number; // Posts per week
contentVolume: number; // Total pieces per period
// Platform Scheduling
priorityPlatforms: string[]; // ["LinkedIn", "Twitter", "Blog"]
timeZone: string; // User's timezone
// Calendar Preferences
excludeDates: string[]; // Holiday/blackout dates
contentDistribution: 'even' | 'frontloaded' | 'backloaded';
reviewCycle: 'weekly' | 'monthly' | 'quarterly';
// Generation Options
includeWeekends: boolean;
autoSchedule: boolean;
generateTopics: boolean;
}
export interface WizardStep {
label: string;
icon: React.ReactNode;
description: string;
isCompleted: boolean;
hasErrors: boolean;
}
export interface ValidationError {
field: string;
message: string;
step: number;
}
export interface WizardState {
activeStep: number;
calendarConfig: CalendarConfig;
steps: WizardStep[];
validationErrors: ValidationError[];
isLoading: boolean;
error: string | null;
isGenerating: boolean;
generationProgress: number;
}
export interface WizardActions {
setActiveStep: (step: number) => void;
updateCalendarConfig: (updates: Partial<CalendarConfig>) => void;
validateStep: (step: number) => boolean;
validateAllSteps: () => boolean;
clearErrors: () => void;
setError: (error: string | null) => void;
setLoading: (loading: boolean) => void;
setGenerating: (generating: boolean) => void;
setGenerationProgress: (progress: number) => void;
resetWizard: () => void;
canProceedToStep: (step: number) => boolean;
getStepStatus: (step: number) => 'completed' | 'current' | 'pending' | 'error';
}

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
@@ -55,7 +55,8 @@ import {
Schedule as ScheduleIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import { useEnhancedStrategyStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/enhancedStrategyStore';
import { useStrategyBuilderStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/strategyBuilderStore';
import { useEnhancedStrategyStore } from '../../../stores/enhancedStrategyStore';
import StrategicInputField from './ContentStrategyBuilder/StrategicInputField';
import EnhancedTooltip from './ContentStrategyBuilder/EnhancedTooltip';
import AIRecommendationsPanel from './AIRecommendationsPanel';
@@ -90,53 +91,58 @@ import CategoryDetailView from './ContentStrategyBuilder/components/CategoryDeta
const ContentStrategyBuilder: React.FC = () => {
const navigate = useNavigate();
// Strategy Builder Store (for form data, validation, auto-population)
const {
formData,
formErrors,
autoPopulatedFields,
dataSources,
inputDataPoints, // Add inputDataPoints from store
inputDataPoints,
personalizationData,
loading,
error,
saving,
aiGenerating,
currentStep,
completedSteps,
disclosureSteps,
currentStrategy,
updateFormField,
// Transparency state
transparencyModalOpen,
generationProgress: storeGenerationProgress,
currentPhase,
educationalContent: storeEducationalContent,
transparencyMessages,
isGenerating,
setTransparencyModalOpen,
setGenerationProgress: setStoreGenerationProgress,
setCurrentPhase,
setEducationalContent: setStoreEducationalContent,
addTransparencyMessage,
clearTransparencyMessages,
setIsGenerating,
validateFormField,
validateAllFields,
completeStep,
getNextStep,
getPreviousStep,
setCurrentStep,
canProceedToStep,
resetForm,
autoPopulateFromOnboarding,
generateAIRecommendations,
createEnhancedStrategy,
createStrategy: createEnhancedStrategy,
calculateCompletionPercentage,
getCompletionStats,
setError,
setCurrentStrategy,
setAIGenerating,
setSaving,
personalizationData
setSaving
} = useStrategyBuilderStore();
// Enhanced Strategy Store (for AI analysis, progressive disclosure, transparency)
const {
aiGenerating,
currentStep,
completedSteps,
disclosureSteps,
transparencyModalOpen,
transparencyGenerationProgress: storeGenerationProgress,
currentPhase,
educationalContent: storeEducationalContent,
transparencyMessages,
transparencyGenerating: isGenerating,
setTransparencyModalOpen,
setTransparencyGenerationProgress: setStoreGenerationProgress,
setCurrentPhase,
setEducationalContent: setStoreEducationalContent,
addTransparencyMessage,
clearTransparencyMessages,
setTransparencyGenerating: setIsGenerating,
completeStep,
getNextStep,
getPreviousStep,
setCurrentStep,
canProceedToDisclosureStep: canProceedToStep,
generateAIRecommendations,
setAIGenerating
} = useEnhancedStrategyStore();
const [showAIRecommendations, setShowAIRecommendations] = useState(false);
@@ -237,8 +243,8 @@ const ContentStrategyBuilder: React.FC = () => {
setError
});
const completionStats = getCompletionStats();
const completionPercentage = calculateCompletionPercentage();
const completionStats = useMemo(() => getCompletionStats(), [formData]);
const completionPercentage = useMemo(() => calculateCompletionPercentage(), [formData]);
// Use extracted hooks
const {
@@ -314,7 +320,7 @@ const ContentStrategyBuilder: React.FC = () => {
if (!autoPopulateAttempted) {
autoPopulateFromOnboarding();
}
}, [autoPopulateAttempted, autoPopulateFromOnboarding]);
}, [autoPopulateAttempted]); // Removed autoPopulateFromOnboarding from dependencies
// Set default category selection
useEffect(() => {
@@ -362,15 +368,7 @@ const ContentStrategyBuilder: React.FC = () => {
}
}, [showEnterpriseModal, aiGenerating]);
// Monitor store data changes for debugging
useEffect(() => {
console.log('🎯 Store data changed:', {
autoPopulatedFieldsCount: Object.keys(autoPopulatedFields || {}).length,
dataSourcesCount: Object.keys(dataSources || {}).length,
inputDataPointsCount: Object.keys(inputDataPoints || {}).length,
transparencyMessagesCount: transparencyMessages?.length || 0
});
}, [autoPopulatedFields, dataSources, inputDataPoints, transparencyMessages]);
// Note: Removed store monitoring useEffect to prevent infinite re-renders
// Add CSS keyframes for pulse animation
useEffect(() => {

View File

@@ -27,7 +27,7 @@ import {
DataUsage as DataUsageIcon,
Close as CloseIcon
} from '@mui/icons-material';
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
import { useStrategyBuilderStore } from '../../../../stores/strategyBuilderStore';
interface EnhancedTooltipProps {
fieldId: string;
@@ -40,7 +40,19 @@ const EnhancedTooltip: React.FC<EnhancedTooltipProps> = ({
open,
onClose
}) => {
const { getTooltipData, autoPopulatedFields, dataSources } = useEnhancedStrategyStore();
const { autoPopulatedFields, dataSources, confidenceScores } = useStrategyBuilderStore();
// Since getTooltipData is not in strategyBuilderStore, we'll create a simple implementation
const getTooltipData = (fieldId: string) => {
// This is a simplified tooltip data implementation
// In a real scenario, you might want to move this to the strategyBuilderStore
return {
title: `About ${fieldId.replace(/_/g, ' ')}`,
description: `Information about ${fieldId.replace(/_/g, ' ')}`,
tips: [`Tip for ${fieldId}`],
confidence_level: confidenceScores?.[fieldId] || 0.8
};
};
const tooltipData = getTooltipData(fieldId);
const isAutoPopulated = !!(autoPopulatedFields && autoPopulatedFields[fieldId]);

View File

@@ -29,7 +29,7 @@ import {
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon
} from '@mui/icons-material';
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
import { useStrategyBuilderStore } from '../../../../stores/strategyBuilderStore';
interface StrategicInputFieldProps {
fieldId: string;
@@ -108,7 +108,15 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
accentColorKey = 'primary',
isCompact = false
}) => {
const { getTooltipData } = useEnhancedStrategyStore();
// Since getTooltipData is not in strategyBuilderStore, we'll create a simple implementation
const getTooltipData = (fieldId: string) => {
// This is a simplified tooltip data implementation
return {
title: `About ${fieldId.replace(/_/g, ' ')}`,
description: `Information about ${fieldId.replace(/_/g, ' ')}`,
tips: [`Tip for ${fieldId}`]
};
};
const [isEditing, setIsEditing] = useState(false);
const [showPersonalization, setShowPersonalization] = useState(false);

View File

@@ -27,7 +27,7 @@ import {
import { motion, AnimatePresence } from 'framer-motion';
import StrategicInputField from '../StrategicInputField';
import { CategoryDetailViewProps, EducationalInfoDialogProps } from '../types/contentStrategy.types';
import { useEnhancedStrategyStore } from '../../../../../stores/enhancedStrategyStore';
import { useStrategyBuilderStore } from '../../../../../stores/strategyBuilderStore';
const EducationalInfoDialog: React.FC<EducationalInfoDialogProps> = ({
open,
@@ -121,7 +121,7 @@ const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
const [expandedCard, setExpandedCard] = useState<string | null>(null);
// Get confidence scores from store
const { confidenceScores } = useEnhancedStrategyStore();
const { confidenceScores } = useStrategyBuilderStore();
if (!activeCategory) {
return (

View File

@@ -34,8 +34,7 @@ const EducationalModal: React.FC<EducationalModalProps> = ({
generationProgress,
onReviewStrategy
}) => {
// Debug: Log progress and button state
console.log('🎯 EducationalModal - Progress:', generationProgress, 'Show Next Button:', generationProgress >= 100);
// Note: Removed debug logging to prevent infinite re-renders
return (
<Dialog
open={open}

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
import { useEnhancedStrategyStore } from '../../../../../stores/enhancedStrategyStore';
import { useStrategyBuilderStore } from '../../../../../stores/strategyBuilderStore';
interface UseAIRefreshProps {
setTransparencyModalOpen: (open: boolean) => void;
@@ -235,7 +235,7 @@ export const useAIRefresh = ({
console.log('🎯 Field values details:', fieldValues);
// Update the store with the new data
useEnhancedStrategyStore.setState((state) => {
useStrategyBuilderStore.setState((state) => {
const newState = {
autoPopulatedFields: { ...state.autoPopulatedFields, ...fieldValues },
dataSources: { ...state.dataSources, ...sources },

View File

@@ -10,14 +10,17 @@ export const useAutoPopulation = ({
completionStats
}: UseAutoPopulationProps) => {
const [autoPopulateAttempted, setAutoPopulateAttempted] = useState(false);
const [isAutoPopulating, setIsAutoPopulating] = useState(false);
// Auto-populate from onboarding on first load
useEffect(() => {
if (!autoPopulateAttempted) {
if (!autoPopulateAttempted && !isAutoPopulating) {
setIsAutoPopulating(true);
autoPopulateFromOnboarding();
setAutoPopulateAttempted(true);
setIsAutoPopulating(false);
}
}, [autoPopulateAttempted, autoPopulateFromOnboarding]);
}, [autoPopulateAttempted, isAutoPopulating]); // Removed autoPopulateFromOnboarding from dependencies
return {
autoPopulateAttempted,

View File

@@ -0,0 +1,526 @@
import React, { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Box,
Alert,
AlertTitle,
CircularProgress,
LinearProgress,
Card,
CardContent,
Grid,
List,
ListItem,
ListItemIcon,
ListItemText,
Chip
} from '@mui/material';
import {
AutoAwesome as AutoAwesomeIcon,
Assessment as AssessmentIcon,
PlayArrow as PlayArrowIcon,
SmartToy as SmartToyIcon,
Person as PersonIcon,
TrendingUp as TrendingUpIcon,
ThumbUp as ThumbUpIcon,
MonetizationOn as MonetizationOnIcon,
CheckCircle as CheckCircleIcon,
Star as StarIcon,
EmojiEvents as EmojiEventsIcon,
People as PeopleIcon
} from '@mui/icons-material';
import { strategyMonitoringApi, MonitoringPlan } from '../../../services/strategyMonitoringApi';
interface StrategyActivationModalProps {
open: boolean;
onClose: () => void;
strategyId: number;
strategyData: any;
onSetupMonitoring: (monitoringPlan: any) => Promise<void>;
}
interface MonitoringTask {
title: string;
description: string;
assignee: 'ALwrity' | 'Human';
frequency: string;
metric: string;
measurementMethod: string;
successCriteria: string;
alertThreshold: string;
actionableInsights?: string;
}
interface MonitoringComponent {
name: string;
icon: string;
tasks: MonitoringTask[];
}
const StrategyActivationModal: React.FC<StrategyActivationModalProps> = ({
open,
onClose,
strategyId,
strategyData,
onSetupMonitoring
}) => {
const [isGenerating, setIsGenerating] = useState(false);
const [monitoringPlan, setMonitoringPlan] = useState<MonitoringPlan | null>(null);
const [showMonitoringPlan, setShowMonitoringPlan] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSetupMonitoring = async () => {
setIsGenerating(true);
setError(null);
try {
// Call the API to generate monitoring plan
const response = await strategyMonitoringApi.generateMonitoringPlan(strategyId);
setMonitoringPlan(response.data);
setShowMonitoringPlan(true);
} catch (err: any) {
setError(err.message || 'Failed to generate monitoring plan');
console.error('Error generating monitoring plan:', err);
} finally {
setIsGenerating(false);
}
};
const handleActivateStrategy = async () => {
if (!monitoringPlan) return;
try {
await onSetupMonitoring(monitoringPlan);
onClose();
} catch (error) {
console.error('Error activating strategy:', error);
}
};
const getComponentIcon = (iconName: string) => {
const iconMap: { [key: string]: React.ReactElement } = {
'TrendingUpIcon': <TrendingUpIcon />,
'ThumbUpIcon': <ThumbUpIcon />,
'MonetizationOnIcon': <MonetizationOnIcon />,
'CheckCircleIcon': <CheckCircleIcon />,
'StarIcon': <StarIcon />,
'EmojiEventsIcon': <EmojiEventsIcon />,
'PeopleIcon': <PeopleIcon />,
'AssessmentIcon': <AssessmentIcon />
};
return iconMap[iconName] || <AssessmentIcon />;
};
return (
<Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth>
<DialogTitle>
<Box display="flex" alignItems="center" gap={2}>
<AutoAwesomeIcon color="primary" />
Activate Content Strategy & Setup Monitoring
</Box>
</DialogTitle>
<DialogContent>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{!showMonitoringPlan ? (
// Initial Setup View
<Box>
<Typography variant="h6" gutterBottom>
ALwrity will create Quality & Performance Metrics
</Typography>
<Typography variant="body1" paragraph>
Your content strategy will be continuously monitored and optimized based on comprehensive metrics and AI-powered analysis.
</Typography>
<Alert severity="info" sx={{ mb: 3 }}>
<AlertTitle>What happens next?</AlertTitle>
ALwrity AI will analyze your strategy components and create a customized monitoring plan with specific tasks, metrics, and schedules to keep your strategy performing optimally.
</Alert>
{isGenerating && (
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom color="primary">
🎯 Creating Your Custom Monitoring Plan
</Typography>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary" gutterBottom>
Step 1: Analyzing Strategy Components
</Typography>
<LinearProgress variant="indeterminate" sx={{ mb: 1 }} />
<Typography variant="caption" color="text.secondary">
Reviewing your content pillars, target audience, and business goals
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary" gutterBottom>
Step 2: Generating Monitoring Tasks
</Typography>
<LinearProgress variant="indeterminate" sx={{ mb: 1 }} />
<Typography variant="caption" color="text.secondary">
Creating automated ALwrity tasks and manual human review tasks
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary" gutterBottom>
Step 3: Setting Up Metrics & Alerts
</Typography>
<LinearProgress variant="indeterminate" sx={{ mb: 1 }} />
<Typography variant="caption" color="text.secondary">
Configuring success criteria, alert thresholds, and measurement methods
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary" gutterBottom>
Step 4: Finalizing Your Plan
</Typography>
<LinearProgress variant="indeterminate" sx={{ mb: 1 }} />
<Typography variant="caption" color="text.secondary">
Validating plan structure and preparing for activation
</Typography>
</Box>
<Alert severity="info" sx={{ mt: 2 }}>
<AlertTitle>What You'll Get</AlertTitle>
<Typography variant="body2" component="div">
• <strong>Automated Tasks:</strong> ALwrity will handle daily monitoring, analytics, and alerting
<br />
• <strong>Manual Tasks:</strong> You'll review strategic decisions and creative direction
<br />
<strong>Performance Metrics:</strong> Track traffic growth, engagement, conversions, and ROI
<br />
<strong>Smart Alerts:</strong> Get notified when metrics need attention
<br />
<strong>Actionable Insights:</strong> Clear recommendations for optimization
</Typography>
</Alert>
</Box>
)}
<Button
variant="contained"
size="large"
onClick={handleSetupMonitoring}
disabled={isGenerating || !!error}
startIcon={isGenerating ? <CircularProgress size={20} /> : <AssessmentIcon />}
fullWidth
sx={{ mt: 2 }}
>
{isGenerating ? 'Creating Your Monitoring Plan...' : 'Setup Audit & Adaptive Monitoring'}
</Button>
{error && (
<Button
variant="outlined"
size="large"
onClick={() => {
setError(null);
handleSetupMonitoring();
}}
disabled={isGenerating}
fullWidth
sx={{ mt: 1 }}
>
Retry Setup
</Button>
)}
</Box>
) : (
// Monitoring Plan Display View
<Box>
<Typography variant="h6" gutterBottom>
Generated Monitoring Plan
</Typography>
{monitoringPlan && (
<MonitoringPlanDisplay
plan={monitoringPlan}
strategyData={strategyData}
getComponentIcon={getComponentIcon}
/>
)}
<Alert severity="success" sx={{ mt: 2 }}>
<AlertTitle>Monitoring Plan Ready!</AlertTitle>
Your strategy will now be continuously monitored with AI-powered analysis and adaptive recommendations.
</Alert>
<Button
variant="contained"
size="large"
onClick={handleActivateStrategy}
startIcon={<PlayArrowIcon />}
fullWidth
sx={{ mt: 2 }}
>
Activate Strategy & Start Monitoring
</Button>
</Box>
)}
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
{showMonitoringPlan ? 'Cancel' : 'Close'}
</Button>
</DialogActions>
</Dialog>
);
};
// Monitoring Plan Display Component
interface MonitoringPlanDisplayProps {
plan: MonitoringPlan;
strategyData: any;
getComponentIcon: (iconName: string) => React.ReactElement;
}
const MonitoringPlanDisplay: React.FC<MonitoringPlanDisplayProps> = ({
plan,
strategyData,
getComponentIcon
}) => {
// Helper function to get icon name from component name
const getComponentIconName = (componentName: string): string => {
const iconMap: Record<string, string> = {
'Strategic Insights': 'TrendingUpIcon',
'Competitive Analysis': 'EmojiEventsIcon',
'Performance Predictions': 'AssessmentIcon',
'Implementation Roadmap': 'CheckCircleIcon',
'Risk Assessment': 'StarIcon'
};
return iconMap[componentName] || 'TrendingUpIcon';
};
return (
<Box>
{/* Enhanced Summary Section */}
<Card sx={{
mb: 3,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3)'
}}>
<CardContent>
<Typography variant="h5" gutterBottom sx={{ fontWeight: 700 }}>
🎯 AI-Powered Monitoring Plan
</Typography>
<Typography variant="body2" sx={{ mb: 3, opacity: 0.9 }}>
Your content strategy will be continuously monitored with comprehensive metrics and actionable insights
</Typography>
<Grid container spacing={3}>
<Grid item xs={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1 }}>
{plan.totalTasks}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>Total Tasks</Typography>
</Box>
</Grid>
<Grid item xs={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1, color: '#4caf50' }}>
{plan.alwrityTasks}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>AI Automated</Typography>
</Box>
</Grid>
<Grid item xs={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1, color: '#ff9800' }}>
{plan.humanTasks}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>Human Tasks</Typography>
</Box>
</Grid>
<Grid item xs={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1, color: '#2196f3' }}>
{plan.metricsCount}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>Metrics Tracked</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
{/* Enhanced Component-wise Monitoring Tasks */}
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, mb: 3 }}>
📊 Monitoring Tasks by Strategy Component
</Typography>
{/* Group tasks by component */}
{(() => {
const tasksByComponent = plan.monitoringTasks.reduce((acc: Record<string, typeof plan.monitoringTasks>, task) => {
if (!acc[task.component]) {
acc[task.component] = [];
}
acc[task.component].push(task);
return acc;
}, {});
return Object.entries(tasksByComponent).map(([componentName, tasks], index) => (
<Card key={index} sx={{
mb: 3,
border: '1px solid rgba(102, 126, 234, 0.2)',
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
'&:hover': {
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.2)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s ease'
}}>
<CardContent sx={{ p: 3 }}>
{/* Component Header */}
<Box display="flex" alignItems="center" gap={2} sx={{ mb: 3 }}>
<Box sx={{
p: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white'
}}>
{getComponentIcon(getComponentIconName(componentName))}
</Box>
<Box>
<Typography variant="h6" sx={{ fontWeight: 600 }}>
{componentName}
</Typography>
<Typography variant="body2" color="text.secondary">
{tasks.length} comprehensive monitoring tasks
</Typography>
</Box>
</Box>
{/* Enhanced Task List */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{(tasks as typeof plan.monitoringTasks).map((task, taskIndex) => (
<Card key={taskIndex} sx={{
border: '1px solid rgba(0,0,0,0.1)',
background: task.assignee === 'ALwrity' ? 'rgba(76, 175, 80, 0.05)' : 'rgba(255, 152, 0, 0.05)'
}}>
<CardContent sx={{ p: 2.5 }}>
{/* Task Header */}
<Box display="flex" alignItems="center" gap={2} sx={{ mb: 2 }}>
<Box sx={{
p: 1,
borderRadius: 1,
background: task.assignee === 'ALwrity' ? 'rgba(76, 175, 80, 0.2)' : 'rgba(255, 152, 0, 0.2)',
color: task.assignee === 'ALwrity' ? '#4caf50' : '#ff9800'
}}>
{task.assignee === 'ALwrity' ?
<SmartToyIcon fontSize="small" /> :
<PersonIcon fontSize="small" />
}
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 0.5 }}>
{task.title}
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip
label={task.assignee}
size="small"
color={task.assignee === 'ALwrity' ? 'success' : 'warning'}
sx={{ fontWeight: 600 }}
/>
<Chip
label={task.frequency}
size="small"
variant="outlined"
sx={{ fontWeight: 500 }}
/>
</Box>
</Box>
</Box>
{/* Task Description */}
<Typography variant="body2" color="text.secondary" sx={{ mb: 2, lineHeight: 1.6 }}>
{task.description}
</Typography>
{/* Task Details Grid */}
<Grid container spacing={2} sx={{ mb: 2 }}>
<Grid item xs={12} md={6}>
<Box sx={{ p: 2, background: 'rgba(33, 150, 243, 0.05)', borderRadius: 1 }}>
<Typography variant="caption" color="primary" sx={{ fontWeight: 600, textTransform: 'uppercase' }}>
Metric to Track
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, fontWeight: 500 }}>
{task.metric}
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ p: 2, background: 'rgba(156, 39, 176, 0.05)', borderRadius: 1 }}>
<Typography variant="caption" color="secondary" sx={{ fontWeight: 600, textTransform: 'uppercase' }}>
Success Criteria
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, fontWeight: 500 }}>
{task.successCriteria}
</Typography>
</Box>
</Grid>
</Grid>
{/* Measurement Method */}
<Box sx={{ p: 2, background: 'rgba(255, 193, 7, 0.05)', borderRadius: 1, mb: 2 }}>
<Typography variant="caption" color="warning.main" sx={{ fontWeight: 600, textTransform: 'uppercase' }}>
📏 Measurement Method
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, lineHeight: 1.6 }}>
{task.measurementMethod}
</Typography>
</Box>
{/* Alert Threshold and Actionable Insights */}
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Box sx={{ p: 2, background: 'rgba(244, 67, 54, 0.05)', borderRadius: 1 }}>
<Typography variant="caption" color="error" sx={{ fontWeight: 600, textTransform: 'uppercase' }}>
🚨 Alert Threshold
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, fontWeight: 500 }}>
{task.alertThreshold}
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ p: 2, background: 'rgba(76, 175, 80, 0.05)', borderRadius: 1 }}>
<Typography variant="caption" color="success.main" sx={{ fontWeight: 600, textTransform: 'uppercase' }}>
💡 Actionable Insights
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, fontWeight: 500 }}>
{task.actionableInsights || "Review data and adjust strategy based on performance trends"}
</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
))}
</Box>
</CardContent>
</Card>
));
})()}
</Box>
);
};
export default StrategyActivationModal;

View File

@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import React, { useEffect } from 'react';
import { Box, CircularProgress, Alert, Typography } from '@mui/material';
import { useStrategyData } from './hooks/useStrategyData';
import StrategyHeader from './components/StrategyHeader';
import StrategicInsightsCard from './components/StrategicInsightsCard';
import CompetitiveAnalysisCard from './components/CompetitiveAnalysisCard';
@@ -8,15 +7,60 @@ import PerformancePredictionsCard from './components/PerformancePredictionsCard'
import ImplementationRoadmapCard from './components/ImplementationRoadmapCard';
import RiskAssessmentCard from './components/RiskAssessmentCard';
import ReviewProgressHeader from './components/ReviewProgressHeader';
import { StrategyData } from './types/strategy.types';
import { useStrategyReviewStore } from '../../../../stores/strategyReviewStore';
const StrategyIntelligenceTab: React.FC = () => {
const { strategyData, loading, error } = useStrategyData();
// State to control review progress visibility
const [showReviewProgress, setShowReviewProgress] = useState(false);
interface StrategyIntelligenceTabProps {
strategyData?: StrategyData | null;
loading?: boolean;
error?: string | null;
}
const StrategyIntelligenceTab: React.FC<StrategyIntelligenceTabProps> = ({
strategyData,
loading = false,
error = null
}) => {
// Get review process state from store
const { reviewProcessStarted, startReviewProcess, components, initializeComponents } = useStrategyReviewStore();
// Initialize components if they don't exist
useEffect(() => {
if (components.length === 0) {
console.log('🔧 StrategyIntelligenceTab: Initializing components');
const STRATEGY_COMPONENTS = [
{
id: 'strategic_insights',
title: 'Strategic Insights',
subtitle: 'AI-powered market analysis'
},
{
id: 'competitive_analysis',
title: 'Competitive Analysis',
subtitle: 'Market positioning insights'
},
{
id: 'performance_predictions',
title: 'Performance Predictions',
subtitle: 'ROI and success metrics'
},
{
id: 'implementation_roadmap',
title: 'Implementation Roadmap',
subtitle: 'Project timeline and phases'
},
{
id: 'risk_assessment',
title: 'Risk Assessment',
subtitle: 'Risk analysis and mitigation'
}
];
initializeComponents(STRATEGY_COMPONENTS);
}
}, [components.length, initializeComponents]);
const handleStartReviewProcess = () => {
setShowReviewProgress(true);
startReviewProcess();
};
if (loading) {
@@ -58,9 +102,9 @@ const StrategyIntelligenceTab: React.FC = () => {
/>
{/* Review Progress Header - Only shown when review process is started */}
{showReviewProgress && <ReviewProgressHeader />}
{reviewProcessStarted && <ReviewProgressHeader strategyData={strategyData} />}
{/* Strategy Components Grid */}
{/* Strategy Intelligence Cards */}
<Box
sx={{
display: 'grid',
@@ -93,10 +137,6 @@ const StrategyIntelligenceTab: React.FC = () => {
<ImplementationRoadmapCard strategyData={strategyData} />
<RiskAssessmentCard strategyData={strategyData} />
</Box>
{/* Action Buttons - Removed, functionality moved to "Confirm & Activate Strategy" button in ReviewProgressHeader */}
{/* Confirmation Dialog - Removed, functionality moved to "Confirm & Activate Strategy" button */}
</Box>
);
};

View File

@@ -0,0 +1,447 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Grid,
Card,
CardContent,
Chip,
IconButton,
Tooltip,
Alert,
AlertTitle,
CircularProgress,
Divider,
Switch,
FormControlLabel
} from '@mui/material';
import {
Visibility as VisibilityIcon,
Refresh as RefreshIcon,
AutoAwesome as AutoAwesomeIcon,
TrendingUp as TrendingUpIcon,
People as PeopleIcon,
MonetizationOn as MonetizationOnIcon,
Speed as SpeedIcon,
Assessment as AssessmentIcon,
CheckCircle as CheckCircleIcon,
Schedule as ScheduleIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import MetricTransparencyCard from './MetricTransparencyCard';
import { strategyMonitoringApi } from '../../../../../services/strategyMonitoringApi';
interface DataTransparencyPanelProps {
strategyId: number;
strategyData?: any;
}
const DataTransparencyPanel: React.FC<DataTransparencyPanelProps> = ({
strategyId,
strategyData
}) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [transparencyData, setTransparencyData] = useState<any[]>([]);
const [lastRefresh, setLastRefresh] = useState<Date>(new Date());
const [autoRefresh, setAutoRefresh] = useState(true);
const [refreshInterval, setRefreshInterval] = useState<NodeJS.Timeout | null>(null);
useEffect(() => {
loadTransparencyData();
// Set up auto-refresh every 5 minutes if enabled
if (autoRefresh) {
const interval = setInterval(() => {
loadTransparencyData();
setLastRefresh(new Date());
}, 5 * 60 * 1000); // 5 minutes
setRefreshInterval(interval);
return () => {
if (interval) clearInterval(interval);
};
}
}, [strategyId, autoRefresh]);
// Cleanup interval on unmount
useEffect(() => {
return () => {
if (refreshInterval) {
clearInterval(refreshInterval);
}
};
}, [refreshInterval]);
const loadTransparencyData = async () => {
try {
setLoading(true);
setError(null);
// Try to get real data from API first
try {
const response = await strategyMonitoringApi.getTransparencyData(strategyId);
if (response.success && response.data) {
setTransparencyData(response.data);
return;
}
} catch (apiError) {
console.warn('API call failed, falling back to mock data:', apiError);
// Continue to mock data as fallback
}
// Fallback to mock data if API fails
const mockTransparencyData = [
{
metricName: "Traffic Growth",
currentValue: 15.7,
unit: "%",
dataFreshness: {
lastUpdated: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), // 2 hours ago
updateFrequency: "Every 4 hours",
dataSource: "Google Analytics + AI Analysis",
confidence: 92
},
measurementMethodology: {
description: "Organic traffic growth compared to previous period",
calculationMethod: "Percentage change in organic sessions over 30-day rolling period, weighted by content performance and user engagement",
dataPoints: ["Organic Sessions", "Page Views", "Bounce Rate", "Time on Site", "Content Performance"],
validationProcess: "Cross-validated with Google Search Console data and AI-powered content performance analysis"
},
monitoringTasks: [
{
title: "Monitor Organic Traffic Trends",
description: "Track daily organic traffic patterns and identify growth opportunities",
assignee: "ALwrity",
frequency: "Daily",
metric: "Organic Sessions",
measurementMethod: "Automated Google Analytics API integration with real-time data processing",
successCriteria: "Maintain 10%+ monthly growth rate with <5% variance",
alertThreshold: "Drop below 5% growth for 3 consecutive days",
actionableInsights: "Optimize content based on top-performing pages and keywords",
status: "active",
lastExecuted: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString()
},
{
title: "Content Performance Analysis",
description: "Analyze which content pieces drive the most traffic and engagement",
assignee: "ALwrity",
frequency: "Weekly",
metric: "Content Performance Score",
measurementMethod: "AI-powered content analysis using engagement metrics and conversion data",
successCriteria: "Identify top 20% performing content pieces",
alertThreshold: "Performance score drops below 70%",
actionableInsights: "Replicate successful content patterns and optimize underperforming pieces",
status: "completed",
lastExecuted: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
}
],
strategyMapping: {
relatedComponents: ["Strategic Insights", "Content Strategy", "Audience Analysis"],
impactAreas: ["Brand Awareness", "Lead Generation", "Market Reach"],
dependencies: ["SEO Optimization", "Content Quality", "User Experience"]
},
aiInsights: {
trendAnalysis: "Traffic growth shows strong upward trend with 15.7% increase. Top-performing content categories are educational blog posts and case studies. Seasonal patterns indicate peak engagement during business hours.",
recommendations: [
"Increase content production in educational blog category by 25%",
"Optimize case study content for better search visibility",
"Implement A/B testing for content headlines",
"Focus on long-form content (2000+ words) which shows 40% higher engagement"
],
riskFactors: ["Seasonal traffic fluctuations", "Competitor content strategy changes", "Algorithm updates"],
opportunities: ["Video content expansion", "Guest posting opportunities", "Social media amplification"]
}
},
{
metricName: "Engagement Rate",
currentValue: 8.3,
unit: "%",
dataFreshness: {
lastUpdated: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString(), // 1 hour ago
updateFrequency: "Every 2 hours",
dataSource: "Social Media Analytics + Website Analytics",
confidence: 88
},
measurementMethodology: {
description: "Average engagement rate across all content and social media",
calculationMethod: "Weighted average of likes, shares, comments, and time spent across all platforms",
dataPoints: ["Social Media Engagement", "Website Comments", "Time on Page", "Social Shares", "Email Engagement"],
validationProcess: "Cross-platform validation using multiple analytics tools and AI sentiment analysis"
},
monitoringTasks: [
{
title: "Social Media Engagement Tracking",
description: "Monitor engagement across all social media platforms",
assignee: "ALwrity",
frequency: "Real-time",
metric: "Engagement Rate",
measurementMethod: "Automated social media API integration with sentiment analysis",
successCriteria: "Maintain 8%+ average engagement rate",
alertThreshold: "Engagement drops below 5% for 24 hours",
actionableInsights: "Adjust content timing and messaging based on engagement patterns",
status: "active",
lastExecuted: new Date(Date.now() - 30 * 60 * 1000).toISOString()
}
],
strategyMapping: {
relatedComponents: ["Audience Analysis", "Content Strategy", "Social Media Strategy"],
impactAreas: ["Brand Engagement", "Community Building", "Customer Loyalty"],
dependencies: ["Content Quality", "Social Media Presence", "Community Management"]
},
aiInsights: {
trendAnalysis: "Engagement rate is stable at 8.3% with peak engagement during lunch hours and early evenings. Video content shows 2.5x higher engagement than text-only posts.",
recommendations: [
"Increase video content production by 50%",
"Optimize posting times for peak engagement hours",
"Implement interactive content elements",
"Focus on community-building content"
],
riskFactors: ["Platform algorithm changes", "Content fatigue", "Competition for attention"],
opportunities: ["Live streaming opportunities", "User-generated content campaigns", "Influencer collaborations"]
}
},
{
metricName: "Conversion Rate",
currentValue: 2.1,
unit: "%",
dataFreshness: {
lastUpdated: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), // 6 hours ago
updateFrequency: "Every 6 hours",
dataSource: "Google Analytics + CRM Data",
confidence: 85
},
measurementMethodology: {
description: "Content-driven conversion rate across all touchpoints",
calculationMethod: "Conversions divided by total visitors, weighted by content attribution and customer journey analysis",
dataPoints: ["Website Conversions", "Email Signups", "Lead Form Submissions", "Content Downloads", "Sales Attribution"],
validationProcess: "CRM integration validation and conversion funnel analysis"
},
monitoringTasks: [
{
title: "Conversion Funnel Analysis",
description: "Track conversion rates at each stage of the customer journey",
assignee: "ALwrity",
frequency: "Daily",
metric: "Conversion Rate",
measurementMethod: "Automated funnel analysis using Google Analytics and CRM data",
successCriteria: "Maintain 2%+ overall conversion rate",
alertThreshold: "Conversion rate drops below 1.5%",
actionableInsights: "Optimize conversion points and remove friction from customer journey",
status: "active",
lastExecuted: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString()
}
],
strategyMapping: {
relatedComponents: ["Performance Predictions", "Implementation Roadmap", "Risk Assessment"],
impactAreas: ["Revenue Generation", "Lead Quality", "Customer Acquisition"],
dependencies: ["Content Quality", "User Experience", "Lead Nurturing"]
},
aiInsights: {
trendAnalysis: "Conversion rate is improving steadily with 2.1% current rate. Top-converting content includes case studies and product demos. Mobile conversions show 30% improvement after UX optimization.",
recommendations: [
"Increase case study and demo content production",
"Optimize mobile user experience further",
"Implement personalized content recommendations",
"A/B test call-to-action buttons and forms"
],
riskFactors: ["Market competition", "Economic factors", "Technology changes"],
opportunities: ["Personalization opportunities", "Automation implementation", "Cross-selling strategies"]
}
}
];
setTransparencyData(mockTransparencyData);
} catch (err: any) {
setError(err.message || 'Failed to load transparency data');
} finally {
setLoading(false);
}
};
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<CircularProgress size={60} />
</Box>
);
}
if (error) {
return (
<Alert severity="error" sx={{ mb: 3 }}>
<AlertTitle>Error Loading Transparency Data</AlertTitle>
{error}
</Alert>
);
}
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
{/* Header */}
<Box sx={{ mb: 4 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Box>
<Typography variant="h4" sx={{
fontWeight: 700,
background: 'linear-gradient(45deg, #667eea, #764ba2)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
mb: 1
}}>
🔍 Data Transparency & Methodology
</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary', mb: 1 }}>
Detailed insights into how each metric is measured, data freshness, and AI monitoring tasks
</Typography>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="caption" sx={{ color: 'text.secondary', display: 'flex', alignItems: 'center', gap: 0.5 }}>
<ScheduleIcon sx={{ fontSize: 14 }} />
Last updated: {lastRefresh.toLocaleTimeString()}
</Typography>
<FormControlLabel
control={
<Switch
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.target.checked)}
size="small"
/>
}
label="Auto-refresh"
sx={{ '& .MuiFormControlLabel-label': { fontSize: '0.75rem' } }}
/>
</Box>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Tooltip title="Refresh transparency data">
<IconButton
onClick={() => {
loadTransparencyData();
setLastRefresh(new Date());
}}
sx={{ color: 'primary.main' }}
disabled={loading}
>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
<Divider sx={{ mb: 3 }} />
</Box>
{/* Summary Cards */}
<Grid container spacing={3} sx={{ mb: 4 }}>
<Grid item xs={12} md={3}>
<Card sx={{ background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)', color: 'white' }}>
<CardContent sx={{ p: 2, textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
{transparencyData.length}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Metrics Tracked
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={3}>
<Card sx={{ background: 'linear-gradient(135deg, #2196f3 0%, #1976d2 100%)', color: 'white' }}>
<CardContent sx={{ p: 2, textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
{transparencyData.reduce((acc, metric) => acc + metric.monitoringTasks.length, 0)}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
AI Monitoring Tasks
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={3}>
<Card sx={{ background: 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)', color: 'white' }}>
<CardContent sx={{ p: 2, textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
{Math.round(transparencyData.reduce((acc, metric) => acc + metric.dataFreshness.confidence, 0) / transparencyData.length)}%
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Avg. Data Confidence
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={3}>
<Card sx={{ background: 'linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%)', color: 'white' }}>
<CardContent sx={{ p: 2, textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
Real-time
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Data Updates
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Transparency Cards */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{transparencyData.map((metricData, index) => (
<MetricTransparencyCard
key={index}
metricData={metricData}
isExpanded={index === 0} // First card expanded by default
/>
))}
</Box>
{/* Footer Information */}
<Box sx={{ mt: 4, p: 3, background: 'rgba(102, 126, 234, 0.1)', borderRadius: 2 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon />
How This Data Helps Your Strategy
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
📊 Data-Driven Decisions
</Typography>
<Typography variant="body2" sx={{ opacity: 0.8 }}>
Understand exactly how each metric is calculated and what data sources are used, ensuring confidence in your strategic decisions.
</Typography>
</Grid>
<Grid item xs={12} md={4}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
🤖 AI-Powered Monitoring
</Typography>
<Typography variant="body2" sx={{ opacity: 0.8 }}>
See how AI tasks are monitoring your strategy performance and get actionable insights for optimization.
</Typography>
</Grid>
<Grid item xs={12} md={4}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
🎯 Strategy Alignment
</Typography>
<Typography variant="body2" sx={{ opacity: 0.8 }}>
Understand how each metric maps to your strategy components and identify areas for improvement.
</Typography>
</Grid>
</Grid>
</Box>
</motion.div>
);
};
export default DataTransparencyPanel;

View File

@@ -0,0 +1,497 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Grid,
Card,
CardContent,
Typography,
Chip,
IconButton,
Tooltip,
Alert,
AlertTitle,
CircularProgress,
LinearProgress,
Divider,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
List,
ListItem,
ListItemText,
ListItemIcon
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Assessment as AssessmentIcon,
Speed as SpeedIcon,
Visibility as VisibilityIcon,
People as EngagementIcon,
MonetizationOn as MonetizationOnIcon,
Refresh as RefreshIcon,
AutoAwesome as AutoAwesomeIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Error as ErrorIcon,
Analytics as AnalyticsIcon,
Lightbulb as LightbulbIcon,
Timeline as TimelineIcon,
Close as CloseIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
// Import our advanced chart components
import {
PerformanceTrendChart,
QualityMetricsRadar,
PerformanceMetricsBar,
ContentDistributionPie,
PerformanceGauge
} from '../../../../shared/charts/AdvancedChartComponents';
// Import real-time data hook
import { useMockRealTimeData } from '../../../../../hooks/useRealTimeData';
// Import API services
import { strategyMonitoringApi } from '../../../../../services/strategyMonitoringApi';
interface EnhancedPerformanceVisualizationProps {
strategyId: number;
strategyData: any;
}
interface QualityAnalysisData {
overall_score: number;
overall_status: string;
metrics: Array<{
name: string;
score: number;
status: string;
description: string;
recommendations: string[];
}>;
recommendations: string[];
confidence_score: number;
}
const EnhancedPerformanceVisualization: React.FC<EnhancedPerformanceVisualizationProps> = ({
strategyId,
strategyData
}) => {
const [qualityAnalysis, setQualityAnalysis] = useState<QualityAnalysisData | null>(null);
const [loadingQuality, setLoadingQuality] = useState(false);
const [showQualityDialog, setShowQualityDialog] = useState(false);
const [error, setError] = useState<string | null>(null);
// Use real-time data hook
const { data: realTimeData, isConnected, error: realTimeError } = useMockRealTimeData(strategyId);
useEffect(() => {
loadQualityAnalysis();
}, [strategyId]);
const loadQualityAnalysis = async () => {
try {
setLoadingQuality(true);
setError(null);
// Call the quality analysis API
const response = await strategyMonitoringApi.getQualityAnalysis(strategyId);
setQualityAnalysis(response.data);
} catch (err: any) {
setError(err.message || 'Failed to load quality analysis');
console.error('Error loading quality analysis:', err);
} finally {
setLoadingQuality(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'excellent': return 'success';
case 'good': return 'info';
case 'needs_attention': return 'warning';
case 'poor': return 'error';
default: return 'default';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'excellent': return <CheckCircleIcon />;
case 'good': return <CheckCircleIcon />;
case 'needs_attention': return <WarningIcon />;
case 'poor': return <ErrorIcon />;
default: return <AssessmentIcon />;
}
};
// Prepare chart data from real-time data
const trendData = realTimeData?.trends?.daily || [];
const qualityMetricsData = qualityAnalysis?.metrics?.map(metric => ({
metric: metric.name,
score: metric.score,
target: 85 // Target score
})) || [];
const performanceMetricsData = realTimeData?.metrics ? [
{
metric: 'Traffic Growth',
value: realTimeData.metrics.traffic_growth_percentage,
target: 15,
status: (realTimeData.metrics.traffic_growth_percentage >= 15 ? 'excellent' :
realTimeData.metrics.traffic_growth_percentage >= 10 ? 'good' : 'needs_attention') as 'excellent' | 'good' | 'needs_attention'
},
{
metric: 'Engagement Rate',
value: realTimeData.metrics.engagement_rate_percentage,
target: 8,
status: (realTimeData.metrics.engagement_rate_percentage >= 8 ? 'excellent' :
realTimeData.metrics.engagement_rate_percentage >= 6 ? 'good' : 'needs_attention') as 'excellent' | 'good' | 'needs_attention'
},
{
metric: 'Conversion Rate',
value: realTimeData.metrics.conversion_rate_percentage,
target: 2.5,
status: (realTimeData.metrics.conversion_rate_percentage >= 2.5 ? 'excellent' :
realTimeData.metrics.conversion_rate_percentage >= 2 ? 'good' : 'needs_attention') as 'excellent' | 'good' | 'needs_attention'
},
{
metric: 'Content Quality',
value: realTimeData.metrics.content_quality_score,
target: 90,
status: (realTimeData.metrics.content_quality_score >= 90 ? 'excellent' :
realTimeData.metrics.content_quality_score >= 80 ? 'good' : 'needs_attention') as 'excellent' | 'good' | 'needs_attention'
}
] : [];
const contentDistributionData = [
{ name: 'Blog Posts', value: 40, color: '#667eea' },
{ name: 'Social Media', value: 25, color: '#764ba2' },
{ name: 'Video Content', value: 20, color: '#4caf50' },
{ name: 'Infographics', value: 10, color: '#ff9800' },
{ name: 'Newsletters', value: 5, color: '#f44336' }
];
if (error) {
return (
<Alert severity="error" sx={{ mb: 3 }}>
<AlertTitle>Error Loading Performance Data</AlertTitle>
{error}
</Alert>
);
}
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
{/* Header Section */}
<Box sx={{ mb: 4 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Box>
<Typography variant="h4" sx={{
fontWeight: 700,
background: 'linear-gradient(45deg, #667eea, #764ba2)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
mb: 1
}}>
🚀 Advanced Performance Dashboard
</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
Real-time monitoring and AI-powered quality analysis for your content strategy
</Typography>
</Box>
<Box display="flex" alignItems="center" gap={2}>
<Chip
icon={isConnected ? <CheckCircleIcon /> : <ErrorIcon />}
label={isConnected ? 'Live Data' : 'Offline'}
color={isConnected ? 'success' : 'error'}
size="small"
/>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Last updated: {realTimeData?.timestamp ? new Date(realTimeData.timestamp).toLocaleString() : 'N/A'}
</Typography>
<Tooltip title="Refresh data">
<IconButton onClick={loadQualityAnalysis} sx={{ color: 'primary.main' }}>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
<Divider sx={{ mb: 3 }} />
</Box>
{/* Real-time Performance Gauges */}
{realTimeData?.metrics && (
<Grid container spacing={3} sx={{ mb: 4 }}>
<Grid item xs={12} md={6} lg={3}>
<PerformanceGauge
value={realTimeData.metrics.traffic_growth_percentage}
maxValue={25}
title="Traffic Growth"
color="#667eea"
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<PerformanceGauge
value={realTimeData.metrics.engagement_rate_percentage}
maxValue={15}
title="Engagement Rate"
color="#4caf50"
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<PerformanceGauge
value={realTimeData.metrics.conversion_rate_percentage}
maxValue={5}
title="Conversion Rate"
color="#ff9800"
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<PerformanceGauge
value={realTimeData.metrics.content_quality_score}
maxValue={100}
title="Content Quality"
color="#2196f3"
/>
</Grid>
</Grid>
)}
{/* Quality Analysis Section */}
<Box sx={{ mb: 4 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h5" sx={{ fontWeight: 600 }}>
<AutoAwesomeIcon sx={{ mr: 1, color: 'primary.main' }} />
AI Quality Analysis
</Typography>
<Button
variant="outlined"
startIcon={<AnalyticsIcon />}
onClick={() => setShowQualityDialog(true)}
disabled={loadingQuality}
>
{loadingQuality ? 'Analyzing...' : 'View Detailed Analysis'}
</Button>
</Box>
{qualityAnalysis && (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card elevation={2}>
<CardContent>
<Typography variant="h6" gutterBottom>
Overall Quality Score
</Typography>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="h3" sx={{ fontWeight: 'bold', color: 'primary.main' }}>
{qualityAnalysis.overall_score.toFixed(1)}
</Typography>
<Box>
<Chip
icon={getStatusIcon(qualityAnalysis.overall_status)}
label={qualityAnalysis.overall_status.replace('_', ' ').toUpperCase()}
color={getStatusColor(qualityAnalysis.overall_status) as any}
size="small"
/>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
Confidence: {qualityAnalysis.confidence_score.toFixed(1)}%
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card elevation={2}>
<CardContent>
<Typography variant="h6" gutterBottom>
Priority Areas
</Typography>
<Box display="flex" flexWrap="wrap" gap={1}>
{qualityAnalysis.metrics
.filter(metric => metric.status === 'needs_attention' || metric.status === 'poor')
.map((metric, index) => (
<Chip
key={index}
label={metric.name}
color={getStatusColor(metric.status) as any}
size="small"
variant="outlined"
/>
))}
{qualityAnalysis.metrics.filter(m => m.status === 'needs_attention' || m.status === 'poor').length === 0 && (
<Typography variant="body2" color="text.secondary">
All areas are performing well! 🎉
</Typography>
)}
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
)}
</Box>
{/* Advanced Charts Section */}
<Grid container spacing={3} sx={{ mb: 4 }}>
{/* Performance Trends Chart */}
<Grid item xs={12} lg={8}>
<PerformanceTrendChart
data={trendData}
title="Performance Trends Over Time"
height={400}
/>
</Grid>
{/* Content Distribution Chart */}
<Grid item xs={12} lg={4}>
<ContentDistributionPie
data={contentDistributionData}
title="Content Distribution"
height={400}
/>
</Grid>
{/* Quality Metrics Radar Chart */}
<Grid item xs={12} lg={6}>
<QualityMetricsRadar
data={qualityMetricsData}
title="Quality Metrics Analysis"
height={400}
/>
</Grid>
{/* Performance Metrics Bar Chart */}
<Grid item xs={12} lg={6}>
<PerformanceMetricsBar
data={performanceMetricsData}
title="Performance vs Targets"
height={400}
/>
</Grid>
</Grid>
{/* Quality Analysis Dialog */}
<Dialog
open={showQualityDialog}
onClose={() => setShowQualityDialog(false)}
maxWidth="lg"
fullWidth
>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Box display="flex" alignItems="center" gap={1}>
<AutoAwesomeIcon color="primary" />
<Typography variant="h6">AI Quality Analysis Details</Typography>
</Box>
<IconButton onClick={() => setShowQualityDialog(false)}>
<CloseIcon />
</IconButton>
</Box>
</DialogTitle>
<DialogContent>
{qualityAnalysis && (
<Box>
{/* Overall Score */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Overall Quality Assessment
</Typography>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="h4" sx={{ fontWeight: 'bold', color: 'primary.main' }}>
{qualityAnalysis.overall_score.toFixed(1)}/100
</Typography>
<Chip
icon={getStatusIcon(qualityAnalysis.overall_status)}
label={qualityAnalysis.overall_status.replace('_', ' ').toUpperCase()}
color={getStatusColor(qualityAnalysis.overall_status) as any}
/>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
Confidence Score: {qualityAnalysis.confidence_score.toFixed(1)}%
</Typography>
</CardContent>
</Card>
{/* Detailed Metrics */}
<Typography variant="h6" gutterBottom>
Detailed Quality Metrics
</Typography>
<Grid container spacing={2} sx={{ mb: 3 }}>
{qualityAnalysis.metrics.map((metric, index) => (
<Grid item xs={12} md={6} key={index}>
<Card variant="outlined">
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={1}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
{metric.name}
</Typography>
<Chip
label={`${metric.score.toFixed(1)}/100`}
color={getStatusColor(metric.status) as any}
size="small"
/>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{metric.description}
</Typography>
<Chip
icon={getStatusIcon(metric.status)}
label={metric.status.replace('_', ' ').toUpperCase()}
color={getStatusColor(metric.status) as any}
size="small"
variant="outlined"
/>
</CardContent>
</Card>
</Grid>
))}
</Grid>
{/* Recommendations */}
<Typography variant="h6" gutterBottom>
<LightbulbIcon sx={{ mr: 1, color: 'warning.main' }} />
AI Recommendations
</Typography>
<List>
{qualityAnalysis.recommendations.map((recommendation, index) => (
<ListItem key={index}>
<ListItemIcon>
<LightbulbIcon color="warning" />
</ListItemIcon>
<ListItemText primary={recommendation} />
</ListItem>
))}
</List>
</Box>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setShowQualityDialog(false)}>Close</Button>
<Button
variant="contained"
onClick={loadQualityAnalysis}
disabled={loadingQuality}
>
Refresh Analysis
</Button>
</DialogActions>
</Dialog>
</motion.div>
);
};
export default EnhancedPerformanceVisualization;

View File

@@ -0,0 +1,403 @@
import React, { useState } from 'react';
import {
Box,
Button,
CircularProgress,
Typography,
Alert,
Snackbar
} from '@mui/material';
import {
Check as CheckIcon,
PlayArrow as PlayArrowIcon,
AutoAwesome as AutoAwesomeIcon,
Celebration as CelebrationIcon
} from '@mui/icons-material';
import { motion, AnimatePresence, easeOut } from 'framer-motion';
import StrategyActivationModal from '../../StrategyActivationModal';
import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
interface EnhancedStrategyActivationButtonProps {
strategyData: any;
strategyConfirmed: boolean;
onConfirmStrategy: () => Promise<void>;
onGenerateContentCalendar: () => void;
disabled?: boolean;
}
const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButtonProps> = ({
strategyData,
strategyConfirmed,
onConfirmStrategy,
onGenerateContentCalendar,
disabled = false
}) => {
const [isLoading, setIsLoading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
const [activationProgress, setActivationProgress] = useState(0);
const [showActivationModal, setShowActivationModal] = useState(false);
// Initialize navigation orchestrator
const navigationOrchestrator = useNavigationOrchestrator();
const handleActivation = async () => {
console.log('🎯 EnhancedStrategyActivationButton: handleActivation called');
if (isLoading || disabled) return;
// For now, directly call the activation function instead of opening the modal
console.log('🎯 EnhancedStrategyActivationButton: Directly calling onConfirmStrategy');
try {
await onConfirmStrategy();
console.log('🎯 EnhancedStrategyActivationButton: onConfirmStrategy completed successfully');
} catch (error) {
console.error('🎯 EnhancedStrategyActivationButton: onConfirmStrategy failed:', error);
}
// Open the activation modal instead of calling onConfirmStrategy directly
console.log('🎯 EnhancedStrategyActivationButton: Opening activation modal');
setShowActivationModal(true);
};
const handleGenerateCalendar = () => {
onGenerateContentCalendar();
};
const handleCloseModal = () => {
setShowActivationModal(false);
};
const handleSetupMonitoring = async (monitoringPlan: any) => {
try {
console.log('🎯 EnhancedStrategyActivationButton: handleSetupMonitoring called');
// Call the actual activation function
console.log('🎯 EnhancedStrategyActivationButton: Calling onConfirmStrategy()');
await onConfirmStrategy();
console.log('🎯 EnhancedStrategyActivationButton: onConfirmStrategy() completed');
// Update strategy state to confirmed/active
// This will trigger UI updates in parent components
// Show success state
setIsSuccess(true);
setShowSuccessMessage(true);
// Use navigation orchestrator to handle successful activation
const strategyId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1';
navigationOrchestrator.handleStrategyActivationSuccess(strategyId, strategyData);
// Reset after success animation
setTimeout(() => {
setIsSuccess(false);
setActivationProgress(0);
}, 2000);
} catch (error) {
console.error('Strategy activation failed:', error);
throw error;
}
};
// Success animation variants
const successVariants = {
initial: { scale: 0, opacity: 0 },
animate: {
scale: [0, 1.2, 1],
opacity: [0, 1, 1],
transition: { duration: 0.6, ease: easeOut }
},
exit: { scale: 0, opacity: 0 }
};
// Confetti animation variants
const confettiVariants = {
initial: { y: -20, opacity: 0, rotate: 0 },
animate: {
y: [0, -30, 0],
opacity: [0, 1, 0],
rotate: [0, 360],
transition: { duration: 1.5, ease: easeOut }
}
};
return (
<Box sx={{ position: 'relative' }}>
{/* Strategy Activation Modal */}
<StrategyActivationModal
open={showActivationModal}
onClose={handleCloseModal}
strategyId={strategyData?.id || 1} // Use actual strategy ID
strategyData={strategyData}
onSetupMonitoring={handleSetupMonitoring}
/>
{/* Success Message Snackbar */}
<Snackbar
open={showSuccessMessage}
autoHideDuration={4000}
onClose={() => setShowSuccessMessage(false)}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<Alert
severity="success"
sx={{
borderRadius: 2,
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
border: '1px solid rgba(76, 175, 80, 0.3)'
}}
>
🎉 Strategy activated successfully! Ready to generate content calendar.
</Alert>
</Snackbar>
{/* Main Button Container */}
<Box sx={{ display: 'flex', justifyContent: 'center', position: 'relative' }}>
<motion.div
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
style={{ position: 'relative' }}
>
{/* Enhanced Activation Button */}
<Button
variant="contained"
size="large"
onClick={strategyConfirmed ? handleGenerateCalendar : handleActivation}
disabled={disabled || isLoading}
startIcon={
isLoading ? (
<CircularProgress
size={20}
sx={{ color: 'white' }}
/>
) : strategyConfirmed ? (
<AutoAwesomeIcon />
) : (
<PlayArrowIcon />
)
}
sx={{
background: strategyConfirmed
? 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)'
: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 4,
px: 6,
py: 2,
fontWeight: 700,
fontSize: '1.1rem',
textTransform: 'none',
letterSpacing: '0.5px',
boxShadow: strategyConfirmed
? '0 8px 32px rgba(76, 175, 80, 0.4), 0 0 20px rgba(76, 175, 80, 0.2)'
: '0 8px 32px rgba(102, 126, 234, 0.4), 0 0 20px rgba(102, 126, 234, 0.2)',
border: '2px solid transparent',
backgroundClip: 'padding-box',
position: 'relative',
overflow: 'hidden',
minWidth: 280,
height: 56,
'&:hover': {
background: strategyConfirmed
? 'linear-gradient(135deg, #45a049 0%, #3d8b40 100%)'
: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)',
boxShadow: strategyConfirmed
? '0 12px 40px rgba(76, 175, 80, 0.5), 0 0 30px rgba(76, 175, 80, 0.3)'
: '0 12px 40px rgba(102, 126, 234, 0.5), 0 0 30px rgba(102, 126, 234, 0.3)',
transform: 'translateY(-3px)',
'&::before': {
opacity: 1,
transform: 'scale(1.1)'
}
},
'&:disabled': {
background: 'linear-gradient(135deg, #9e9e9e 0%, #757575 100%)',
boxShadow: 'none',
transform: 'none'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.2) 50%, transparent 70%)',
opacity: 0,
transform: 'scale(0.8)',
transition: 'all 0.3s ease',
pointerEvents: 'none'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
{/* Button Text */}
<AnimatePresence mode="wait">
{isLoading ? (
<motion.div
key="loading"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
<Typography variant="button" sx={{ fontWeight: 600 }}>
Activating Strategy... {activationProgress}%
</Typography>
</motion.div>
) : isSuccess ? (
<motion.div
key="success"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3 }}
>
<Typography variant="button" sx={{ fontWeight: 600 }}>
Strategy Activated! 🎉
</Typography>
</motion.div>
) : strategyConfirmed ? (
<motion.div
key="calendar"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
>
<Typography variant="button" sx={{ fontWeight: 600 }}>
Generate Content Calendar
</Typography>
</motion.div>
) : (
<motion.div
key="activate"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
>
<Typography variant="button" sx={{ fontWeight: 600 }}>
Confirm & Activate Strategy
</Typography>
</motion.div>
)}
</AnimatePresence>
</Button>
{/* Progress Ring (shown during loading) */}
<AnimatePresence>
{isLoading && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1
}}
>
<Box
sx={{
width: 80,
height: 80,
borderRadius: '50%',
background: 'conic-gradient(from 0deg, rgba(102, 126, 234, 0.3) 0deg, rgba(102, 126, 234, 0.8) 360deg)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
animation: 'spin 2s linear infinite',
'@keyframes spin': {
'0%': { transform: 'translate(-50%, -50%) rotate(0deg)' },
'100%': { transform: 'translate(-50%, -50%) rotate(360deg)' }
}
}}
/>
</motion.div>
)}
</AnimatePresence>
{/* Success Animation Overlay */}
<AnimatePresence>
{isSuccess && (
<motion.div
variants={successVariants}
initial="initial"
animate="animate"
exit="exit"
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 2
}}
>
<Box
sx={{
width: 60,
height: 60,
borderRadius: '50%',
background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.4)'
}}
>
<CheckIcon sx={{ color: 'white', fontSize: 32 }} />
</Box>
</motion.div>
)}
</AnimatePresence>
{/* Confetti Animation */}
<AnimatePresence>
{isSuccess && (
<>
{[...Array(8)].map((_, index) => (
<motion.div
key={index}
variants={confettiVariants}
initial="initial"
animate="animate"
style={{
position: 'absolute',
top: '50%',
left: '50%',
zIndex: 3,
transform: `translate(-50%, -50%) rotate(${index * 45}deg)`
}}
>
<CelebrationIcon
sx={{
color: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'][index % 5],
fontSize: 16
}}
/>
</motion.div>
))}
</>
)}
</AnimatePresence>
</motion.div>
</Box>
{/* Status Text */}
<AnimatePresence>
{isLoading && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
style={{ marginTop: 16, textAlign: 'center' }}
>
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
Setting up monitoring and quality gates...
</Typography>
</motion.div>
)}
</AnimatePresence>
</Box>
);
};
export default EnhancedStrategyActivationButton;

View File

@@ -0,0 +1,456 @@
import React, { useState } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Chip,
IconButton,
Collapse,
Grid,
List,
ListItem,
ListItemIcon,
ListItemText,
Divider,
Tooltip,
Alert,
AlertTitle
} from '@mui/material';
import {
Info as InfoIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
Schedule as ScheduleIcon,
Assessment as AssessmentIcon,
AutoAwesome as AutoAwesomeIcon,
TrendingUp as TrendingUpIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Error as ErrorIcon,
DataUsage as DataUsageIcon,
Psychology as PsychologyIcon,
Timeline as TimelineIcon,
Refresh as RefreshIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
interface MonitoringTask {
title: string;
description: string;
assignee: 'ALwrity' | 'Human';
frequency: string;
metric: string;
measurementMethod: string;
successCriteria: string;
alertThreshold: string;
actionableInsights?: string;
lastExecuted?: string;
status: 'pending' | 'active' | 'completed' | 'failed';
}
interface MetricTransparencyData {
metricName: string;
currentValue: number;
unit: string;
dataFreshness: {
lastUpdated: string;
updateFrequency: string;
dataSource: string;
confidence: number;
};
measurementMethodology: {
description: string;
calculationMethod: string;
dataPoints: string[];
validationProcess: string;
};
monitoringTasks: MonitoringTask[];
strategyMapping: {
relatedComponents: string[];
impactAreas: string[];
dependencies: string[];
};
aiInsights: {
trendAnalysis: string;
recommendations: string[];
riskFactors: string[];
opportunities: string[];
};
}
interface MetricTransparencyCardProps {
metricData: MetricTransparencyData;
isExpanded?: boolean;
onToggle?: () => void;
}
const MetricTransparencyCard: React.FC<MetricTransparencyCardProps> = ({
metricData,
isExpanded = false,
onToggle
}) => {
const [expanded, setExpanded] = useState(isExpanded);
const handleToggle = () => {
setExpanded(!expanded);
onToggle?.();
};
const getStatusColor = (status: string) => {
switch (status) {
case 'active': return '#4caf50';
case 'completed': return '#2196f3';
case 'pending': return '#ff9800';
case 'failed': return '#f44336';
default: return '#9e9e9e';
}
};
const getConfidenceColor = (confidence: number) => {
if (confidence >= 90) return '#4caf50';
if (confidence >= 70) return '#ff9800';
return '#f44336';
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
const diffInHours = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60));
if (diffInHours < 1) return 'Just now';
if (diffInHours < 24) return `${diffInHours} hours ago`;
if (diffInHours < 168) return `${Math.floor(diffInHours / 24)} days ago`;
return date.toLocaleDateString();
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Card sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
position: 'relative',
overflow: 'hidden',
mb: 2,
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(255,255,255,0.1) 0%, transparent 50%)',
pointerEvents: 'none'
}
}}>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 3 }}>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Box display="flex" alignItems="center" gap={2}>
<AutoAwesomeIcon sx={{ fontSize: 28, color: 'rgba(255,255,255,0.9)' }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>
{metricData.metricName}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.8 }}>
{metricData.currentValue}{metricData.unit}
</Typography>
</Box>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Chip
label={`${metricData.dataFreshness.confidence}% Confidence`}
size="small"
sx={{
background: getConfidenceColor(metricData.dataFreshness.confidence),
color: 'white',
fontWeight: 600
}}
/>
<Tooltip title={expanded ? "Hide details" : "Show details"}>
<IconButton onClick={handleToggle} sx={{ color: 'white' }}>
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Tooltip>
</Box>
</Box>
{/* Data Freshness Summary */}
<Box sx={{ mb: 2, p: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 1 }}>
<Box display="flex" alignItems="center" gap={1} mb={1}>
<ScheduleIcon sx={{ fontSize: 16 }} />
<Typography variant="body2" sx={{ fontWeight: 600 }}>
Data Freshness
</Typography>
</Box>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Last updated: {formatDate(metricData.dataFreshness.lastUpdated)}
Source: {metricData.dataFreshness.dataSource}
Updates: {metricData.dataFreshness.updateFrequency}
</Typography>
</Box>
{/* Expanded Content */}
<Collapse in={expanded}>
<Box sx={{ mt: 2 }}>
<Divider sx={{ mb: 2, borderColor: 'rgba(255,255,255,0.2)' }} />
{/* Measurement Methodology */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<AssessmentIcon />
Measurement Methodology
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Calculation Method
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9, mb: 2 }}>
{metricData.measurementMethodology.calculationMethod}
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Validation Process
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9, mb: 2 }}>
{metricData.measurementMethodology.validationProcess}
</Typography>
</Grid>
</Grid>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Data Points Used
</Typography>
<Box display="flex" gap={1} flexWrap="wrap" mb={2}>
{metricData.measurementMethodology.dataPoints.map((point, index) => (
<Chip
key={index}
label={point}
size="small"
sx={{
background: 'rgba(255,255,255,0.2)',
color: 'white',
fontSize: '0.7rem'
}}
/>
))}
</Box>
</Box>
{/* AI Monitoring Tasks */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<PsychologyIcon />
AI Monitoring Tasks ({metricData.monitoringTasks.length})
</Typography>
<List sx={{ p: 0 }}>
{metricData.monitoringTasks.map((task, index) => (
<ListItem key={index} sx={{
background: 'rgba(255,255,255,0.05)',
borderRadius: 1,
mb: 1,
flexDirection: 'column',
alignItems: 'flex-start'
}}>
<Box display="flex" justifyContent="space-between" alignItems="center" width="100%" mb={1}>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{task.title}
</Typography>
<Box display="flex" gap={1}>
<Chip
label={task.assignee}
size="small"
sx={{
background: task.assignee === 'ALwrity' ? '#4caf50' : '#2196f3',
color: 'white',
fontSize: '0.6rem'
}}
/>
<Chip
label={task.status}
size="small"
sx={{
background: getStatusColor(task.status),
color: 'white',
fontSize: '0.6rem'
}}
/>
</Box>
</Box>
<Typography variant="body2" sx={{ opacity: 0.8, mb: 1 }}>
{task.description}
</Typography>
<Grid container spacing={2} sx={{ width: '100%' }}>
<Grid item xs={12} sm={6}>
<Typography variant="caption" sx={{ fontWeight: 600, color: 'rgba(255,255,255,0.7)' }}>
Measurement Method
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.75rem', opacity: 0.8 }}>
{task.measurementMethod}
</Typography>
</Grid>
<Grid item xs={12} sm={6}>
<Typography variant="caption" sx={{ fontWeight: 600, color: 'rgba(255,255,255,0.7)' }}>
Success Criteria
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.75rem', opacity: 0.8 }}>
{task.successCriteria}
</Typography>
</Grid>
</Grid>
</ListItem>
))}
</List>
</Box>
{/* Strategy Mapping */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<TimelineIcon />
Strategy Component Mapping
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Related Components
</Typography>
<Box display="flex" gap={1} flexWrap="wrap">
{metricData.strategyMapping.relatedComponents.map((component, index) => (
<Chip
key={index}
label={component}
size="small"
sx={{
background: 'rgba(255,255,255,0.2)',
color: 'white',
fontSize: '0.7rem'
}}
/>
))}
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Impact Areas
</Typography>
<Box display="flex" gap={1} flexWrap="wrap">
{metricData.strategyMapping.impactAreas.map((area, index) => (
<Chip
key={index}
label={area}
size="small"
sx={{
background: 'rgba(76, 175, 80, 0.3)',
color: '#4caf50',
fontSize: '0.7rem'
}}
/>
))}
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Dependencies
</Typography>
<Box display="flex" gap={1} flexWrap="wrap">
{metricData.strategyMapping.dependencies.map((dep, index) => (
<Chip
key={index}
label={dep}
size="small"
sx={{
background: 'rgba(255, 152, 0, 0.3)',
color: '#ff9800',
fontSize: '0.7rem'
}}
/>
))}
</Box>
</Grid>
</Grid>
</Box>
{/* AI Insights */}
<Box>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon />
AI-Powered Insights
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Trend Analysis
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9, mb: 2 }}>
{metricData.aiInsights.trendAnalysis}
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Risk Factors
</Typography>
<Box display="flex" gap={1} flexWrap="wrap" mb={2}>
{metricData.aiInsights.riskFactors.map((risk, index) => (
<Chip
key={index}
label={risk}
size="small"
sx={{
background: 'rgba(244, 67, 54, 0.3)',
color: '#f44336',
fontSize: '0.7rem'
}}
/>
))}
</Box>
</Grid>
</Grid>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
Recommendations
</Typography>
<List sx={{ p: 0 }}>
{metricData.aiInsights.recommendations.map((rec, index) => (
<ListItem key={index} sx={{ p: 0, mb: 1 }}>
<ListItemIcon sx={{ minWidth: 24 }}>
<CheckCircleIcon sx={{ fontSize: 16, color: '#4caf50' }} />
</ListItemIcon>
<ListItemText
primary={rec}
sx={{
'& .MuiListItemText-primary': {
fontSize: '0.85rem',
opacity: 0.9
}
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
</Collapse>
</CardContent>
</Card>
</motion.div>
);
};
export default MetricTransparencyCard;

View File

@@ -0,0 +1,439 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
CircularProgress,
Chip,
IconButton,
Tooltip,
Alert,
AlertTitle,
LinearProgress,
Divider
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Assessment as AssessmentIcon,
Speed as SpeedIcon,
Visibility as VisibilityIcon,
People as EngagementIcon,
MonetizationOn as MonetizationOnIcon,
Refresh as RefreshIcon,
AutoAwesome as AutoAwesomeIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Error as ErrorIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { strategyMonitoringApi } from '../../../../../services/strategyMonitoringApi';
interface PerformanceMetrics {
traffic_growth_percentage: number;
engagement_rate_percentage: number;
conversion_rate_percentage: number;
roi_ratio: number;
strategy_adoption_rate: number;
content_quality_score: number;
competitive_position_rank: number;
audience_growth_percentage: number;
confidence_score: number;
last_updated: string;
}
interface PerformanceVisualizationProps {
strategyId: number;
strategyData?: any;
}
const PerformanceVisualization: React.FC<PerformanceVisualizationProps> = ({
strategyId,
strategyData
}) => {
const [metrics, setMetrics] = useState<PerformanceMetrics | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [lastRefresh, setLastRefresh] = useState<Date>(new Date());
useEffect(() => {
loadPerformanceMetrics();
}, [strategyId]);
const loadPerformanceMetrics = async () => {
try {
setLoading(true);
setError(null);
// Call the API to get performance metrics
const response = await strategyMonitoringApi.getPerformanceMetrics(strategyId);
setMetrics(response.data);
setLastRefresh(new Date());
} catch (err: any) {
setError(err.message || 'Failed to load performance metrics');
} finally {
setLoading(false);
}
};
const getMetricColor = (value: number, threshold: number = 0) => {
if (value >= threshold + 10) return '#4caf50'; // Green
if (value >= threshold) return '#ff9800'; // Orange
return '#f44336'; // Red
};
const getMetricIcon = (value: number, threshold: number = 0) => {
if (value >= threshold + 10) return <TrendingUpIcon />;
if (value >= threshold) return <TrendingDownIcon />;
return <ErrorIcon />;
};
const getMetricStatus = (value: number, threshold: number = 0) => {
if (value >= threshold + 10) return 'Excellent';
if (value >= threshold) return 'Good';
return 'Needs Attention';
};
const MetricCard = ({
title,
value,
unit = '%',
threshold = 0,
icon,
description
}: {
title: string;
value: number;
unit?: string;
threshold?: number;
icon: React.ReactNode;
description: string;
}) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Card sx={{
height: '100%',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(255,255,255,0.1) 0%, transparent 50%)',
pointerEvents: 'none'
}
}}>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 3 }}>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Box display="flex" alignItems="center" gap={1}>
{icon}
<Typography variant="h6" sx={{ fontWeight: 600 }}>
{title}
</Typography>
</Box>
<Chip
label={getMetricStatus(value, threshold)}
size="small"
sx={{
background: getMetricColor(value, threshold),
color: 'white',
fontWeight: 600
}}
/>
</Box>
<Typography variant="h3" sx={{
fontWeight: 700,
mb: 1,
color: getMetricColor(value, threshold)
}}>
{value}{unit}
</Typography>
<Typography variant="body2" sx={{
opacity: 0.9,
mb: 2
}}>
{description}
</Typography>
<LinearProgress
variant="determinate"
value={Math.min((value / (threshold + 20)) * 100, 100)}
sx={{
height: 6,
borderRadius: 3,
background: 'rgba(255,255,255,0.2)',
'& .MuiLinearProgress-bar': {
background: getMetricColor(value, threshold),
borderRadius: 3
}
}}
/>
</CardContent>
</Card>
</motion.div>
);
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<CircularProgress size={60} />
</Box>
);
}
if (error) {
return (
<Alert severity="error" sx={{ mb: 3 }}>
<AlertTitle>Error Loading Performance Data</AlertTitle>
{error}
</Alert>
);
}
if (!metrics) {
return (
<Alert severity="info" sx={{ mb: 3 }}>
<AlertTitle>No Performance Data Available</AlertTitle>
Performance metrics will appear here once your strategy is activated and monitoring begins.
</Alert>
);
}
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
{/* Header Section */}
<Box sx={{ mb: 4 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Box>
<Typography variant="h4" sx={{
fontWeight: 700,
background: 'linear-gradient(45deg, #667eea, #764ba2)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
mb: 1
}}>
🚀 Strategy Performance Dashboard
</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
Real-time monitoring and performance analytics for your content strategy
</Typography>
</Box>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Last updated: {new Date(metrics.last_updated).toLocaleString()}
</Typography>
<Tooltip title="Refresh metrics">
<IconButton onClick={loadPerformanceMetrics} sx={{ color: 'primary.main' }}>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
<Divider sx={{ mb: 3 }} />
</Box>
{/* Performance Metrics Grid */}
<Grid container spacing={3} sx={{ mb: 4 }}>
<Grid item xs={12} md={6} lg={3}>
<MetricCard
title="Traffic Growth"
value={metrics.traffic_growth_percentage}
unit="%"
threshold={5}
icon={<TrendingUpIcon />}
description="Organic traffic growth compared to previous period"
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<MetricCard
title="Engagement Rate"
value={metrics.engagement_rate_percentage}
unit="%"
threshold={5}
icon={<EngagementIcon />}
description="Average engagement rate across all content"
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<MetricCard
title="Conversion Rate"
value={metrics.conversion_rate_percentage}
unit="%"
threshold={1}
icon={<MonetizationOnIcon />}
description="Content-driven conversion rate"
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<MetricCard
title="ROI Ratio"
value={metrics.roi_ratio}
unit="x"
threshold={2}
icon={<SpeedIcon />}
description="Return on investment for content strategy"
/>
</Grid>
</Grid>
{/* Strategy Effectiveness Metrics */}
<Grid container spacing={3} sx={{ mb: 4 }}>
<Grid item xs={12} md={6}>
<Card sx={{ height: '100%', background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)', color: 'white' }}>
<CardContent sx={{ p: 3 }}>
<Box display="flex" alignItems="center" gap={2} mb={2}>
<AutoAwesomeIcon />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Strategy Adoption Rate
</Typography>
</Box>
<Typography variant="h2" sx={{ fontWeight: 700, mb: 2 }}>
{metrics.strategy_adoption_rate}%
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9, mb: 2 }}>
Percentage of strategy components successfully implemented and monitored
</Typography>
<LinearProgress
variant="determinate"
value={metrics.strategy_adoption_rate}
sx={{
height: 8,
borderRadius: 4,
background: 'rgba(255,255,255,0.2)',
'& .MuiLinearProgress-bar': {
background: 'white',
borderRadius: 4
}
}}
/>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card sx={{ height: '100%', background: 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)', color: 'white' }}>
<CardContent sx={{ p: 3 }}>
<Box display="flex" alignItems="center" gap={2} mb={2}>
<AssessmentIcon />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Content Quality Score
</Typography>
</Box>
<Typography variant="h2" sx={{ fontWeight: 700, mb: 2 }}>
{metrics.content_quality_score}/100
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9, mb: 2 }}>
AI-powered quality assessment of your content strategy
</Typography>
<LinearProgress
variant="determinate"
value={metrics.content_quality_score}
sx={{
height: 8,
borderRadius: 4,
background: 'rgba(255,255,255,0.2)',
'& .MuiLinearProgress-bar': {
background: 'white',
borderRadius: 4
}
}}
/>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Competitive Analysis */}
<Card sx={{ mb: 4, background: 'linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%)', color: 'white' }}>
<CardContent sx={{ p: 3 }}>
<Box display="flex" alignItems="center" gap={2} mb={3}>
<VisibilityIcon />
<Typography variant="h5" sx={{ fontWeight: 600 }}>
Competitive Position Analysis
</Typography>
</Box>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Box sx={{ mb: 2 }}>
<Typography variant="h6" sx={{ mb: 1 }}>
Market Rank
</Typography>
<Typography variant="h3" sx={{ fontWeight: 700 }}>
#{metrics.competitive_position_rank}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Your position among top competitors in the market
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ mb: 2 }}>
<Typography variant="h6" sx={{ mb: 1 }}>
Audience Growth
</Typography>
<Typography variant="h3" sx={{ fontWeight: 700 }}>
{metrics.audience_growth_percentage}%
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Monthly audience growth rate
</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
{/* Confidence Score */}
<Card sx={{ background: 'linear-gradient(135deg, #2196f3 0%, #1976d2 100%)', color: 'white' }}>
<CardContent sx={{ p: 3 }}>
<Box display="flex" alignItems="center" gap={2} mb={2}>
<CheckCircleIcon />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
AI Confidence Score
</Typography>
</Box>
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1 }}>
{metrics.confidence_score}%
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
AI confidence level in the accuracy of these performance metrics
</Typography>
</CardContent>
</Card>
</motion.div>
);
};
export default PerformanceVisualization;

View File

@@ -67,6 +67,15 @@ const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
const componentStatus = component?.status || 'not_reviewed';
const componentReviewedAt = component?.reviewedAt;
// Debug logging for component status
if (componentId) {
console.log(`🔧 ProgressiveCard [${componentId}]:`, {
componentStatus,
componentReviewedAt,
allComponents: components.map(c => ({ id: c.id, status: c.status }))
});
}
// Handle hover interactions
const handleMouseEnter = () => {
if (trigger === 'hover') {

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Box,
Typography,
@@ -16,27 +16,40 @@ import {
CheckCircle as CheckCircleIcon,
Schedule as ScheduleIcon,
Warning as WarningIcon,
PlayArrow as PlayArrowIcon
PlayArrow as PlayArrowIcon,
AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { useStrategyReviewStore } from '../../../../../stores/strategyReviewStore';
import { useStrategyReviewStore, StrategyComponent } from '../../../../../stores/strategyReviewStore';
import { ANALYSIS_CARD_STYLES } from '../styles';
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
import EnhancedStrategyActivationButton from './EnhancedStrategyActivationButton';
import { StrategyData } from '../types/strategy.types';
import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
const ReviewProgressHeader: React.FC = () => {
interface ReviewProgressHeaderProps {
strategyData?: StrategyData | null;
}
const ReviewProgressHeader: React.FC<ReviewProgressHeaderProps> = ({ strategyData }) => {
const {
components,
reviewProgress,
isAllReviewed,
isActivated,
resetAllReviews,
getUnreviewedComponents,
getReviewedComponents
getReviewedComponents,
activateStrategy
} = useStrategyReviewStore();
// Extract domain name from strategy data (you can pass this as prop if needed)
// Initialize navigation orchestrator
const navigationOrchestrator = useNavigationOrchestrator();
// Extract domain name from strategy data
const getDomainName = () => {
// For now, return a default domain - you can enhance this to get from strategy data
return "alwrity.com";
// Since StrategyMetadata doesn't have domain, we'll use a fallback
return "alwrity.com"; // fallback
};
const unreviewedCount = getUnreviewedComponents().length;
@@ -50,10 +63,13 @@ const ReviewProgressHeader: React.FC = () => {
unreviewedCount,
reviewedCount,
totalCount,
isAllReviewed: isAllReviewed()
isAllReviewed: isAllReviewed(),
isActivated: isActivated(),
strategyData
});
const getProgressColor = () => {
if (isActivated()) return ANALYSIS_CARD_STYLES.colors.success;
if (reviewProgress === 100) return ANALYSIS_CARD_STYLES.colors.success;
if (reviewProgress >= 60) return ANALYSIS_CARD_STYLES.colors.primary;
if (reviewProgress >= 30) return ANALYSIS_CARD_STYLES.colors.warning;
@@ -61,12 +77,55 @@ const ReviewProgressHeader: React.FC = () => {
};
const getProgressText = () => {
if (isActivated()) return 'Strategy Active & Monitored!';
if (reviewProgress === 100) return 'All components reviewed!';
if (reviewProgress >= 60) return 'Great progress!';
if (reviewProgress >= 30) return 'Making good progress';
return 'Getting started';
};
// Prepare strategy data for the enhanced button
const buttonStrategyData = strategyData ? {
id: strategyData.strategy_metadata?.user_id || strategyData.metadata?.user_id || 1,
business_name: strategyData.strategy_metadata?.strategy_name || strategyData.metadata?.strategy_name || "ALwrity",
domain: getDomainName(),
// Add other strategy data as needed
} : {
id: 1,
business_name: "ALwrity",
domain: getDomainName(),
};
const handleConfirmStrategy = async () => {
// This will be called by the enhanced button when activation is confirmed
console.log('🎯 Strategy activation confirmed');
// Activate the strategy in the store
activateStrategy();
// You can add additional logic here if needed
};
const handleGenerateContentCalendar = () => {
console.log('🎯 Generate content calendar clicked');
// Prepare strategy context for navigation
const strategyContext = {
strategyId: (strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1').toString(),
strategyData: strategyData,
activationStatus: 'active' as const,
activationTimestamp: new Date().toISOString(),
userPreferences: {},
strategicIntelligence: {}
};
// Navigate to calendar wizard using navigation orchestrator
navigationOrchestrator.navigateToCalendarWizard(
strategyContext.strategyId,
strategyContext
);
};
return (
<motion.div
initial={{ opacity: 0, y: -20 }}
@@ -102,291 +161,99 @@ const ReviewProgressHeader: React.FC = () => {
bottom: 0,
background: 'linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%)',
animation: 'shimmer 3s infinite',
pointerEvents: 'none'
},
'@keyframes shimmer': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' }
'@keyframes shimmer': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' }
}
}
}}
>
{/* Animated Border Lights */}
<motion.div
animate={{
boxShadow: [
'0 0 20px rgba(102, 126, 234, 0.5)',
'0 0 40px rgba(102, 126, 234, 0.8)',
'0 0 20px rgba(102, 126, 234, 0.5)'
]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
borderRadius: '12px',
pointerEvents: 'none'
}}
/>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 3 }}>
{/* Header Section */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Box>
<Typography variant="h5" sx={{
fontWeight: 700,
background: 'linear-gradient(45deg, #667eea, #764ba2)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
mb: 0.5
}}>
Strategy Review Progress
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 500 }}>
Review all strategy components to activate your content strategy
</Typography>
</Box>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 2 }}>
{/* Header with Circular Progress and Status Chips */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{/* Circular Progress */}
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
{/* Progress Circle */}
<Box sx={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 2 }}>
<Box sx={{ position: 'relative' }}>
<CircularProgress
variant="determinate"
value={reviewProgress}
size={50}
size={60}
thickness={4}
sx={{
color: getProgressColor(),
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
filter: 'drop-shadow(0 0 8px rgba(102, 126, 234, 0.5))'
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
textAlign: 'center'
}}
>
<Typography variant="caption" sx={{
color: getProgressColor(),
fontWeight: 700,
fontSize: '0.7rem'
fontSize: '0.7rem',
fontWeight: 700,
color: 'white',
lineHeight: 1
}}>
{Math.round(reviewProgress)}%
{reviewProgress}%
</Typography>
</Box>
</Box>
{/* Progress Text */}
<Box>
<Typography variant="h6" sx={{
color: 'white',
fontWeight: 600,
mb: 0.25
}}>
Strategy Review Progress
</Typography>
<Typography variant="body2" sx={{
color: 'rgba(255, 255, 255, 0.8)',
fontWeight: 600,
color: getProgressColor(),
fontSize: '0.8rem'
}}>
{getProgressText()}
</Typography>
</Box>
{/* Status Chips */}
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
icon={<CheckCircleIcon />}
label={`${reviewedCount} Reviewed`}
size="small"
sx={{
background: ANALYSIS_CARD_STYLES.colors.success,
color: 'white',
fontWeight: 500,
fontSize: '0.65rem',
height: 24,
'& .MuiChip-icon': {
color: 'white',
fontSize: 14
}
}}
/>
{unreviewedCount > 0 && (
<Chip
icon={<ScheduleIcon />}
label={`${unreviewedCount} Pending`}
size="small"
sx={{
background: ANALYSIS_CARD_STYLES.colors.warning,
color: 'white',
fontWeight: 500,
fontSize: '0.65rem',
height: 24,
'& .MuiChip-icon': {
color: 'white',
fontSize: 14
}
}}
/>
)}
<Typography variant="caption" sx={{
color: 'rgba(255, 255, 255, 0.7)',
fontSize: '0.7rem'
}}>
{reviewedCount} of {totalCount} reviewed
</Typography>
</Box>
</Box>
{/* Reset Button */}
<Tooltip title="Reset all reviews">
<IconButton
onClick={resetAllReviews}
size="small"
sx={{
color: 'rgba(255, 255, 255, 0.8)',
'&:hover': {
color: '#f44336',
background: 'rgba(244, 67, 54, 0.2)'
}
}}
>
<RefreshIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
{/* Progress Summary */}
<Box sx={{ mb: 1.5 }}>
{/* Component Status */}
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{
color: 'white',
fontWeight: 500,
fontSize: '0.8rem'
fontWeight: 600,
mb: 1,
color: 'rgba(255, 255, 255, 0.9)'
}}>
{reviewedCount} of {totalCount} components reviewed
Component Status:
</Typography>
</Box>
{/* Informative Text */}
<Box sx={{ mb: 1.5 }}>
<Typography variant="body2" sx={{
color: 'rgba(255, 255, 255, 0.9)',
fontSize: '0.8rem',
lineHeight: 1.4,
background: 'rgba(255, 255, 255, 0.05)',
p: 1.5,
borderRadius: 1,
border: '1px solid rgba(255, 255, 255, 0.1)'
}}>
<strong>Complete review by clicking 'Not Reviewed' button and confirming datapoints of 5 analysis components below.</strong>
<br />
<span style={{ color: 'rgba(255, 255, 255, 0.7)' }}>
Important: Content strategy for <strong>{getDomainName()}</strong> will shape content generation next.
</span>
</Typography>
</Box>
{/* Individual Component Status and Activate Strategy Button */}
<Box sx={{ mb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.75 }}>
<Typography variant="caption" sx={{
color: 'rgba(255, 255, 255, 0.8)',
fontWeight: 500,
fontSize: '0.7rem'
}}>
Component Status
</Typography>
{/* Confirm & Activate Strategy Button */}
<Tooltip
title={isAllReviewed() ? "Confirm strategy and activate content generation" : "Complete all component reviews to confirm and activate strategy"}
arrow
>
<Button
variant="contained"
size="small"
disabled={!isAllReviewed()}
startIcon={<PlayArrowIcon />}
onClick={async () => {
if (isAllReviewed()) {
try {
// Handle strategy confirmation and activation
console.log('Confirming and activating strategy...');
// 1. Save the strategy confirmation to backend
// Note: You'll need to get the actual strategy ID from context/props
const strategyId = "current_strategy_id"; // Replace with actual strategy ID
try {
await contentPlanningApi.updateEnhancedStrategy(
strategyId,
{
confirmed: true,
confirmed_at: new Date().toISOString(),
review_completed: true,
review_completed_at: new Date().toISOString()
}
);
console.log('Strategy confirmation saved to backend');
} catch (updateError) {
console.warn('Could not save confirmation to backend:', updateError);
}
// 2. Show success message
alert('Strategy confirmed and activated! You can now proceed to create your content calendar.');
// 3. Navigate to content calendar creation
// You can add navigation logic here
// navigate('/content-calendar');
} catch (error) {
console.error('Error confirming and activating strategy:', error);
alert('Error confirming strategy. Please try again.');
}
}
}}
sx={{
background: isAllReviewed()
? 'linear-gradient(135deg, #4caf50 0%, #66bb6a 100%)'
: 'rgba(255, 255, 255, 0.1)',
color: isAllReviewed() ? 'white' : 'rgba(255, 255, 255, 0.5)',
fontWeight: 600,
fontSize: '0.7rem',
px: 2,
py: 0.5,
borderRadius: 2,
boxShadow: isAllReviewed()
? '0 2px 8px rgba(76, 175, 80, 0.3)'
: 'none',
border: isAllReviewed()
? '1px solid rgba(76, 175, 80, 0.4)'
: '1px solid rgba(255, 255, 255, 0.2)',
textTransform: 'none',
minWidth: 'auto',
'&:hover': {
background: isAllReviewed()
? 'linear-gradient(135deg, #66bb6a 0%, #81c784 100%)'
: 'rgba(255, 255, 255, 0.1)',
boxShadow: isAllReviewed()
? '0 4px 12px rgba(76, 175, 80, 0.4)'
: 'none',
transform: isAllReviewed() ? 'translateY(-1px)' : 'none'
},
'&:active': {
transform: isAllReviewed() ? 'translateY(0)' : 'none'
},
'&:disabled': {
background: 'rgba(255, 255, 255, 0.05)',
color: 'rgba(255, 255, 255, 0.3)',
boxShadow: 'none',
transform: 'none'
},
'& .MuiButton-startIcon': {
marginRight: 0.5
}
}}
>
Confirm & Activate Strategy
</Button>
</Tooltip>
</Box>
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
{components.map((component) => (
<Tooltip
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{components.map((component: StrategyComponent) => (
<Tooltip
key={component.id}
title={`${component.title}: ${component.status === 'reviewed' ? 'Reviewed' : 'Pending Review'}`}
arrow
@@ -433,7 +300,7 @@ const ReviewProgressHeader: React.FC = () => {
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip
icon={<CheckCircleIcon />}
label="Ready for Calendar Creation"
label={isActivated() ? "Strategy Active & Monitored" : "Ready for Calendar Creation"}
size="small"
sx={{
background: ANALYSIS_CARD_STYLES.colors.success,
@@ -466,7 +333,7 @@ const ReviewProgressHeader: React.FC = () => {
mt: 1.5,
p: 1.5,
borderRadius: 1,
background: 'rgba(76, 175, 80, 0.1)',
background: isActivated() ? 'rgba(76, 175, 80, 0.15)' : 'rgba(76, 175, 80, 0.1)',
border: '1px solid rgba(76, 175, 80, 0.2)',
textAlign: 'center'
}}>
@@ -475,11 +342,88 @@ const ReviewProgressHeader: React.FC = () => {
fontWeight: 600,
fontSize: '0.8rem'
}}>
🎉 All strategy components have been reviewed! You can now proceed to create your content calendar.
{isActivated()
? '🎉 Your content strategy is now active and being monitored! AI-powered insights and performance tracking are now live.'
: '🎉 All strategy components have been reviewed! You can now proceed to create your content calendar.'
}
</Typography>
</Box>
</motion.div>
)}
{/* Enhanced Strategy Activation Button - Only shown when all components are reviewed and not yet activated */}
{isAllReviewed() && !isActivated() && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'center' }}>
<EnhancedStrategyActivationButton
strategyData={buttonStrategyData}
strategyConfirmed={false}
onConfirmStrategy={handleConfirmStrategy}
onGenerateContentCalendar={handleGenerateContentCalendar}
disabled={false}
/>
</Box>
</motion.div>
)}
{/* Strategy Activated Success Message */}
{isActivated() && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Box sx={{
mt: 3,
p: 3,
borderRadius: 2,
background: 'linear-gradient(135deg, rgba(76, 175, 80, 0.1) 0%, rgba(76, 175, 80, 0.05) 100%)',
border: '2px solid rgba(76, 175, 80, 0.3)',
textAlign: 'center'
}}>
<Typography variant="h6" sx={{
color: ANALYSIS_CARD_STYLES.colors.success,
fontWeight: 700,
mb: 1
}}>
🚀 Strategy Successfully Activated!
</Typography>
<Typography variant="body2" sx={{
color: 'text.secondary',
mb: 2
}}>
Your content strategy is now live and being monitored with AI-powered analytics.
</Typography>
<Button
variant="contained"
size="large"
onClick={handleGenerateContentCalendar}
startIcon={<AutoAwesomeIcon />}
sx={{
background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)',
borderRadius: 3,
px: 4,
py: 1.5,
fontWeight: 600,
textTransform: 'none',
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
'&:hover': {
background: 'linear-gradient(135deg, #45a049 0%, #3d8b40 100%)',
boxShadow: '0 12px 40px rgba(76, 175, 80, 0.4)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s ease'
}}
>
Generate Content Calendar
</Button>
</Box>
</motion.div>
)}
</CardContent>
</Card>
</motion.div>

View File

@@ -12,7 +12,8 @@ import {
Schedule as ScheduleIcon,
Warning as WarningIcon,
Edit as EditIcon,
Undo as UndoIcon
Undo as UndoIcon,
PlayArrow as PlayArrowIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { ReviewStatus } from '../../../../../stores/strategyReviewStore';
@@ -35,8 +36,19 @@ const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
onResetReview,
isReviewing = false
}) => {
// Debug logging for status
console.log('🔧 ReviewStatusIndicator received status:', status);
const getStatusConfig = () => {
switch (status) {
case 'activated':
return {
icon: <PlayArrowIcon />,
label: 'Activated',
color: ANALYSIS_CARD_STYLES.colors.primary,
bgColor: 'rgba(102, 126, 234, 0.1)',
borderColor: 'rgba(102, 126, 234, 0.3)',
textColor: ANALYSIS_CARD_STYLES.colors.primary
};
case 'reviewed':
return {
icon: <CheckCircleIcon />,
@@ -101,6 +113,7 @@ const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
} else if (status === 'reviewed' && onResetReview) {
onResetReview();
}
// Activated status is read-only, no action needed
}}
>
{/* Status Icon */}
@@ -126,7 +139,7 @@ const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
</Typography>
{/* Review Date */}
{status === 'reviewed' && reviewedAt && (
{(status === 'reviewed' || status === 'activated') && reviewedAt && (
<Typography
variant="caption"
sx={{
@@ -141,7 +154,10 @@ const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
{/* Action Buttons - Enhanced for better UX */}
<Box sx={{ display: 'flex', gap: 0.5 }}>
{status === 'not_reviewed' && onStartReview && (
{/* Only show action buttons for non-activated states */}
{status !== 'activated' && (
<>
{status === 'not_reviewed' && onStartReview && (
<Tooltip title="Review Component">
<Button
variant="contained"
@@ -189,51 +205,53 @@ const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
</Tooltip>
)}
{status === 'reviewed' && onResetReview && (
<Tooltip title="Reset Review">
<Button
variant="outlined"
size="small"
onClick={(e) => {
e.stopPropagation();
onResetReview();
}}
disabled={isReviewing}
startIcon={<UndoIcon />}
sx={{
color: ANALYSIS_CARD_STYLES.colors.warning,
borderColor: 'rgba(255, 152, 0, 0.5)',
fontWeight: 600,
fontSize: '0.7rem',
px: 1.5,
py: 0.5,
borderRadius: 2,
textTransform: 'none',
minWidth: 'auto',
background: 'rgba(255, 152, 0, 0.05)',
'&:hover': {
background: 'rgba(255, 152, 0, 0.1)',
borderColor: 'rgba(255, 152, 0, 0.7)',
transform: 'translateY(-1px)',
boxShadow: '0 2px 8px rgba(255, 152, 0, 0.2)'
},
'&:active': {
transform: 'translateY(0)'
},
'&:disabled': {
color: 'rgba(255, 152, 0, 0.4)',
borderColor: 'rgba(255, 152, 0, 0.2)',
background: 'rgba(255, 152, 0, 0.02)',
transform: 'none'
},
'& .MuiButton-startIcon': {
marginRight: 0.5
}
}}
>
Reset
</Button>
</Tooltip>
{status === 'reviewed' && onResetReview && (
<Tooltip title="Reset Review">
<Button
variant="outlined"
size="small"
onClick={(e) => {
e.stopPropagation();
onResetReview();
}}
disabled={isReviewing}
startIcon={<UndoIcon />}
sx={{
color: ANALYSIS_CARD_STYLES.colors.warning,
borderColor: 'rgba(255, 152, 0, 0.5)',
fontWeight: 600,
fontSize: '0.7rem',
px: 1.5,
py: 0.5,
borderRadius: 2,
textTransform: 'none',
minWidth: 'auto',
background: 'rgba(255, 152, 0, 0.05)',
'&:hover': {
background: 'rgba(255, 152, 0, 0.1)',
borderColor: 'rgba(255, 152, 0, 0.7)',
transform: 'translateY(-1px)',
boxShadow: '0 2px 8px rgba(255, 152, 0, 0.2)'
},
'&:active': {
transform: 'translateY(0)'
},
'&:disabled': {
color: 'rgba(255, 152, 0, 0.4)',
borderColor: 'rgba(255, 152, 0, 0.2)',
background: 'rgba(255, 152, 0, 0.02)',
transform: 'none'
},
'& .MuiButton-startIcon': {
marginRight: 0.5
}
}}
>
Reset
</Button>
</Tooltip>
)}
</>
)}
</Box>
</Box>

View File

@@ -11,6 +11,7 @@ import {
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { StrategyData } from '../types/strategy.types';
import EnhancedStrategyActivationButton from './EnhancedStrategyActivationButton';
interface StrategyActionsProps {
strategyData: StrategyData | null;
@@ -27,6 +28,16 @@ const StrategyActions: React.FC<StrategyActionsProps> = ({
onGenerateContentCalendar,
onRefreshData
}) => {
// Convert onConfirmStrategy to async function for the enhanced button
const handleConfirmStrategy = async () => {
try {
await onConfirmStrategy();
} catch (error) {
console.error('Strategy confirmation failed:', error);
throw error; // Re-throw to let the enhanced button handle the error
}
};
return (
<Box sx={{ mt: 4 }}>
{/* Strategy Confirmation Status */}
@@ -68,65 +79,19 @@ const StrategyActions: React.FC<StrategyActionsProps> = ({
</motion.div>
)}
{/* Enhanced Strategy Activation Button */}
<Box sx={{ mb: 3 }}>
<EnhancedStrategyActivationButton
strategyData={strategyData}
strategyConfirmed={strategyConfirmed}
onConfirmStrategy={handleConfirmStrategy}
onGenerateContentCalendar={onGenerateContentCalendar}
disabled={false}
/>
</Box>
{/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
{!strategyConfirmed ? (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant="contained"
size="large"
onClick={onConfirmStrategy}
startIcon={<CheckIcon />}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 3,
px: 4,
py: 1.5,
fontWeight: 600,
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3)',
'&:hover': {
background: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)',
boxShadow: '0 12px 40px rgba(102, 126, 234, 0.4)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
Confirm Strategy
</Button>
</motion.div>
) : (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant="contained"
size="large"
onClick={onGenerateContentCalendar}
startIcon={<CalendarIcon />}
color="success"
sx={{
borderRadius: 3,
px: 4,
py: 1.5,
fontWeight: 600,
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
'&:hover': {
boxShadow: '0 12px 40px rgba(76, 175, 80, 0.4)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
Generate Content Calendar
</Button>
</motion.div>
)}
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}

View File

@@ -0,0 +1,350 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Select,
MenuItem,
FormControl,
InputLabel,
Chip,
IconButton,
Tooltip,
Alert,
AlertTitle
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Timeline as TimelineIcon,
CalendarToday as CalendarIcon,
Refresh as RefreshIcon,
AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { strategyMonitoringApi } from '../../../../../services/strategyMonitoringApi';
interface TrendData {
date: string;
traffic_growth: number;
engagement_rate: number;
conversion_rate: number;
content_quality_score: number;
strategy_adoption_rate: number;
}
interface TrendAnalysisProps {
strategyId: number;
strategyData?: any;
}
const TrendAnalysis: React.FC<TrendAnalysisProps> = ({
strategyId,
strategyData
}) => {
const [selectedMetric, setSelectedMetric] = useState<string>('traffic_growth');
const [timeRange, setTimeRange] = useState<string>('30d');
const [trendData, setTrendData] = useState<TrendData[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadTrendData();
}, [strategyId, timeRange]);
const loadTrendData = async () => {
try {
setLoading(true);
// Call the API to get trend data
const response = await strategyMonitoringApi.getTrendData(strategyId, timeRange);
setTrendData(response.data);
} catch (error) {
console.error('Error loading trend data:', error);
} finally {
setLoading(false);
}
};
const getMetricInfo = (metric: string) => {
const metricInfo = {
traffic_growth: { label: 'Traffic Growth', unit: '%', color: '#4caf50', icon: <TrendingUpIcon /> },
engagement_rate: { label: 'Engagement Rate', unit: '%', color: '#2196f3', icon: <TimelineIcon /> },
conversion_rate: { label: 'Conversion Rate', unit: '%', color: '#ff9800', icon: <AutoAwesomeIcon /> },
content_quality_score: { label: 'Content Quality Score', unit: '/100', color: '#9c27b0', icon: <AutoAwesomeIcon /> },
strategy_adoption_rate: { label: 'Strategy Adoption Rate', unit: '%', color: '#4caf50', icon: <TrendingUpIcon /> }
};
return metricInfo[metric as keyof typeof metricInfo] || metricInfo.traffic_growth;
};
const calculateTrend = (data: TrendData[], metric: string) => {
if (data.length < 2) return { direction: 'stable', percentage: 0 };
const values = data.map(d => d[metric as keyof TrendData] as number);
const firstValue = values[0];
const lastValue = values[values.length - 1];
const change = ((lastValue - firstValue) / firstValue) * 100;
return {
direction: change > 0 ? 'up' : change < 0 ? 'down' : 'stable',
percentage: Math.abs(change)
};
};
const renderTrendChart = (data: TrendData[], metric: string) => {
const metricInfo = getMetricInfo(metric);
const values = data.map(d => d[metric as keyof TrendData] as number);
const maxValue = Math.max(...values);
const minValue = Math.min(...values);
const range = maxValue - minValue;
return (
<Box sx={{ mt: 2, height: 200, position: 'relative' }}>
<svg width="100%" height="100%" viewBox="0 0 400 200">
{/* Grid lines */}
{[0, 25, 50, 75, 100].map(y => (
<line
key={y}
x1="0"
y1={200 - (y * 200 / 100)}
x2="400"
y2={200 - (y * 200 / 100)}
stroke="rgba(255,255,255,0.1)"
strokeWidth="1"
/>
))}
{/* Trend line */}
<polyline
points={data.map((d, i) => {
const value = d[metric as keyof TrendData] as number;
const x = (i / (data.length - 1)) * 400;
const y = 200 - ((value - minValue) / range) * 180;
return `${x},${y}`;
}).join(' ')}
fill="none"
stroke={metricInfo.color}
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
{/* Data points */}
{data.map((d, i) => {
const value = d[metric as keyof TrendData] as number;
const x = (i / (data.length - 1)) * 400;
const y = 200 - ((value - minValue) / range) * 180;
return (
<circle
key={i}
cx={x}
cy={y}
r="4"
fill={metricInfo.color}
stroke="white"
strokeWidth="2"
/>
);
})}
</svg>
{/* Value labels */}
<Box sx={{ position: 'absolute', top: 0, right: 0, textAlign: 'right' }}>
<Typography variant="h6" sx={{ color: metricInfo.color, fontWeight: 700 }}>
{values[values.length - 1]}{metricInfo.unit}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
Current
</Typography>
</Box>
</Box>
);
};
const trend = calculateTrend(trendData, selectedMetric);
const metricInfo = getMetricInfo(selectedMetric);
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography>Loading trend analysis...</Typography>
</Box>
);
}
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
<Card sx={{
background: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%)',
color: 'white',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%)',
pointerEvents: 'none'
}
}}>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 3 }}>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h5" sx={{
fontWeight: 700,
background: 'linear-gradient(45deg, #667eea, #764ba2)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
mb: 1
}}>
📈 Performance Trend Analysis
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
Track your strategy performance over time with AI-powered insights
</Typography>
</Box>
<Tooltip title="Refresh data">
<IconButton onClick={loadTrendData} sx={{ color: 'white' }}>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
{/* Controls */}
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<FormControl fullWidth sx={{
'& .MuiOutlinedInput-root': {
color: 'white',
'& fieldset': { borderColor: 'rgba(255,255,255,0.3)' },
'&:hover fieldset': { borderColor: 'rgba(255,255,255,0.5)' },
'&.Mui-focused fieldset': { borderColor: '#667eea' }
},
'& .MuiInputLabel-root': { color: 'rgba(255,255,255,0.7)' },
'& .MuiSelect-icon': { color: 'white' }
}}>
<InputLabel>Metric</InputLabel>
<Select
value={selectedMetric}
onChange={(e) => setSelectedMetric(e.target.value)}
label="Metric"
>
<MenuItem value="traffic_growth">Traffic Growth</MenuItem>
<MenuItem value="engagement_rate">Engagement Rate</MenuItem>
<MenuItem value="conversion_rate">Conversion Rate</MenuItem>
<MenuItem value="content_quality_score">Content Quality Score</MenuItem>
<MenuItem value="strategy_adoption_rate">Strategy Adoption Rate</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl fullWidth sx={{
'& .MuiOutlinedInput-root': {
color: 'white',
'& fieldset': { borderColor: 'rgba(255,255,255,0.3)' },
'&:hover fieldset': { borderColor: 'rgba(255,255,255,0.5)' },
'&.Mui-focused fieldset': { borderColor: '#667eea' }
},
'& .MuiInputLabel-root': { color: 'rgba(255,255,255,0.7)' },
'& .MuiSelect-icon': { color: 'white' }
}}>
<InputLabel>Time Range</InputLabel>
<Select
value={timeRange}
onChange={(e) => setTimeRange(e.target.value)}
label="Time Range"
>
<MenuItem value="7d">Last 7 days</MenuItem>
<MenuItem value="30d">Last 30 days</MenuItem>
<MenuItem value="90d">Last 90 days</MenuItem>
<MenuItem value="1y">Last year</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
{/* Trend Summary */}
<Box sx={{ mb: 3, p: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 2 }}>
<Box display="flex" alignItems="center" gap={2} mb={2}>
{metricInfo.icon}
<Typography variant="h6" sx={{ fontWeight: 600 }}>
{metricInfo.label} Trend
</Typography>
<Chip
label={`${trend.direction === 'up' ? '+' : trend.direction === 'down' ? '-' : ''}${trend.percentage.toFixed(1)}%`}
size="small"
sx={{
background: trend.direction === 'up' ? '#4caf50' : trend.direction === 'down' ? '#f44336' : '#ff9800',
color: 'white',
fontWeight: 600
}}
/>
</Box>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
{trend.direction === 'up'
? `Your ${metricInfo.label.toLowerCase()} has improved by ${trend.percentage.toFixed(1)}% over this period.`
: trend.direction === 'down'
? `Your ${metricInfo.label.toLowerCase()} has decreased by ${trend.percentage.toFixed(1)}% over this period.`
: `Your ${metricInfo.label.toLowerCase()} has remained stable over this period.`
}
</Typography>
</Box>
{/* Chart */}
<Box sx={{
p: 3,
background: 'rgba(255,255,255,0.05)',
borderRadius: 2,
border: '1px solid rgba(255,255,255,0.1)'
}}>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
{metricInfo.label} Over Time
</Typography>
{renderTrendChart(trendData, selectedMetric)}
</Box>
{/* Data Points */}
<Box sx={{ mt: 3 }}>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
Recent Data Points
</Typography>
<Grid container spacing={2}>
{trendData.slice(-4).map((data, index) => (
<Grid item xs={12} sm={6} md={3} key={index}>
<Box sx={{
p: 2,
background: 'rgba(255,255,255,0.1)',
borderRadius: 1,
textAlign: 'center'
}}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)', mb: 1 }}>
{new Date(data.date).toLocaleDateString()}
</Typography>
<Typography variant="h6" sx={{ fontWeight: 700, color: metricInfo.color }}>
{(data[selectedMetric as keyof TrendData] as number).toFixed(1)}{metricInfo.unit}
</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
</CardContent>
</Card>
</motion.div>
);
};
export default TrendAnalysis;

View File

@@ -1,20 +1,28 @@
import { useState } from 'react';
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
import { StrategyData } from '../types/strategy.types';
import { useStrategyReviewStore } from '../../../../../stores/strategyReviewStore';
export const useStrategyActions = () => {
const [strategyConfirmed, setStrategyConfirmed] = useState(false);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [isActivating, setIsActivating] = useState(false);
// Get the activateStrategy method from the review store
const { activateStrategy } = useStrategyReviewStore();
const handleConfirmStrategy = () => {
setShowConfirmDialog(true);
};
const confirmStrategy = async (strategyData: StrategyData | null) => {
const confirmStrategy = async (strategyData: StrategyData | null): Promise<void> => {
if (isActivating) {
throw new Error('Strategy activation already in progress');
}
setIsActivating(true);
try {
setStrategyConfirmed(true);
setShowConfirmDialog(false);
// Save confirmation status to backend
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id;
if (userId) {
@@ -31,10 +39,20 @@ export const useStrategyActions = () => {
}
}
console.log('Strategy confirmed! Ready to generate content calendar.');
// Set local state
setStrategyConfirmed(true);
setShowConfirmDialog(false);
// Activate strategy in the review store
activateStrategy();
console.log('Strategy confirmed and activated! Ready to generate content calendar.');
} catch (error) {
console.error('Error confirming strategy:', error);
setStrategyConfirmed(false);
throw error; // Re-throw to let the calling component handle the error
} finally {
setIsActivating(false);
}
};
@@ -46,30 +64,22 @@ export const useStrategyActions = () => {
return;
}
// Generate content calendar based on confirmed strategy
const calendarRequest = {
user_id: userId,
strategy_id: userId, // Using user_id as strategy_id for now
calendar_type: 'comprehensive',
industry: strategyData.base_strategy?.industry || 'technology',
business_size: 'medium', // TODO: Get from strategy data
force_refresh: false
};
console.log('🎯 Strategy Actions: Generating content calendar for strategy:', strategyData);
console.log('Generating content calendar with request:', calendarRequest);
// For now, we'll just log that the function was called
// The actual navigation is handled by the ReviewProgressHeader component
// which uses the NavigationOrchestrator to navigate to the calendar wizard
// Call the calendar generation API
const calendarResponse = await contentPlanningApi.generateCalendar(calendarRequest);
console.log('🎯 Strategy Actions: Calendar generation request prepared');
console.log('Content calendar generated successfully:', calendarResponse);
// TODO: Navigate to calendar tab or show success message
// You could also store the calendar data in a global state
// TODO: In the future, this could be enhanced to:
// 1. Call the calendar generation API directly
// 2. Store the generated calendar data
// 3. Navigate to the calendar view with the generated data
} catch (error) {
console.error('Error generating content calendar:', error);
// Show error message to user
throw new Error('Failed to generate content calendar. Please try again.');
console.error('Error in handleGenerateContentCalendar:', error);
throw new Error('Failed to prepare calendar generation. Please try again.');
}
};
@@ -77,6 +87,7 @@ export const useStrategyActions = () => {
strategyConfirmed,
showConfirmDialog,
setShowConfirmDialog,
isActivating,
handleConfirmStrategy,
confirmStrategy,
handleGenerateContentCalendar

View File

@@ -10,21 +10,52 @@ import {
Divider,
Alert,
CircularProgress,
LinearProgress
LinearProgress,
Tabs,
Tab
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
Analytics as AnalyticsIcon,
ShowChart as ShowChartIcon,
Assessment as AssessmentIcon
Assessment as AssessmentIcon,
Visibility as VisibilityIcon,
Timeline as TimelineIcon,
AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import EnhancedPerformanceVisualization from '../components/StrategyIntelligence/components/EnhancedPerformanceVisualization';
import TrendAnalysis from '../components/StrategyIntelligence/components/TrendAnalysis';
import DataTransparencyPanel from '../components/StrategyIntelligence/components/DataTransparencyPanel';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`analytics-tabpanel-${index}`}
aria-labelledby={`analytics-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ pt: 3 }}>{children}</Box>}
</div>
);
}
const AnalyticsTab: React.FC = () => {
const {
performanceMetrics,
aiInsights,
currentStrategy,
loading,
error,
loadAIInsights,
@@ -33,6 +64,7 @@ const AnalyticsTab: React.FC = () => {
const [analyticsData, setAnalyticsData] = useState<any>(null);
const [dataLoading, setDataLoading] = useState(false);
const [activeTab, setActiveTab] = useState(0);
useEffect(() => {
loadAnalyticsData();
@@ -74,16 +106,23 @@ const AnalyticsTab: React.FC = () => {
}
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
};
const getPerformanceColor = (value: number) => {
if (value >= 80) return 'success';
if (value >= 60) return 'warning';
return 'error';
};
// Get strategy ID for performance components
const strategyId = Number(currentStrategy?.id) || currentStrategy?.user_id || 1;
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Performance Analytics
Analytics Dashboard
</Typography>
{error && (
@@ -92,297 +131,351 @@ const AnalyticsTab: React.FC = () => {
</Alert>
)}
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
{/* Tabs Navigation */}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs
value={activeTab}
onChange={handleTabChange}
sx={{
'& .MuiTab-root': {
color: 'text.secondary',
fontWeight: 600,
textTransform: 'none',
fontSize: '1rem',
minHeight: 48,
'&.Mui-selected': {
color: 'primary.main',
fontWeight: 700
}
},
'& .MuiTabs-indicator': {
height: 3,
borderRadius: '3px 3px 0 0'
}
}}
>
<Tab label="Performance Analytics" icon={<ShowChartIcon />} iconPosition="start" />
<Tab label="Content Analytics" icon={<AnalyticsIcon />} iconPosition="start" />
<Tab label="Data Transparency" icon={<VisibilityIcon />} iconPosition="start" />
</Tabs>
</Box>
{/* Performance Analytics Tab */}
<TabPanel value={activeTab} index={0}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<EnhancedPerformanceVisualization
strategyId={strategyId}
strategyData={currentStrategy}
/>
<TrendAnalysis
strategyId={strategyId}
strategyData={currentStrategy}
/>
</Box>
) : (
<Grid container spacing={3}>
{/* Performance Overview */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Performance Overview
</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="h4" color={getPerformanceColor(performanceMetrics.engagement)}>
{performanceMetrics.engagement}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Reach
</Typography>
<Typography variant="h4" color="primary">
{performanceMetrics.reach.toLocaleString()}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Conversion Rate
</Typography>
<Typography variant="h4" color={getPerformanceColor(performanceMetrics.conversion)}>
{performanceMetrics.conversion}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
ROI
</Typography>
<Typography variant="h4" color="success.main">
${performanceMetrics.roi.toLocaleString()}
</Typography>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary">
No performance data available
</Typography>
)}
</Paper>
</Grid>
</TabPanel>
{/* AI Insights */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
AI Insights
</Typography>
<Divider sx={{ mb: 2 }} />
{aiInsights && aiInsights.length > 0 ? (
<Box>
{aiInsights.slice(0, 3).map((insight, index) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
{insight.title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{insight.description}
</Typography>
<Chip
label={insight.priority}
color={insight.priority === 'high' ? 'error' : insight.priority === 'medium' ? 'warning' : 'success'}
size="small"
/>
</Box>
))}
</Box>
) : (
<Typography variant="body2" color="text.secondary">
No AI insights available
</Typography>
)}
</Paper>
</Grid>
{/* Content Evolution */}
{analyticsData && analyticsData.content_evolution && (
{/* Content Analytics Tab */}
<TabPanel value={activeTab} index={1}>
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : (
<Grid container spacing={3}>
{/* Performance Overview */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<ShowChartIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Evolution
</Typography>
<Divider sx={{ mb: 2 }} />
{analyticsData.content_evolution.content_types ? (
<Box>
{analyticsData.content_evolution.content_types.map((contentType: string, index: number) => {
const performance = analyticsData.content_evolution.performance_by_type?.[contentType];
return (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1" sx={{ textTransform: 'capitalize' }}>
{contentType.replace('_', ' ')}
</Typography>
{performance && (
<Grid container spacing={1}>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Growth
</Typography>
<Typography variant="h6" color="success.main">
+{performance.growth}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Engagement
</Typography>
<Typography variant="h6">
{performance.engagement}%
</Typography>
</Grid>
</Grid>
)}
</Box>
);
})}
</Box>
) : (
<Typography variant="body2" color="text.secondary">
No content evolution data available
</Typography>
)}
</Paper>
</Grid>
)}
{/* Performance Trends */}
{analyticsData && analyticsData.performance_trends && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingUpIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Performance Trends
</Typography>
<Divider sx={{ mb: 2 }} />
{analyticsData.performance_trends.engagement_trend ? (
<Box>
<Typography variant="subtitle2" gutterBottom>
Engagement Trend (Last 5 periods)
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
{analyticsData.performance_trends.engagement_trend.map((value: number, index: number) => (
<Box key={index} sx={{ flex: 1, textAlign: 'center' }}>
<Typography variant="h6" color="primary">
{value}%
</Typography>
<Typography variant="caption" color="text.secondary">
Period {index + 1}
</Typography>
</Box>
))}
</Box>
</Box>
) : (
<Typography variant="body2" color="text.secondary">
No trend data available
</Typography>
)}
</Paper>
</Grid>
)}
{/* Engagement Patterns */}
{analyticsData && analyticsData.engagement_patterns && (
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Engagement Patterns
Performance Overview
</Typography>
<Divider sx={{ mb: 2 }} />
<Grid container spacing={3}>
{analyticsData.engagement_patterns.peak_times && (
<Grid item xs={12} md={4}>
<Typography variant="subtitle2" gutterBottom>
Peak Engagement Times
{performanceMetrics ? (
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Engagement Rate
</Typography>
{analyticsData.engagement_patterns.peak_times.map((time: string, index: number) => (
<Chip
key={index}
label={time}
color="primary"
variant="outlined"
sx={{ mr: 1, mb: 1 }}
/>
))}
</Grid>
)}
{analyticsData.engagement_patterns.best_days && (
<Grid item xs={12} md={4}>
<Typography variant="subtitle2" gutterBottom>
Best Performing Days
<Typography variant="h4" color={getPerformanceColor(performanceMetrics.engagement)}>
{performanceMetrics.engagement}%
</Typography>
{analyticsData.engagement_patterns.best_days.map((day: string, index: number) => (
<Chip
key={index}
label={day}
color="success"
variant="outlined"
sx={{ mr: 1, mb: 1 }}
/>
))}
</Grid>
)}
{analyticsData.engagement_patterns.audience_segments && (
<Grid item xs={12} md={4}>
<Typography variant="subtitle2" gutterBottom>
Top Audience Segments
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Reach
</Typography>
<Typography variant="h4" color="primary">
{performanceMetrics.reach.toLocaleString()}
</Typography>
{analyticsData.engagement_patterns.audience_segments.map((segment: string, index: number) => (
<Chip
key={index}
label={segment.replace('_', ' ')}
color="secondary"
variant="outlined"
sx={{ mr: 1, mb: 1 }}
/>
))}
</Grid>
)}
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Conversion Rate
</Typography>
<Typography variant="h4" color={getPerformanceColor(performanceMetrics.conversion)}>
{performanceMetrics.conversion}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
ROI
</Typography>
<Typography variant="h4" color="success.main">
${performanceMetrics.roi.toLocaleString()}
</Typography>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary">
No performance data available
</Typography>
)}
</Paper>
</Grid>
)}
{/* Recommendations */}
{analyticsData && analyticsData.recommendations && analyticsData.recommendations.length > 0 && (
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
{/* AI Insights */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
AI Recommendations
AI Insights
</Typography>
<Divider sx={{ mb: 2 }} />
<Grid container spacing={2}>
{analyticsData.recommendations.map((recommendation: any, index: number) => (
<Grid item xs={12} md={6} key={index}>
<Card variant="outlined">
<CardContent>
<Typography variant="subtitle1" gutterBottom>
{recommendation.title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{recommendation.description}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={recommendation.type}
color="primary"
size="small"
/>
<Chip
label={`${(recommendation.confidence * 100).toFixed(0)}% confidence`}
color="success"
size="small"
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
{aiInsights && aiInsights.length > 0 ? (
<Box>
{aiInsights.slice(0, 3).map((insight, index) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
{insight.title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{insight.description}
</Typography>
<Chip
label={insight.priority}
color={insight.priority === 'high' ? 'error' : insight.priority === 'medium' ? 'warning' : 'success'}
size="small"
/>
</Box>
))}
</Box>
) : (
<Typography variant="body2" color="text.secondary">
No AI insights available
</Typography>
)}
</Paper>
</Grid>
)}
</Grid>
)}
{/* Content Evolution */}
{analyticsData && analyticsData.content_evolution && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<ShowChartIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Evolution
</Typography>
<Divider sx={{ mb: 2 }} />
{analyticsData.content_evolution.content_types ? (
<Box>
{analyticsData.content_evolution.content_types.map((contentType: string, index: number) => {
const performance = analyticsData.content_evolution.performance_by_type?.[contentType];
return (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1" sx={{ textTransform: 'capitalize' }}>
{contentType.replace('_', ' ')}
</Typography>
{performance && (
<Grid container spacing={1}>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Growth
</Typography>
<Typography variant="h6" color="success.main">
+{performance.growth}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2" color="text.secondary">
Engagement
</Typography>
<Typography variant="h6">
{performance.engagement}%
</Typography>
</Grid>
</Grid>
)}
</Box>
);
})}
</Box>
) : (
<Typography variant="body2" color="text.secondary">
No content evolution data available
</Typography>
)}
</Paper>
</Grid>
)}
{/* Performance Trends */}
{analyticsData && analyticsData.performance_trends && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingUpIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Performance Trends
</Typography>
<Divider sx={{ mb: 2 }} />
{analyticsData.performance_trends.engagement_trend ? (
<Box>
<Typography variant="subtitle2" gutterBottom>
Engagement Trend (Last 5 periods)
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
{analyticsData.performance_trends.engagement_trend.map((value: number, index: number) => (
<Box key={index} sx={{ flex: 1, textAlign: 'center' }}>
<Typography variant="h6" color="primary">
{value}%
</Typography>
<Typography variant="caption" color="text.secondary">
Period {index + 1}
</Typography>
</Box>
))}
</Box>
</Box>
) : (
<Typography variant="body2" color="text.secondary">
No trend data available
</Typography>
)}
</Paper>
</Grid>
)}
{/* Engagement Patterns */}
{analyticsData && analyticsData.engagement_patterns && (
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Engagement Patterns
</Typography>
<Divider sx={{ mb: 2 }} />
<Grid container spacing={3}>
{analyticsData.engagement_patterns.peak_times && (
<Grid item xs={12} md={4}>
<Typography variant="subtitle2" gutterBottom>
Peak Engagement Times
</Typography>
{analyticsData.engagement_patterns.peak_times.map((time: string, index: number) => (
<Chip
key={index}
label={time}
color="primary"
variant="outlined"
sx={{ mr: 1, mb: 1 }}
/>
))}
</Grid>
)}
{analyticsData.engagement_patterns.best_days && (
<Grid item xs={12} md={4}>
<Typography variant="subtitle2" gutterBottom>
Best Performing Days
</Typography>
{analyticsData.engagement_patterns.best_days.map((day: string, index: number) => (
<Chip
key={index}
label={day}
color="success"
variant="outlined"
sx={{ mr: 1, mb: 1 }}
/>
))}
</Grid>
)}
{analyticsData.engagement_patterns.audience_segments && (
<Grid item xs={12} md={4}>
<Typography variant="subtitle2" gutterBottom>
Top Audience Segments
</Typography>
{analyticsData.engagement_patterns.audience_segments.map((segment: string, index: number) => (
<Chip
key={index}
label={segment.replace('_', ' ')}
color="secondary"
variant="outlined"
sx={{ mr: 1, mb: 1 }}
/>
))}
</Grid>
)}
</Grid>
</Paper>
</Grid>
)}
{/* Recommendations */}
{analyticsData && analyticsData.recommendations && analyticsData.recommendations.length > 0 && (
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
AI Recommendations
</Typography>
<Divider sx={{ mb: 2 }} />
<Grid container spacing={2}>
{analyticsData.recommendations.map((recommendation: any, index: number) => (
<Grid item xs={12} md={6} key={index}>
<Card variant="outlined">
<CardContent>
<Typography variant="subtitle1" gutterBottom>
{recommendation.title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{recommendation.description}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={recommendation.type}
color="primary"
size="small"
/>
<Chip
label={`${(recommendation.confidence * 100).toFixed(0)}% confidence`}
color="success"
size="small"
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Paper>
</Grid>
)}
</Grid>
)}
</TabPanel>
{/* Data Transparency Tab */}
<TabPanel value={activeTab} index={2}>
<DataTransparencyPanel
strategyId={strategyId}
strategyData={currentStrategy}
/>
</TabPanel>
</Box>
);
};

View File

@@ -1,58 +1,22 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Grid,
Paper,
Typography,
Button,
Card,
CardContent,
CardActions,
Chip,
Alert,
List,
ListItem,
ListItemText,
ListItemIcon,
LinearProgress,
CircularProgress,
Tabs,
Tab,
Accordion,
AccordionSummary,
AccordionDetails,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
IconButton,
Tooltip,
Badge
CircularProgress
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
Business as BusinessIcon,
Lightbulb as LightbulbIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Analytics as AnalyticsIcon,
Timeline as TimelineIcon,
Assessment as AssessmentIcon,
ExpandMore as ExpandMoreIcon,
Refresh as RefreshIcon,
Add as AddIcon,
Edit as EditIcon,
Visibility as VisibilityIcon,
ShowChart as ShowChartIcon,
PlayArrow as PlayArrowIcon,
AutoAwesome as AutoAwesomeIcon,
PlayArrow as PlayArrowIcon
Edit as EditIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import StrategyIntelligenceTab from '../components/StrategyIntelligence/StrategyIntelligenceTab';
import StrategyOnboardingDialog from '../components/StrategyOnboardingDialog';
import { StrategyData } from '../components/StrategyIntelligence/types/strategy.types';
const ContentStrategyTab: React.FC = () => {
const {
@@ -77,6 +41,9 @@ const ContentStrategyTab: React.FC = () => {
// Real data states
const [strategicIntelligence, setStrategicIntelligence] = useState<any>(null);
const [strategyData, setStrategyData] = useState<StrategyData | null>(null);
const [strategyDataLoading, setStrategyDataLoading] = useState(false);
const [strategyDataError, setStrategyDataError] = useState<string | null>(null);
const [dataLoading, setDataLoading] = useState({
strategies: false,
insights: false,
@@ -118,6 +85,7 @@ const ContentStrategyTab: React.FC = () => {
if (strategiesArray.length > 0) {
console.log('✅ Strategies found, checking status...');
checkStrategyStatus();
loadStrategyData();
} else if (strategiesArray.length === 0 && hasCheckedStrategy) {
// Only set to 'none' if we've already checked and confirmed no strategies
console.log('❌ No strategies found, setting status to none...');
@@ -127,6 +95,61 @@ const ContentStrategyTab: React.FC = () => {
// If strategiesArray.length === 0 and !hasCheckedStrategy, do nothing (wait for data to load)
}, [strategies, loadStrategies]);
const loadStrategyData = async () => {
try {
setStrategyDataLoading(true);
setStrategyDataError(null);
const userId = 1; // Default user ID
// Try to get the latest generated strategy
try {
const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategy(userId);
console.log('🔍 Latest strategy response from API:', latestStrategyResponse);
if (latestStrategyResponse && latestStrategyResponse.strategic_insights) {
console.log('✅ Found latest generated strategy:', latestStrategyResponse);
setStrategyData(latestStrategyResponse);
return;
}
} catch (pollingError) {
console.log('No latest strategy found in polling system, checking database...', pollingError);
}
// If no strategy found in polling system, try to get from database
try {
const strategiesResponse = await contentPlanningApi.getEnhancedStrategies(userId);
const strategies = strategiesResponse?.data?.strategies || strategiesResponse?.strategies || [];
if (strategies && strategies.length > 0) {
const latestStrategy = strategies[0];
if (latestStrategy.comprehensive_ai_analysis) {
console.log('✅ Found comprehensive strategy in database:', latestStrategy);
setStrategyData(latestStrategy.comprehensive_ai_analysis);
return;
}
}
} catch (dbError) {
console.log('No comprehensive strategies found in database:', dbError);
}
// If no strategy data is available
console.log('❌ No comprehensive strategy data found');
setStrategyData(null);
setStrategyDataError('No comprehensive strategy data available. Please generate a strategy first.');
} catch (err: any) {
console.error('Error loading strategy data:', err);
setStrategyDataError(err.message || 'Failed to load strategy data');
setStrategyData(null);
} finally {
setStrategyDataLoading(false);
}
};
const checkStrategyStatus = () => {
console.log('🔍 Checking strategy status...');
console.log('🔍 Strategies from store:', strategies);
@@ -178,9 +201,6 @@ const ContentStrategyTab: React.FC = () => {
loadAIInsights(),
loadAIRecommendations()
]);
// Load strategic intelligence
await loadStrategicIntelligence();
} catch (error) {
console.error('Error loading initial data:', error);
@@ -189,141 +209,13 @@ const ContentStrategyTab: React.FC = () => {
}
};
const loadStrategicIntelligence = async () => {
try {
setDataLoading(prev => ({ ...prev, strategicIntelligence: true }));
// Use streaming endpoint for real-time updates
const eventSource = await contentPlanningApi.streamStrategicIntelligence(1);
contentPlanningApi.handleSSEData(
eventSource,
(data) => {
if (data.type === 'status') {
// Update loading message
} else if (data.type === 'progress') {
// Update progress (could be used for progress bar)
} else if (data.type === 'result' && data.status === 'success') {
// Set the strategic intelligence data
setStrategicIntelligence(data.data);
setDataLoading(prev => ({ ...prev, strategicIntelligence: false }));
} else if (data.type === 'error') {
// Set fallback data on error
setStrategicIntelligence({
market_positioning: {
score: 75,
strengths: ['Strong brand voice', 'Consistent content quality'],
weaknesses: ['Limited video content', 'Slow content production']
},
competitive_advantages: [
{ advantage: 'AI-powered content creation', impact: 'High', implementation: 'In Progress' },
{ advantage: 'Data-driven strategy', impact: 'Medium', implementation: 'Complete' }
],
strategic_risks: [
{ risk: 'Content saturation in market', probability: 'Medium', impact: 'High' },
{ risk: 'Algorithm changes affecting reach', probability: 'High', impact: 'Medium' }
]
});
setDataLoading(prev => ({ ...prev, strategicIntelligence: false }));
}
},
(error) => {
// Set fallback data on error
setStrategicIntelligence({
market_positioning: {
score: 75,
strengths: ['Strong brand voice', 'Consistent content quality'],
weaknesses: ['Limited video content', 'Slow content production']
},
competitive_advantages: [
{ advantage: 'AI-powered content creation', impact: 'High', implementation: 'In Progress' },
{ advantage: 'Data-driven strategy', impact: 'Medium', implementation: 'Complete' }
],
strategic_risks: [
{ risk: 'Content saturation in market', probability: 'Medium', impact: 'High' },
{ risk: 'Algorithm changes affecting reach', probability: 'High', impact: 'Medium' }
]
});
setDataLoading(prev => ({ ...prev, strategicIntelligence: false }));
}
);
} catch (error) {
console.error('Error loading strategic intelligence:', error);
// Set fallback data on error
setStrategicIntelligence({
market_positioning: {
score: 75,
strengths: ['Strong brand voice', 'Consistent content quality'],
weaknesses: ['Limited video content', 'Slow content production']
},
competitive_advantages: [
{ advantage: 'AI-powered content creation', impact: 'High', implementation: 'In Progress' },
{ advantage: 'Data-driven strategy', impact: 'Medium', implementation: 'Complete' }
],
strategic_risks: [
{ risk: 'Content saturation in market', probability: 'Medium', impact: 'High' },
{ risk: 'Algorithm changes affecting reach', probability: 'High', impact: 'Medium' }
]
});
setDataLoading(prev => ({ ...prev, strategicIntelligence: false }));
}
};
const handleStrategyFormChange = (field: string, value: string) => {
setStrategyForm(prev => ({
...prev,
[field]: value
}));
};
const handleCreateStrategy = async () => {
if (!strategyForm.name || !strategyForm.description) {
return;
}
try {
// Call backend API to create strategy
await contentPlanningApi.createStrategy({
name: strategyForm.name,
description: strategyForm.description,
industry: strategyForm.industry,
target_audience: strategyForm.target_audience,
content_pillars: strategyForm.content_pillars
});
// Reload data after creating strategy
await loadInitialData();
// Reset form
setStrategyForm({
name: '',
description: '',
industry: '',
target_audience: '',
content_pillars: []
});
} catch (error) {
console.error('Error creating strategy:', error);
}
};
const handleRefreshData = async () => {
await loadInitialData();
};
// Onboarding dialog handlers
const handleConfirmStrategy = async () => {
try {
if (currentStrategy) {
// For now, we'll just close the dialog since we can't update status
// In a real implementation, you would update the strategy status in the database
setShowOnboarding(false);
// Reload strategies to get updated data
await loadStrategies();
}
// In a real implementation, you would update the strategy status in the database
setShowOnboarding(false);
// Reload strategies to get updated data
await loadStrategies();
} catch (error) {
console.error('Error activating strategy:', error);
}
@@ -396,12 +288,38 @@ const ContentStrategyTab: React.FC = () => {
</Alert>
)}
{/* Active Strategy Status Banner */}
{strategyStatus === 'active' && currentStrategy && (
<Alert
severity="success"
sx={{ mb: 3 }}
action={
<Button
color="inherit"
size="small"
onClick={() => setShowOnboarding(true)}
startIcon={<EditIcon />}
>
Edit Strategy
</Button>
}
>
<Typography variant="body1">
<strong>Strategy Active:</strong> Your AI-powered content strategy is active and being monitored. View performance analytics in the Analytics tab.
</Typography>
</Alert>
)}
{/* Strategic Intelligence */}
<Paper sx={{ width: '100%', mb: 3 }}>
<StrategyIntelligenceTab />
</Paper>
{/* Strategic Intelligence - Only show if there's an active strategy */}
{strategyStatus === 'active' && (
<Paper sx={{ width: '100%', mb: 3 }}>
<StrategyIntelligenceTab
strategyData={strategyData}
loading={strategyDataLoading}
error={strategyDataError}
/>
</Paper>
)}
{/* Strategy Onboarding Dialog */}
<StrategyOnboardingDialog

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import {
Box,
Tabs,
@@ -12,6 +13,7 @@ import {
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
import CalendarGenerationWizard from '../components/CalendarGenerationWizard';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import { useStrategyCalendarContext } from '../../../contexts/StrategyCalendarContext';
interface TabPanelProps {
children?: React.ReactNode;
@@ -36,28 +38,87 @@ function TabPanel(props: TabPanelProps) {
}
const CreateTab: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [userData, setUserData] = useState<any>({});
const location = useLocation();
// Get strategy context from the provider
const { state: { strategyContext }, isFromStrategyActivation } = useStrategyCalendarContext();
console.log('🔍 CreateTab: Rendering', { userData, strategyContext, locationState: location.state });
// Memoize the strategy activation status to avoid infinite re-renders
const fromStrategyActivation = useMemo(() => {
return isFromStrategyActivation();
}, [isFromStrategyActivation]);
// Set initial tab value based on strategy activation
const [tabValue, setTabValue] = useState(0); // Always start with Strategy Builder tab
useEffect(() => {
loadUserData();
}, []);
// Only load user data once on mount
const loadData = async () => {
try {
const comprehensiveData = await contentPlanningApi.getComprehensiveUserData(1);
setUserData(comprehensiveData.data);
} catch (error) {
console.error('Error loading user data:', error);
// Set empty data to prevent infinite loops
setUserData({});
}
};
loadData();
}, []); // Empty dependency array - only run once
const loadUserData = async () => {
try {
// Load comprehensive user data for calendar generation
const comprehensiveData = await contentPlanningApi.getComprehensiveUserData(1); // Pass user ID
setUserData(comprehensiveData.data); // Extract the data from the response
} catch (error) {
console.error('Error loading user data:', error);
// Auto-switch to Calendar Wizard tab when coming from strategy activation
useEffect(() => {
console.log('🔍 CreateTab: Checking strategy activation status:', { fromStrategyActivation });
// Check multiple sources for strategy activation status
const isFromStrategy = fromStrategyActivation ||
(strategyContext?.activationStatus === 'active') ||
(location.state as any)?.fromStrategyActivation;
console.log('🔍 CreateTab: Strategy activation check:', {
fromStrategyActivation,
strategyContextActivationStatus: strategyContext?.activationStatus,
windowLocationState: location.state || 'N/A',
isFromStrategy
});
if (isFromStrategy) {
console.log('🎯 CreateTab: Switching to Calendar Wizard tab (index 1)');
setTabValue(1); // Switch to Calendar Wizard tab
}
};
}, [fromStrategyActivation, strategyContext?.activationStatus]);
// Also check on mount for immediate navigation state
useEffect(() => {
const checkNavigationState = () => {
const locationState = location.state as any;
console.log('🔍 CreateTab: Initial navigation state check:', locationState);
if (locationState?.fromStrategyActivation || locationState?.strategyContext) {
console.log('🎯 CreateTab: Found navigation state, switching to Calendar Wizard tab (index 1)');
setTabValue(1);
}
};
// Check immediately
checkNavigationState();
// Also check after a short delay to ensure context is loaded
const timer = setTimeout(checkNavigationState, 100);
return () => clearTimeout(timer);
}, [location.state]);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleGenerateCalendar = async (calendarConfig: any) => {
const handleGenerateCalendar = useCallback(async (calendarConfig: any) => {
try {
await contentPlanningApi.generateComprehensiveCalendar({
...calendarConfig,
@@ -66,7 +127,7 @@ const CreateTab: React.FC = () => {
} catch (error) {
console.error('Error generating calendar:', error);
}
};
}, [userData]);
return (
<Box sx={{ p: 3 }}>
@@ -104,6 +165,8 @@ const CreateTab: React.FC = () => {
userData={userData}
onGenerateCalendar={handleGenerateCalendar}
loading={false}
strategyContext={strategyContext}
fromStrategyActivation={fromStrategyActivation}
/>
</TabPanel>
</Box>

View File

@@ -0,0 +1,410 @@
import React from 'react';
import {
LineChart,
Line,
BarChart,
Bar,
PieChart,
Pie,
Cell,
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
RadarChart,
PolarGrid,
PolarAngleAxis,
PolarRadiusAxis,
Radar,
ComposedChart
} from 'recharts';
import { Box, Typography, Paper, useTheme } from '@mui/material';
// Color palette for charts
const CHART_COLORS = {
primary: '#667eea',
secondary: '#764ba2',
success: '#4caf50',
warning: '#ff9800',
error: '#f44336',
info: '#2196f3',
light: '#f5f5f5',
dark: '#333333'
};
// Performance Trend Chart Component
interface PerformanceTrendChartProps {
data: Array<{
date: string;
traffic_growth: number;
engagement_rate: number;
conversion_rate: number;
content_quality_score: number;
}>;
title?: string;
height?: number;
}
const PerformanceTrendChart: React.FC<PerformanceTrendChartProps> = ({
data,
title = 'Performance Trends',
height = 400
}) => {
const theme = useTheme();
return (
<Paper elevation={2} sx={{ p: 3, height }}>
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
{title}
</Typography>
<ResponsiveContainer width="100%" height="85%">
<ComposedChart data={data}>
<CartesianGrid strokeDasharray="3 3" stroke={theme.palette.divider} />
<XAxis
dataKey="date"
stroke={theme.palette.text.secondary}
fontSize={12}
/>
<YAxis
stroke={theme.palette.text.secondary}
fontSize={12}
/>
<Tooltip
contentStyle={{
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 8
}}
/>
<Legend />
<Area
type="monotone"
dataKey="content_quality_score"
stackId="1"
stroke={CHART_COLORS.info}
fill={CHART_COLORS.info}
fillOpacity={0.3}
name="Content Quality Score"
/>
<Line
type="monotone"
dataKey="traffic_growth"
stroke={CHART_COLORS.primary}
strokeWidth={3}
dot={{ fill: CHART_COLORS.primary, strokeWidth: 2, r: 4 }}
name="Traffic Growth (%)"
/>
<Line
type="monotone"
dataKey="engagement_rate"
stroke={CHART_COLORS.success}
strokeWidth={3}
dot={{ fill: CHART_COLORS.success, strokeWidth: 2, r: 4 }}
name="Engagement Rate (%)"
/>
<Line
type="monotone"
dataKey="conversion_rate"
stroke={CHART_COLORS.warning}
strokeWidth={3}
dot={{ fill: CHART_COLORS.warning, strokeWidth: 2, r: 4 }}
name="Conversion Rate (%)"
/>
</ComposedChart>
</ResponsiveContainer>
</Paper>
);
};
// Quality Metrics Radar Chart Component
interface QualityMetricsRadarProps {
data: Array<{
metric: string;
score: number;
target: number;
}>;
title?: string;
height?: number;
}
const QualityMetricsRadar: React.FC<QualityMetricsRadarProps> = ({
data,
title = 'Quality Metrics Analysis',
height = 400
}) => {
const theme = useTheme();
return (
<Paper elevation={2} sx={{ p: 3, height }}>
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
{title}
</Typography>
<ResponsiveContainer width="100%" height="85%">
<RadarChart data={data}>
<PolarGrid stroke={theme.palette.divider} />
<PolarAngleAxis
dataKey="metric"
tick={{ fill: theme.palette.text.primary, fontSize: 12 }}
/>
<PolarRadiusAxis
angle={90}
domain={[0, 100]}
tick={{ fill: theme.palette.text.secondary, fontSize: 10 }}
/>
<Radar
name="Current Score"
dataKey="score"
stroke={CHART_COLORS.primary}
fill={CHART_COLORS.primary}
fillOpacity={0.3}
/>
<Radar
name="Target Score"
dataKey="target"
stroke={CHART_COLORS.success}
fill={CHART_COLORS.success}
fillOpacity={0.1}
strokeDasharray="5 5"
/>
<Legend />
<Tooltip
contentStyle={{
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 8
}}
/>
</RadarChart>
</ResponsiveContainer>
</Paper>
);
};
// Performance Metrics Bar Chart Component
interface PerformanceMetricsBarProps {
data: Array<{
metric: string;
value: number;
target: number;
status: 'excellent' | 'good' | 'needs_attention';
}>;
title?: string;
height?: number;
}
const PerformanceMetricsBar: React.FC<PerformanceMetricsBarProps> = ({
data,
title = 'Performance Metrics',
height = 400
}) => {
const theme = useTheme();
const getStatusColor = (status: string) => {
switch (status) {
case 'excellent': return CHART_COLORS.success;
case 'good': return CHART_COLORS.warning;
case 'needs_attention': return CHART_COLORS.error;
default: return CHART_COLORS.primary;
}
};
return (
<Paper elevation={2} sx={{ p: 3, height }}>
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
{title}
</Typography>
<ResponsiveContainer width="100%" height="85%">
<BarChart data={data} layout="horizontal">
<CartesianGrid strokeDasharray="3 3" stroke={theme.palette.divider} />
<XAxis
type="number"
stroke={theme.palette.text.secondary}
fontSize={12}
/>
<YAxis
type="category"
dataKey="metric"
stroke={theme.palette.text.secondary}
fontSize={12}
width={120}
/>
<Tooltip
contentStyle={{
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 8
}}
/>
<Legend />
<Bar
dataKey="value"
fill={CHART_COLORS.primary}
radius={[0, 4, 4, 0]}
>
{data.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={getStatusColor(entry.status)}
/>
))}
</Bar>
<Bar
dataKey="target"
fill={CHART_COLORS.info}
fillOpacity={0.5}
radius={[0, 4, 4, 0]}
/>
</BarChart>
</ResponsiveContainer>
</Paper>
);
};
// Content Distribution Pie Chart Component
interface ContentDistributionPieProps {
data: Array<{
name: string;
value: number;
color?: string;
}>;
title?: string;
height?: number;
}
const ContentDistributionPie: React.FC<ContentDistributionPieProps> = ({
data,
title = 'Content Distribution',
height = 400
}) => {
const theme = useTheme();
const COLORS = [
CHART_COLORS.primary,
CHART_COLORS.secondary,
CHART_COLORS.success,
CHART_COLORS.warning,
CHART_COLORS.error,
CHART_COLORS.info
];
return (
<Paper elevation={2} sx={{ p: 3, height }}>
<Typography variant="h6" gutterBottom sx={{ mb: 3 }}>
{title}
</Typography>
<ResponsiveContainer width="100%" height="85%">
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) => `${name} ${((percent || 0) * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{data.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.color || COLORS[index % COLORS.length]}
/>
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 8
}}
/>
</PieChart>
</ResponsiveContainer>
</Paper>
);
};
// Real-time Performance Gauge Component
interface PerformanceGaugeProps {
value: number;
maxValue: number;
title: string;
color?: string;
size?: number;
}
const PerformanceGauge: React.FC<PerformanceGaugeProps> = ({
value,
maxValue,
title,
color = CHART_COLORS.primary,
size = 200
}) => {
const percentage = (value / maxValue) * 100;
const circumference = 2 * Math.PI * 80; // radius = 80
const strokeDasharray = circumference;
const strokeDashoffset = circumference - (percentage / 100) * circumference;
return (
<Box sx={{ textAlign: 'center', p: 2 }}>
<Typography variant="h6" gutterBottom>
{title}
</Typography>
<Box sx={{ position: 'relative', display: 'inline-block' }}>
<svg width={size} height={size} viewBox="0 0 200 200">
{/* Background circle */}
<circle
cx="100"
cy="100"
r="80"
fill="none"
stroke="#e0e0e0"
strokeWidth="12"
/>
{/* Progress circle */}
<circle
cx="100"
cy="100"
r="80"
fill="none"
stroke={color}
strokeWidth="12"
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
transform="rotate(-90 100 100)"
/>
</svg>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
textAlign: 'center'
}}
>
<Typography variant="h4" sx={{ fontWeight: 'bold', color }}>
{value}
</Typography>
<Typography variant="body2" color="text.secondary">
of {maxValue}
</Typography>
</Box>
</Box>
</Box>
);
};
// Export all components
export {
PerformanceTrendChart,
QualityMetricsRadar,
PerformanceMetricsBar,
ContentDistributionPie,
PerformanceGauge
};