ALwrity version 0.5.6

This commit is contained in:
ajaysi
2025-08-22 14:08:54 +05:30
parent 3f2f4d7b8c
commit 5d8d1cfb73
113 changed files with 28164 additions and 2968 deletions

View File

@@ -30,12 +30,11 @@ import AnalyticsTab from './tabs/AnalyticsTab';
import GapAnalysisTab from './tabs/GapAnalysisTab';
import CreateTab from './tabs/CreateTab';
import AIInsightsPanel from './components/AIInsightsPanel';
import ServiceStatusPanel from './components/ServiceStatusPanel';
import SystemStatusIndicator from './components/SystemStatusIndicator';
import ProgressIndicator from './components/ProgressIndicator';
import { useContentPlanningStore } from '../../stores/contentPlanningStore';
import {
contentPlanningOrchestrator,
ServiceStatus,
DashboardData
} from '../../services/contentPlanningOrchestrator';
import { StrategyCalendarProvider } from '../../contexts/StrategyCalendarContext';
@@ -76,7 +75,6 @@ function a11yProps(index: number) {
const ContentPlanningDashboard: React.FC = () => {
const location = useLocation();
const [activeTab, setActiveTab] = useState(0);
const [serviceStatuses, setServiceStatuses] = useState<ServiceStatus[]>([]);
const [dashboardData, setDashboardData] = useState<DashboardData>({
strategies: [],
gapAnalyses: [],
@@ -89,7 +87,6 @@ const ContentPlanningDashboard: React.FC = () => {
aiServices: false
}
});
const [statusPanelExpanded, setStatusPanelExpanded] = useState(false);
const [progressExpanded, setProgressExpanded] = useState(true);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -104,10 +101,6 @@ const ContentPlanningDashboard: React.FC = () => {
// Initialize orchestrator callbacks
useEffect(() => {
contentPlanningOrchestrator.setProgressCallback((statuses) => {
setServiceStatuses(statuses);
});
contentPlanningOrchestrator.setDataUpdateCallback((data) => {
setDashboardData(prev => ({ ...prev, ...data }));
@@ -134,15 +127,15 @@ const ContentPlanningDashboard: React.FC = () => {
// Load dashboard data using orchestrator
useEffect(() => {
const loadDashboardData = async () => {
setLoading(true);
setError(null);
try {
setLoading(true);
setError(null);
await contentPlanningOrchestrator.loadDashboardData();
setLoading(false);
} catch (error: any) {
console.error('Failed to load dashboard data:', error);
console.error('Error loading dashboard data:', error);
setError(error.message || 'Failed to load dashboard data');
} finally {
setLoading(false);
}
};
@@ -157,27 +150,10 @@ const ContentPlanningDashboard: React.FC = () => {
}
}, []);
const handleRefreshService = (serviceName: string) => {
contentPlanningOrchestrator.refreshService(serviceName);
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
};
const getOverallHealthStatus = () => {
const { healthStatus } = dashboardData;
if (healthStatus.backend && healthStatus.database && healthStatus.aiServices) {
return { status: 'success', text: 'Connected' };
} else if (healthStatus.backend && healthStatus.database) {
return { status: 'warning', text: 'Connected API & DB' };
} else {
return { status: 'error', text: 'Disconnected' };
}
};
const overallHealth = getOverallHealthStatus();
const tabs = [
{ label: 'CONTENT STRATEGY', icon: <StrategyIcon />, component: <ContentStrategyTab /> },
{ label: 'CALENDAR', icon: <CalendarIcon />, component: <CalendarTab /> },
@@ -197,12 +173,7 @@ const ContentPlanningDashboard: React.FC = () => {
Content Planning Dashboard
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<ServiceStatusPanel
serviceStatuses={serviceStatuses}
onRefreshService={handleRefreshService}
expanded={statusPanelExpanded}
onToggleExpanded={() => setStatusPanelExpanded(!statusPanelExpanded)}
/>
<SystemStatusIndicator />
{/* AI Insights Button with Badge */}
<motion.div
@@ -244,43 +215,59 @@ const ContentPlanningDashboard: React.FC = () => {
{loading && (
<Box sx={{ m: 2 }}>
<ProgressIndicator
serviceStatuses={serviceStatuses}
onRefreshService={handleRefreshService}
expanded={progressExpanded}
onToggleExpanded={() => setProgressExpanded(!progressExpanded)}
/>
</Box>
)}
<Box sx={{ display: 'flex', height: 'calc(100vh - 64px)' }}>
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={activeTab}
onChange={handleTabChange}
aria-label="content planning tabs"
sx={{ px: 2 }}
{/* Main Content */}
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={activeTab}
onChange={handleTabChange}
aria-label="content planning tabs"
sx={{
'& .MuiTab-root': {
textTransform: 'none',
fontWeight: 600,
minHeight: 64,
fontSize: '0.875rem'
}
}}
>
{tabs.map((tab, index) => (
<Tab
key={index}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{tab.icon}
{tab.label}
</Box>
}
{...a11yProps(index)}
/>
))}
</Tabs>
</Box>
<Box sx={{ flexGrow: 1, overflow: 'auto' }}>
<AnimatePresence mode="wait">
<motion.div
key={activeTab}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
>
{tabs.map((tab, index) => (
<Tab
key={index}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{tab.icon}
{tab.label}
</Box>
}
{...a11yProps(index)}
/>
<TabPanel key={index} value={activeTab} index={index}>
{tab.component}
</TabPanel>
))}
</Tabs>
</Box>
{tabs.map((tab, index) => (
<TabPanel key={index} value={activeTab} index={index}>
{tab.component}
</TabPanel>
))}
</motion.div>
</AnimatePresence>
</Box>
</Box>
@@ -291,43 +278,22 @@ const ContentPlanningDashboard: React.FC = () => {
onClose={() => setAiInsightsDrawerOpen(false)}
PaperProps={{
sx: {
width: 400,
height: '100%',
backgroundColor: 'background.paper',
borderLeft: '1px solid',
borderColor: 'divider'
width: { xs: '100%', sm: 400 },
maxWidth: '100vw'
}
}}
>
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center' }}>
<AIInsightsIcon sx={{ mr: 1 }} />
AI Insights
</Typography>
<IconButton
onClick={() => setAiInsightsDrawerOpen(false)}
size="small"
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6">AI Insights & Recommendations</Typography>
<IconButton onClick={() => setAiInsightsDrawerOpen(false)}>
<CloseIcon />
</IconButton>
</Box>
</Box>
<Box sx={{ flex: 1, overflow: 'auto' }}>
<AnimatePresence>
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
>
<AIInsightsPanel />
</motion.div>
</AnimatePresence>
</Box>
<AIInsightsPanel />
</Drawer>
</Container>
</Container>
</StrategyCalendarProvider>
);
};

View File

@@ -0,0 +1,359 @@
import { SxProps, Theme } from '@mui/material/styles';
/**
* Styles for CalendarGenerationModal Component
* All styling logic extracted for maintainability and reusability
*/
// Dialog and Layout Styles
export const dialogStyles = {
paper: {
height: '90vh',
maxHeight: '90vh'
}
};
export const contentContainerStyles: SxProps<Theme> = {
p: 2
};
// Progress Bar Styles
export const progressBarContainerStyles: SxProps<Theme> = {
flexGrow: 1,
position: 'relative'
};
export const progressBarStyles: SxProps<Theme> = {
height: 8,
borderRadius: 4,
backgroundColor: 'grey.200',
'& .MuiLinearProgress-bar': {
borderRadius: 4,
background: 'linear-gradient(90deg, #1976d2 0%, #42a5f5 100%)',
transition: 'transform 0.8s ease-in-out'
}
};
export const stepProgressBarStyles: SxProps<Theme> = {
height: 10,
borderRadius: 5,
backgroundColor: 'grey.200',
'& .MuiLinearProgress-bar': {
borderRadius: 5,
background: 'linear-gradient(90deg, #1976d2 0%, #42a5f5 100%)',
transition: 'transform 0.6s ease-in-out'
}
};
// Step Indicator Styles
export const getStepIndicatorStyles = (currentStep: number, step: number): SxProps<Theme> => ({
p: 1,
borderRadius: 1,
backgroundColor: currentStep === step ? 'primary.light' : 'grey.100',
color: currentStep === step ? 'primary.contrastText' : 'text.secondary',
transition: 'all 0.3s ease',
cursor: 'pointer'
});
// Step Card Styles
export const getStepCardStyles = (currentStep: number, step: number): SxProps<Theme> => ({
p: 2,
backgroundColor: currentStep === step ? 'primary.light' : 'grey.50',
borderColor: currentStep === step ? 'primary.main' : 'grey.300',
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: currentStep === step
? 'linear-gradient(90deg, transparent, rgba(25, 118, 210, 0.1), transparent)'
: 'none',
transition: 'left 0.6s ease-in-out'
},
'&:hover::before': {
left: '100%'
}
});
// Step Circle Styles
export const stepCircleBaseStyles = {
width: 32,
height: 32,
borderRadius: '50%',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.875rem',
fontWeight: 'bold'
};
export const getStepCircleColor = (currentStep: number, step: number): string => {
if (currentStep > step) return '#4caf50';
if (currentStep === step) return '#1976d2';
return '#9e9e9e';
};
// Tab Button Styles
export const tabButtonStyles: SxProps<Theme> = {
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
transition: 'left 0.5s'
},
'&:hover::before': {
left: '100%'
}
};
// Activity Indicator Styles
export const activityIndicatorStyles = {
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: '#1976d2',
marginTop: 8,
flexShrink: 0
};
// Quality Score Styles
export const qualityScoreContainerStyles: SxProps<Theme> = {
width: 80,
height: 80,
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative'
};
export const getQualityScoreBackground = (score: number): string => {
const color = score >= 0.9 ? '#4caf50' : score >= 0.8 ? '#ff9800' : '#f44336';
return `conic-gradient(${color} 0deg, ${score * 360}deg, #e0e0e0 ${score * 360}deg, 360deg)`;
};
export const qualityScoreInnerStyles: SxProps<Theme> = {
width: 60,
height: 60,
borderRadius: '50%',
backgroundColor: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
fontSize: '1.2rem'
};
// Data Source Card Styles
export const dataSourceCardStyles: SxProps<Theme> = {
p: 2
};
export const dataSourceIconStyles = {
width: 32,
height: 32,
borderRadius: '50%',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.875rem'
};
export const getDataSourceIconColor = (type: string): string => {
switch (type) {
case 'strategy':
return '#4caf50'; // success.main
case 'onboarding':
return '#2196f3'; // info.main
case 'ai':
return '#1976d2'; // primary.main
case 'performance':
return '#9c27b0'; // secondary.main
default:
return '#757575'; // grey
}
};
// Quality Metrics Styles
export const qualityMetricsContainerStyles: SxProps<Theme> = {
textAlign: 'center',
p: 2
};
export const getMetricColor = (label: string): string => {
switch (label) {
case 'Overall Data Quality':
return '#4caf50'; // success.main
case 'Data Completeness':
return '#2196f3'; // info.main
case 'Data Freshness':
return '#1976d2'; // primary.main
default:
return '#757575';
}
};
// Step Results Styles
export const stepResultsCardStyles: SxProps<Theme> = {
p: 2
};
export const stepResultsHeaderStyles: SxProps<Theme> = {
width: 40,
height: 40,
borderRadius: '50%',
backgroundColor: 'primary.main',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold'
};
export const stepResultsContentStyles: SxProps<Theme> = {
backgroundColor: 'grey.50',
p: 2,
borderRadius: 1
};
// Loading State Styles
export const loadingContainerStyles: SxProps<Theme> = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '400px'
};
export const loadingContentStyles: SxProps<Theme> = {
textAlign: 'center'
};
// Animation Constants
export const animationDurations = {
fast: 0.3,
medium: 0.5,
slow: 0.8,
extraSlow: 1.0
};
export const animationEasing = {
easeOut: "easeOut" as const,
easeInOut: "easeInOut" as const,
linear: "linear" as const
};
export const springConfig = {
type: "spring" as const,
stiffness: 200,
damping: 10
};
export const staggerDelay = 0.1;
export const cardStaggerDelay = 0.2;
// Motion Variants
export const fadeInUp = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 }
};
export const fadeInLeft = {
initial: { opacity: 0, x: -20, scale: 0.95 },
animate: { opacity: 1, x: 0, scale: 1 },
exit: { opacity: 0, x: 50, scale: 0.95 }
};
export const scaleIn = {
initial: { opacity: 0, scale: 0.8 },
animate: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0.8 }
};
export const slideInStaggered = {
initial: { opacity: 0, y: 50, scale: 0.8 },
animate: { opacity: 1, y: 0, scale: 1 },
exit: { opacity: 0, y: -50, scale: 0.8 }
};
// Hover and Interaction Styles
export const hoverLift = {
scale: 1.02,
y: -5
};
export const hoverScale = {
scale: 1.05
};
export const tapScale = {
scale: 0.95
};
// Pulse Animation Config
export const pulseAnimation = {
scale: [1, 1.1, 1],
boxShadow: [
"0 0 0 0 rgba(76, 175, 80, 0.4)",
"0 0 0 10px rgba(76, 175, 80, 0)",
"0 0 0 0 rgba(76, 175, 80, 0)"
]
};
export const smallPulseAnimation = {
scale: [1, 1.1, 1],
boxShadow: [
"0 0 0 0 rgba(76, 175, 80, 0.4)",
"0 0 0 6px rgba(76, 175, 80, 0)",
"0 0 0 0 rgba(76, 175, 80, 0)"
]
};
// Color Animation Config
export const colorPulseAnimation = {
scale: [1, 1.2, 1],
backgroundColor: ['#1976d2', '#42a5f5', '#1976d2']
};
// Progress Animation Config
export const progressFillAnimation = {
initial: { scaleX: 0 },
animate: (progress: number) => ({ scaleX: progress / 100 }),
transition: { duration: animationDurations.slow, ease: animationEasing.easeOut }
};
export const progressOverlayStyles = {
position: 'absolute' as const,
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(90deg, #4caf50 0%, #8bc34a 100%)',
borderRadius: 4,
transformOrigin: 'left' as const
};
export const stepProgressOverlayStyles = {
position: 'absolute' as const,
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(90deg, #4caf50 0%, #8bc34a 100%)',
borderRadius: 5,
transformOrigin: 'left' as const
};

View File

@@ -0,0 +1,724 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Box,
Grid,
Paper,
LinearProgress,
Chip,
IconButton,
Alert,
CircularProgress,
Card
} from '@mui/material';
import {
Close as CloseIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Refresh as RefreshIcon,
Schedule as ScheduleIcon,
TrendingUp as TrendingUpIcon,
School as SchoolIcon,
DataUsage as DataUsageIcon,
ViewModule as ViewModuleIcon,
Devices as DevicesIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
// Import existing components for reuse
import DataSourceTransparency from '../DataSourceTransparency';
import ProgressIndicator from '../ProgressIndicator';
// Import panel components
import {
LiveProgressPanel,
QualityGatesPanel,
DataSourcePanel,
StepResultsPanel,
EducationalPanel,
useCalendarGenerationPolling,
type CalendarGenerationProgress,
type QualityScores
} from './calendarGenerationModalPanels';
// Import styles
import {
dialogStyles,
contentContainerStyles,
progressBarContainerStyles,
progressBarStyles,
stepProgressBarStyles,
getStepIndicatorStyles,
getStepCardStyles,
stepCircleBaseStyles,
getStepCircleColor,
tabButtonStyles,
activityIndicatorStyles,
qualityScoreContainerStyles,
getQualityScoreBackground,
qualityScoreInnerStyles,
dataSourceCardStyles,
dataSourceIconStyles,
getDataSourceIconColor,
qualityMetricsContainerStyles,
getMetricColor,
stepResultsCardStyles,
stepResultsHeaderStyles,
stepResultsContentStyles,
loadingContainerStyles,
loadingContentStyles,
animationDurations,
animationEasing,
springConfig,
staggerDelay,
cardStaggerDelay,
fadeInUp,
fadeInLeft,
scaleIn,
slideInStaggered,
hoverLift,
hoverScale,
tapScale,
pulseAnimation,
smallPulseAnimation,
colorPulseAnimation,
progressFillAnimation,
progressOverlayStyles,
stepProgressOverlayStyles
} from './CalendarGenerationModal.styles';
// Types
interface CalendarGenerationModalProps {
open: boolean;
onClose: () => void;
sessionId: string;
initialConfig: CalendarConfig;
onComplete: (results: CalendarGenerationResults) => void;
onError: (error: string) => void;
}
interface CalendarConfig {
userId: string;
strategyId: string;
calendarType: 'monthly' | 'quarterly' | 'yearly';
platforms: string[];
duration: number;
postingFrequency: 'daily' | 'weekly' | 'biweekly';
}
interface CalendarGenerationResults {
calendar: CalendarData;
qualityScores: QualityScores;
insights: GenerationInsights;
recommendations: Recommendations;
exportData: ExportData;
}
interface CalendarData {
id: string;
title: string;
description: string;
startDate: string;
endDate: string;
content: CalendarContent[];
themes: Theme[];
platforms: Platform[];
}
interface CalendarContent {
id: string;
title: string;
description: string;
contentType: string;
platform: string;
scheduledDate: string;
theme: string;
keywords: string[];
}
interface Theme {
id: string;
name: string;
description: string;
weekNumber: number;
contentTypes: string[];
}
interface Platform {
id: string;
name: string;
contentCount: number;
postingSchedule: PostingSchedule[];
}
interface PostingSchedule {
day: string;
time: string;
contentType: string;
}
// QualityScores type imported from panels
interface GenerationInsights {
contentGaps: ContentGap[];
keywordOpportunities: KeywordOpportunity[];
audienceInsights: AudienceInsight[];
platformPerformance: PlatformPerformance[];
}
interface ContentGap {
id: string;
title: string;
description: string;
impact: number;
priority: 'high' | 'medium' | 'low';
estimatedTraffic: number;
}
interface KeywordOpportunity {
id: string;
keyword: string;
searchVolume: number;
competition: number;
relevance: number;
estimatedTraffic: number;
}
interface AudienceInsight {
id: string;
segment: string;
demographics: string[];
preferences: string[];
engagementRate: number;
bestTimes: string[];
}
interface PlatformPerformance {
id: string;
platform: string;
engagementRate: number;
reach: number;
conversionRate: number;
bestContentTypes: string[];
}
interface Recommendations {
contentMix: ContentMixRecommendation;
postingSchedule: PostingScheduleRecommendation;
platformStrategy: PlatformStrategyRecommendation;
optimizationTips: string[];
}
interface ContentMixRecommendation {
educational: number;
thoughtLeadership: number;
engagement: number;
promotional: number;
reasoning: string;
}
interface PostingScheduleRecommendation {
bestDays: string[];
bestTimes: string[];
frequency: string;
reasoning: string;
}
interface PlatformStrategyRecommendation {
primaryPlatforms: string[];
contentDistribution: Record<string, number>;
crossPlatformStrategy: string;
}
interface ExportData {
calendarJson: string;
insightsCsv: string;
recommendationsPdf: string;
qualityReport: string;
}
// Polling hook imported from panels
// Types imported from panels
// Remove mock data completely - no fallback
// const mockProgressData: CalendarGenerationProgress = { ... };
const CalendarGenerationModal: React.FC<CalendarGenerationModalProps> = ({
open,
onClose,
sessionId,
initialConfig,
onComplete,
onError
}) => {
const [activeTab, setActiveTab] = useState(0);
const [educationalPanelExpanded, setEducationalPanelExpanded] = useState(false);
const [expandedSections, setExpandedSections] = useState({
dataSources: true,
progress: true,
educational: false,
messages: true,
stepResults: true
});
// Use polling hook for real backend data only
const { progress, isPolling, error, startPolling, stopPolling } = useCalendarGenerationPolling(sessionId);
// Use only real progress data - no fallback to mock data
const currentProgress = progress;
useEffect(() => {
if (open && sessionId) {
// Start real polling when modal opens
startPolling();
} else if (!open) {
stopPolling();
}
}, [open, sessionId, startPolling, stopPolling]);
useEffect(() => {
if (currentProgress?.status === 'completed') {
// Handle completion
console.log('Calendar generation completed');
} else if (currentProgress?.status === 'error') {
onError(currentProgress.errors[0]?.message || 'Unknown error');
}
}, [currentProgress?.status, currentProgress?.errors, onError]);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
};
const getQualityColor = (score: number) => {
if (score >= 0.9) return 'success';
if (score >= 0.8) return 'warning';
return 'error';
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
return 'success';
case 'error':
return 'error';
case 'initializing':
return 'info';
default:
return 'primary';
}
};
const getStepIcon = (stepNumber: number) => {
switch (stepNumber) {
case 1:
return <SchoolIcon />;
case 2:
return <DataUsageIcon />;
case 3:
return <TrendingUpIcon />;
case 4:
return <ScheduleIcon />;
case 5:
return <ViewModuleIcon />;
case 6:
return <DevicesIcon />;
default:
return <ScheduleIcon />;
}
};
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="xl"
fullWidth
PaperProps={{
sx: dialogStyles.paper
}}
>
<DialogTitle>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Typography variant="h5">
Calendar Generation Progress
</Typography>
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
</Box>
</DialogTitle>
<DialogContent>
{!currentProgress ? (
// Loading state when no progress data is available
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
<Box sx={loadingContainerStyles}>
<Box sx={loadingContentStyles}>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
>
<CircularProgress size={60} />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.5 }}
>
<Typography variant="h6" sx={{ mt: 2 }}>
Initializing Calendar Generation...
</Typography>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5, duration: 0.5 }}
>
<Typography variant="body2" color="text.secondary">
Please wait while we set up your generation session
</Typography>
</motion.div>
</Box>
</Box>
</motion.div>
) : (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
<Grid container spacing={2}>
{/* Header Section with Enhanced Animations */}
<Grid item xs={12}>
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<Paper elevation={1} sx={{ p: 2, mb: 2 }}>
<Grid container spacing={2} alignItems="center">
{/* Progress Bar with Animation */}
<Grid item xs={12}>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="body2" color="text.secondary">
Overall Progress
</Typography>
<Box sx={progressBarContainerStyles}>
<LinearProgress
variant="determinate"
value={currentProgress.overallProgress}
sx={progressBarStyles}
/>
<motion.div
initial={{ scaleX: 0 }}
animate={{ scaleX: currentProgress.overallProgress / 100 }}
transition={{ duration: animationDurations.slow, ease: animationEasing.easeOut }}
style={progressOverlayStyles}
/>
</Box>
<motion.div
key={currentProgress.overallProgress}
initial={{ scale: 1.2, color: '#1976d2' }}
animate={{ scale: 1, color: 'inherit' }}
transition={{ duration: 0.3 }}
>
<Typography variant="body2" color="text.secondary">
{Math.round(currentProgress.overallProgress)}%
</Typography>
</motion.div>
</Box>
</Grid>
{/* Step Indicators with Staggered Animation */}
<Grid item xs={12}>
<Box display="flex" alignItems="center" gap={1}>
{[1, 2, 3, 4, 5, 6].map((step, index) => (
<motion.div
key={step}
initial={{ opacity: 0, x: -20, scale: 0.8 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
transition={{
delay: index * 0.2,
duration: 0.5,
ease: "easeOut"
}}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Box
display="flex"
alignItems="center"
gap={1}
sx={getStepIndicatorStyles(currentProgress.currentStep, step)}
>
<motion.div
animate={{
rotate: currentProgress.currentStep === step ? [0, 10, -10, 0] : 0,
scale: currentProgress.currentStep === step ? 1.1 : 1
}}
transition={{ duration: 0.5 }}
>
{getStepIcon(step)}
</motion.div>
<Typography variant="body2">
Step {step}
</Typography>
{currentProgress.qualityScores[`step${step}` as keyof QualityScores] > 0 && (
<motion.div
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.5, type: "spring", stiffness: 200 }}
>
<Chip
label={`${Math.round(currentProgress.qualityScores[`step${step}` as keyof QualityScores] * 100)}%`}
size="small"
color={getQualityColor(currentProgress.qualityScores[`step${step}` as keyof QualityScores])}
/>
</motion.div>
)}
</Box>
</motion.div>
))}
</Box>
</Grid>
{/* Quality Score and Status with Pulse Animation */}
<Grid item xs={6}>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="body2" color="text.secondary">
Overall Quality:
</Typography>
<motion.div
animate={pulseAnimation}
transition={{
duration: 2,
repeat: Infinity,
ease: animationEasing.easeInOut
}}
>
<Chip
label={`${Math.round(currentProgress.qualityScores.overall * 100)}%`}
color={getQualityColor(currentProgress.qualityScores.overall)}
size="small"
/>
</motion.div>
</Box>
</Grid>
<Grid item xs={6}>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="body2" color="text.secondary">
Status:
</Typography>
<motion.div
key={currentProgress.status}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<Chip
label={currentProgress.status}
color={getStatusColor(currentProgress.status)}
size="small"
/>
</motion.div>
</Box>
</Grid>
</Grid>
</Paper>
</motion.div>
</Grid>
{/* Main Content Area */}
<Grid item xs={12}>
<Box sx={{ width: '100%' }}>
{/* Tabs with Enhanced Animations */}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
<Grid container spacing={1}>
{[
{ id: 0, label: 'Live Progress' },
{ id: 1, label: 'Step Results' },
{ id: 2, label: 'Data Sources' },
{ id: 3, label: 'Quality Gates' }
].map((tab, index) => (
<Grid item key={tab.id}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1, duration: 0.3 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant={activeTab === tab.id ? 'contained' : 'outlined'}
size="small"
onClick={() => setActiveTab(tab.id)}
sx={tabButtonStyles}
>
{tab.label}
</Button>
</motion.div>
</Grid>
))}
</Grid>
</Box>
{/* Tab Content with Enhanced Transitions */}
<AnimatePresence mode="wait">
{activeTab === 0 && (
<motion.div
key="live-progress"
{...fadeInLeft}
transition={{ duration: 0.4, ease: animationEasing.easeInOut }}
>
<LiveProgressPanel
progress={currentProgress}
isPolling={isPolling}
/>
</motion.div>
)}
{activeTab === 1 && (
<motion.div
key="step-results"
{...fadeInLeft}
transition={{ duration: 0.4, ease: animationEasing.easeInOut }}
>
<StepResultsPanel
stepResults={currentProgress.stepResults}
qualityScores={currentProgress.qualityScores}
/>
</motion.div>
)}
{activeTab === 2 && (
<motion.div
key="data-sources"
{...fadeInLeft}
transition={{ duration: 0.4, ease: animationEasing.easeInOut }}
>
<DataSourcePanel
currentStep={currentProgress.currentStep}
stepResults={currentProgress.stepResults}
/>
</motion.div>
)}
{activeTab === 3 && (
<motion.div
key="quality-gates"
{...fadeInLeft}
transition={{ duration: 0.4, ease: animationEasing.easeInOut }}
>
<QualityGatesPanel
qualityScores={currentProgress.qualityScores}
stepResults={currentProgress.stepResults}
currentStep={currentProgress.currentStep}
/>
</motion.div>
)}
</AnimatePresence>
</Box>
</Grid>
{/* Educational Panel with Animation */}
<Grid item xs={12}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8, duration: 0.5 }}
>
<EducationalPanel
content={currentProgress.educationalContent}
currentStep={currentProgress.currentStep}
isExpanded={educationalPanelExpanded}
onToggleExpanded={() => setEducationalPanelExpanded(!educationalPanelExpanded)}
/>
</motion.div>
</Grid>
</Grid>
</motion.div>
)}
</DialogContent>
<DialogActions>
<Box display="flex" gap={1}>
{currentProgress && currentProgress.status !== 'completed' && currentProgress.status !== 'error' && (
<motion.div
whileHover={hoverScale}
whileTap={tapScale}
>
<Button
variant="outlined"
color="error"
onClick={async () => {
try {
await fetch(`/api/content-planning/calendar-generation/cancel/${sessionId}`, {
method: 'DELETE',
});
onClose();
} catch (error) {
console.error('Error cancelling generation:', error);
}
}}
>
Cancel Generation
</Button>
</motion.div>
)}
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant="outlined"
onClick={onClose}
>
Close
</Button>
</motion.div>
{currentProgress && currentProgress.status === 'completed' && (
<motion.div
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
whileHover={hoverScale}
whileTap={tapScale}
>
<Button
variant="contained"
onClick={() => {
// Handle completion
console.log('Calendar generation completed');
onComplete({
calendar: {} as any,
qualityScores: currentProgress.qualityScores,
insights: {} as any,
recommendations: {} as any,
exportData: {} as any
});
}}
>
View Calendar
</Button>
</motion.div>
)}
</Box>
</DialogActions>
</Dialog>
);
};
// Components imported from panels
export default CalendarGenerationModal;

View File

@@ -0,0 +1,257 @@
# Calendar Generation Modal
## Overview
The CalendarGenerationModal is a specialized transparency modal for the 12-step calendar generation process. It provides real-time progress tracking, educational content, and detailed insights for each step of the calendar generation workflow.
## Features
### ✅ Implemented (Phase 1A - Modal Foundation)
1. **Core Modal Structure**
- Full-screen dialog with proper Material-UI integration
- Responsive design with proper sizing and layout
- Close functionality and action buttons
2. **Progress Tracking**
- Overall progress bar with percentage display
- Step indicators for Phase 1 (Steps 1-3)
- Quality score chips with color coding
- Status indicators (initializing, step1, step2, step3, completed, error)
3. **Tabbed Interface**
- Live Progress tab
- Step Results tab
- Data Sources tab
- Quality Gates tab
- Smooth animations with Framer Motion
4. **Educational Panel**
- Expandable educational content section
- Step-specific tips and examples
- User-friendly toggle interface
5. **Polling Integration**
- Custom hook for real-time progress updates
- Error handling and retry logic
- Configurable polling intervals
6. **Mock Data Support**
- Development-ready mock data for Phase 1
- Realistic progress simulation
- Quality scores and educational content
## File Structure
```
CalendarGenerationModal/
├── index.ts # Main exports
├── types.ts # TypeScript interfaces
├── CalendarGenerationModal.tsx # Main modal component
├── TestModal.tsx # Test component
└── README.md # This file
```
## Usage
### Basic Usage
```tsx
import { CalendarGenerationModal } from './CalendarGenerationModal';
const MyComponent = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const handleComplete = (results) => {
console.log('Calendar generation completed:', results);
setIsModalOpen(false);
};
const handleError = (error) => {
console.error('Calendar generation error:', error);
setIsModalOpen(false);
};
return (
<CalendarGenerationModal
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
sessionId="session-123"
initialConfig={{
userId: 'user123',
strategyId: 'strategy456',
calendarType: 'monthly',
platforms: ['LinkedIn', 'Twitter', 'Website'],
duration: 30,
postingFrequency: 'daily'
}}
onComplete={handleComplete}
onError={handleError}
/>
);
};
```
### Testing
Use the TestModal component to verify functionality:
```tsx
import { TestCalendarGenerationModal } from './CalendarGenerationModal';
// Add to your app for testing
<TestCalendarGenerationModal />
```
## Configuration
### CalendarConfig Interface
```typescript
interface CalendarConfig {
userId: string;
strategyId: string;
calendarType: 'monthly' | 'quarterly' | 'yearly';
platforms: string[];
duration: number;
postingFrequency: 'daily' | 'weekly' | 'biweekly';
}
```
### Polling Configuration
The modal uses a polling mechanism with the following defaults:
- Polling interval: 2 seconds
- Retry interval: 5 seconds (on error)
- Endpoint: `/api/calendar-generation/progress/${sessionId}`
## Integration Points
### Backend Integration
The modal expects the following API endpoints:
1. **Progress Endpoint**: `GET /api/calendar-generation/progress/{sessionId}`
- Returns real-time progress data
- Includes step results, quality scores, and educational content
2. **Completion Handling**:
- Modal automatically handles completion state
- Calls `onComplete` callback with results
- Calls `onError` callback on failures
### Frontend Integration
1. **State Management**: Ready for Zustand store integration
2. **Routing**: Can be integrated with React Router
3. **Theming**: Uses Material-UI theme system
4. **Accessibility**: Built with accessibility best practices
## Development Status
### ✅ Completed
- Modal foundation and structure
- Progress tracking UI
- Tabbed interface
- Educational panel
- Polling mechanism
- Mock data for Phase 1
- TypeScript types
- Test component
### 🔄 Next Steps (Phase 1B)
1. **Enhanced Step Results Panel**
- Detailed step result display
- Data source attribution
- Quality gate validation results
2. **Data Source Transparency**
- Integration with existing DataSourceTransparency component
- Real data source attribution
- Confidence scores and timestamps
3. **Quality Gates Panel**
- Real-time quality gate validation
- Pass/fail status indicators
- Recommendations and improvements
4. **Backend Integration**
- Connect to real Phase 1 backend endpoints
- Replace mock data with live data
- Error handling for real API calls
### 📋 Future Enhancements (Phase 2+)
1. **Advanced Animations**
- Step transition animations
- Progress bar animations
- Loading states and spinners
2. **User Preferences**
- Transparency level settings
- Educational content preferences
- Auto-expand options
3. **Export Functionality**
- Progress reports
- Quality analysis exports
- Educational content downloads
## Technical Details
### Dependencies
- React 18+
- Material-UI (MUI) v5
- Framer Motion
- TypeScript
### Performance Considerations
- Lazy loading of tab content
- Optimized re-renders with React.memo
- Efficient polling with useCallback
- Minimal bundle size impact
### Accessibility Features
- ARIA labels and descriptions
- Keyboard navigation support
- Screen reader compatibility
- High contrast mode support
## Troubleshooting
### Common Issues
1. **Import Errors**
- Ensure all dependencies are installed
- Check TypeScript configuration
- Verify file paths and exports
2. **Polling Issues**
- Check network connectivity
- Verify API endpoint availability
- Review browser console for errors
3. **Styling Issues**
- Ensure Material-UI theme is properly configured
- Check for CSS conflicts
- Verify responsive breakpoints
### Debug Mode
Enable debug logging by setting the environment variable:
```bash
REACT_APP_DEBUG_CALENDAR_MODAL=true
```
## Contributing
When contributing to this component:
1. Follow the existing code structure
2. Add TypeScript types for new features
3. Include test cases for new functionality
4. Update this README for new features
5. Ensure accessibility compliance
## License
This component is part of the ALwrity project and follows the same licensing terms.

View File

@@ -0,0 +1,66 @@
import React, { useState } from 'react';
import { Button, Box, Typography } from '@mui/material';
import CalendarGenerationModal from './CalendarGenerationModal';
const TestCalendarGenerationModal: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const handleOpenModal = () => {
setIsModalOpen(true);
};
const handleCloseModal = () => {
setIsModalOpen(false);
};
const handleComplete = (results: any) => {
console.log('Calendar generation completed:', results);
setIsModalOpen(false);
};
const handleError = (error: string) => {
console.error('Calendar generation error:', error);
setIsModalOpen(false);
};
const mockConfig = {
userId: 'user123',
strategyId: 'strategy456',
calendarType: 'monthly' as const,
platforms: ['LinkedIn', 'Twitter', 'Website'],
duration: 30,
postingFrequency: 'daily' as const
};
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Calendar Generation Modal Test
</Typography>
<Button
variant="contained"
onClick={handleOpenModal}
sx={{ mb: 2 }}
>
Open Calendar Generation Modal
</Button>
<CalendarGenerationModal
open={isModalOpen}
onClose={handleCloseModal}
sessionId="test-session-123"
initialConfig={mockConfig}
onComplete={handleComplete}
onError={handleError}
/>
<Typography variant="body1" color="text.secondary">
This test component allows you to verify the CalendarGenerationModal functionality.
The modal will display mock data for Phase 1 (Steps 1-3) with a 94% quality score.
</Typography>
</Box>
);
};
export default TestCalendarGenerationModal;

View File

@@ -0,0 +1,174 @@
import React, { useState } from 'react';
import {
Button,
Box,
Typography,
Paper,
Grid
} from '@mui/material';
import CalendarGenerationModal from './CalendarGenerationModal';
// Mock data for testing Phase 2 integration
const mockPhase2Progress = {
status: 'in_progress',
currentStep: 4,
stepProgress: 75,
overallProgress: 45,
stepResults: {
1: {
stepName: 'Content Strategy Analysis',
executionTime: '2.3s',
qualityScore: 0.94,
dataSourcesUsed: ['Content Strategy', 'Business Goals', 'Target Audience']
},
2: {
stepName: 'Gap Analysis and Opportunity Identification',
executionTime: '3.1s',
qualityScore: 0.89,
dataSourcesUsed: ['Gap Analysis', 'Keyword Research', 'Competitor Analysis']
},
3: {
stepName: 'Audience and Platform Strategy',
executionTime: '2.8s',
qualityScore: 0.92,
dataSourcesUsed: ['Audience Data', 'Platform Performance', 'Content Mix Analysis']
},
4: {
stepName: 'Calendar Framework and Timeline',
executionTime: '1.9s',
qualityScore: 0.91,
dataSourcesUsed: ['Calendar Configuration', 'Timeline Optimization', 'Duration Control']
}
},
qualityScores: {
overall: 0.91,
step1: 0.94,
step2: 0.89,
step3: 0.92,
step4: 0.91,
step5: 0.0,
step6: 0.0,
step7: 0.0,
step8: 0.0,
step9: 0.0,
step10: 0.0,
step11: 0.0,
step12: 0.0
},
transparencyMessages: [
'Step 4: Calendar Framework and Timeline completed with 91% quality score',
'Calendar structure optimized for your posting preferences',
'Timeline duration validated against business goals'
],
educationalContent: [
{
title: 'Calendar Framework & Timeline',
description: 'Building the structural foundation of your content calendar with optimal timing and duration control.',
tips: [
'Optimize posting frequency for your audience',
'Consider timezone and peak engagement hours',
'Balance content types across the timeline',
'Ensure strategic alignment with business goals'
]
}
],
errors: [],
warnings: []
};
const TestPhase2Integration: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [sessionId] = useState('test-phase2-session-123');
const [calendarConfig] = useState({
userId: '1',
strategyId: '1',
calendarType: 'monthly' as const,
platforms: ['LinkedIn', 'Twitter', 'Blog'],
duration: 30,
postingFrequency: 'weekly' as const
});
const handleComplete = (results: any) => {
console.log('Calendar generation completed:', results);
setIsModalOpen(false);
};
const handleError = (error: string) => {
console.error('Calendar generation error:', error);
setIsModalOpen(false);
};
return (
<Box sx={{ p: 3 }}>
<Paper elevation={2} sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>
Phase 2 Frontend Integration Test
</Typography>
<Typography variant="body1" color="text.secondary" paragraph>
This test verifies that the frontend properly displays Phase 2 steps (4-6) with:
</Typography>
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<Typography variant="h6" gutterBottom>
What's Implemented:
</Typography>
<ul>
<li>Step indicators for Steps 1-6</li>
<li>Step-specific icons for Phase 2</li>
<li>Educational content for Steps 4-6</li>
<li>Data source panel updates for Phase 2</li>
<li>Quality score display for all steps</li>
</ul>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="h6" gutterBottom>
🧪 Test Features:
</Typography>
<ul>
<li>Mock Phase 2 progress data</li>
<li>Step 4 completion simulation</li>
<li>Quality scores for Steps 1-4</li>
<li>Educational content for Step 4</li>
<li>Data sources for Step 4</li>
</ul>
</Grid>
</Grid>
<Button
variant="contained"
size="large"
onClick={() => setIsModalOpen(true)}
sx={{ mr: 2 }}
>
Test Phase 2 Modal
</Button>
<Button
variant="outlined"
size="large"
onClick={() => {
// Simulate backend call
console.log('Simulating backend Phase 2 completion...');
setIsModalOpen(true);
}}
>
Simulate Backend Integration
</Button>
</Paper>
<CalendarGenerationModal
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
sessionId={sessionId}
initialConfig={calendarConfig}
onComplete={handleComplete}
onError={handleError}
/>
</Box>
);
};
export default TestPhase2Integration;

View File

@@ -0,0 +1,352 @@
import React from 'react';
import {
Paper,
Typography,
Box,
Grid,
Chip,
Card,
Alert
} from '@mui/material';
// Import styles
import {
dataSourceCardStyles,
dataSourceIconStyles,
getDataSourceIconColor,
qualityMetricsContainerStyles,
getMetricColor
} from '../CalendarGenerationModal.styles';
interface DataSourcePanelProps {
currentStep?: number;
stepResults?: Record<number, any>;
}
const DataSourcePanel: React.FC<DataSourcePanelProps> = ({
currentStep = 1,
stepResults = {}
}) => {
// Get data sources for current step
const getStepDataSources = (step: number) => {
switch (step) {
case 1:
return [
{
name: "Content Strategy",
description: "Your existing content strategy and business goals",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Updated 2 hours ago",
icon: "✓",
iconColor: "strategy"
},
{
name: "Business Goals",
description: "KPI mapping and strategic objectives",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Updated 2 hours ago",
icon: "🎯",
iconColor: "primary"
},
{
name: "Target Audience",
description: "Audience personas and demographics",
confidence: "Medium Confidence",
confidenceColor: "warning" as const,
lastUpdated: "Updated 1 day ago",
icon: "👥",
iconColor: "info"
}
];
case 2:
return [
{
name: "Gap Analysis",
description: "Content gaps and opportunity identification",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "📊",
iconColor: "info"
},
{
name: "Keyword Research",
description: "High-value keywords and search volume data",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "🔍",
iconColor: "primary"
},
{
name: "Competitor Analysis",
description: "Competitive insights and differentiation strategies",
confidence: "Medium Confidence",
confidenceColor: "warning" as const,
lastUpdated: "Updated 3 hours ago",
icon: "🏆",
iconColor: "secondary"
}
];
case 3:
return [
{
name: "Audience Data",
description: "Detailed audience personas and preferences",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Updated 1 day ago",
icon: "👥",
iconColor: "info"
},
{
name: "Platform Performance",
description: "Historical platform engagement metrics",
confidence: "Medium Confidence",
confidenceColor: "warning" as const,
lastUpdated: "Updated 3 days ago",
icon: "📈",
iconColor: "secondary"
},
{
name: "Content Mix Analysis",
description: "Optimal content type distribution",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "🎨",
iconColor: "primary"
}
];
case 4:
return [
{
name: "Calendar Configuration",
description: "User posting preferences and calendar settings",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Just configured",
icon: "📅",
iconColor: "primary"
},
{
name: "Timeline Optimization",
description: "Optimal posting times and frequency analysis",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "⏰",
iconColor: "info"
},
{
name: "Duration Control",
description: "Calendar duration and structure validation",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "📏",
iconColor: "secondary"
}
];
case 5:
return [
{
name: "Content Pillars",
description: "Strategic content pillar definitions from Step 1",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "From Step 1",
icon: "🏗️",
iconColor: "primary"
},
{
name: "Timeline Structure",
description: "Calendar framework from Step 4",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "From Step 4",
icon: "📅",
iconColor: "info"
},
{
name: "Theme Development",
description: "Industry-specific theme generation",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "🎨",
iconColor: "secondary"
}
];
case 6:
return [
{
name: "Platform Performance",
description: "Platform-specific engagement data from Step 3",
confidence: "Medium Confidence",
confidenceColor: "warning" as const,
lastUpdated: "From Step 3",
icon: "📈",
iconColor: "secondary"
},
{
name: "Content Adaptation",
description: "Platform-specific content optimization",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "🔄",
iconColor: "primary"
},
{
name: "Cross-Platform Coordination",
description: "Multi-platform strategy alignment",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "🔗",
iconColor: "info"
}
];
default:
return [
{
name: "Content Strategy",
description: "Your existing content strategy and business goals",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Updated 2 hours ago",
icon: "✓",
iconColor: "strategy"
},
{
name: "Onboarding Data",
description: "Industry, audience, and platform preferences",
confidence: "Medium Confidence",
confidenceColor: "warning" as const,
lastUpdated: "Updated 1 day ago",
icon: "📊",
iconColor: "info"
},
{
name: "AI Analysis",
description: "AI-powered content and performance insights",
confidence: "High Confidence",
confidenceColor: "success" as const,
lastUpdated: "Real-time analysis",
icon: "🤖",
iconColor: "primary"
}
];
}
};
const currentDataSources = getStepDataSources(currentStep);
return (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Data Sources & Transparency
</Typography>
<Box mb={3}>
<Typography variant="body2" color="text.secondary" gutterBottom>
This calendar generation uses multiple data sources to ensure high-quality, personalized results.
{currentStep <= 6 && (
<span> Currently showing data sources for <strong>Step {currentStep}</strong>.</span>
)}
</Typography>
</Box>
{/* Data Source Attribution */}
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Data Sources Used {currentStep <= 6 && `(Step ${currentStep})`}
</Typography>
<Grid container spacing={2}>
{currentDataSources.map((source, index) => (
<Grid item xs={12} md={6} key={index}>
<Card variant="outlined" sx={dataSourceCardStyles}>
<Box display="flex" alignItems="center" gap={2} mb={1}>
<Box
sx={{
...dataSourceIconStyles,
backgroundColor: getDataSourceIconColor(source.iconColor)
}}
>
{source.icon}
</Box>
<Typography variant="subtitle2">{source.name}</Typography>
</Box>
<Typography variant="body2" color="text.secondary">
{source.description}
</Typography>
<Box display="flex" alignItems="center" gap={1} mt={1}>
<Chip label={source.confidence} size="small" color={source.confidenceColor} />
<Typography variant="caption" color="text.secondary">
{source.lastUpdated}
</Typography>
</Box>
</Card>
</Grid>
))}
</Grid>
</Box>
{/* Data Quality Metrics */}
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Data Quality Metrics
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Box sx={qualityMetricsContainerStyles}>
<Typography variant="h4" sx={{ color: getMetricColor('Overall Data Quality') }} gutterBottom>
94%
</Typography>
<Typography variant="body2">
Overall Data Quality
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={qualityMetricsContainerStyles}>
<Typography variant="h4" sx={{ color: getMetricColor('Data Completeness') }} gutterBottom>
87%
</Typography>
<Typography variant="body2">
Data Completeness
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={qualityMetricsContainerStyles}>
<Typography variant="h4" sx={{ color: getMetricColor('Data Freshness') }} gutterBottom>
91%
</Typography>
<Typography variant="body2">
Data Freshness
</Typography>
</Box>
</Grid>
</Grid>
</Box>
{/* Transparency Note */}
<Alert severity="info">
<Typography variant="body2">
<strong>Transparency Note:</strong> All data sources are processed securely and used only for calendar generation.
No personal data is shared with third parties. You can review and update your data sources in the settings.
</Typography>
</Alert>
</Paper>
);
};
export default DataSourcePanel;

View File

@@ -0,0 +1,202 @@
import React from 'react';
import {
Paper,
Typography,
Box,
IconButton,
Chip,
Accordion,
AccordionSummary,
AccordionDetails
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
School as SchoolIcon,
ExpandMore as ExpandMoreIcon,
Schedule as ScheduleIcon,
ViewModule as ViewModuleIcon,
Devices as DevicesIcon,
TrendingUp as TrendingUpIcon,
DataUsage as DataUsageIcon
} from '@mui/icons-material';
interface EducationalPanelProps {
content: any[];
currentStep: number;
isExpanded: boolean;
onToggleExpanded: () => void;
}
const EducationalPanel: React.FC<EducationalPanelProps> = ({
content,
currentStep,
isExpanded,
onToggleExpanded
}) => {
// Step-specific educational content
const getStepEducationalContent = (step: number) => {
switch (step) {
case 1:
return {
title: "Content Strategy Analysis",
description: "Analyzing your business goals, target audience, and content pillars to establish a strategic foundation.",
tips: [
"Review your business objectives and KPIs",
"Identify your target audience personas",
"Define your core content pillars",
"Align content with business goals"
],
icon: <SchoolIcon />
};
case 2:
return {
title: "Gap Analysis & Opportunities",
description: "Identifying content gaps, keyword opportunities, and competitive insights to optimize your strategy.",
tips: [
"Analyze competitor content strategies",
"Identify high-value keyword opportunities",
"Find content gaps in your niche",
"Prioritize opportunities by impact"
],
icon: <DataUsageIcon />
};
case 3:
return {
title: "Audience & Platform Strategy",
description: "Developing audience personas and platform-specific strategies for maximum engagement.",
tips: [
"Create detailed audience personas",
"Analyze platform performance metrics",
"Develop platform-specific content strategies",
"Optimize for each platform's unique features"
],
icon: <TrendingUpIcon />
};
case 4:
return {
title: "Calendar Framework & Timeline",
description: "Building the structural foundation of your content calendar with optimal timing and duration control.",
tips: [
"Optimize posting frequency for your audience",
"Consider timezone and peak engagement hours",
"Balance content types across the timeline",
"Ensure strategic alignment with business goals"
],
icon: <ScheduleIcon />
};
case 5:
return {
title: "Content Pillar Distribution",
description: "Mapping content pillars across your timeline to ensure balanced and strategic content distribution.",
tips: [
"Distribute pillars based on strategic importance",
"Maintain content variety and freshness",
"Ensure each pillar gets adequate coverage",
"Create thematic content clusters"
],
icon: <ViewModuleIcon />
};
case 6:
return {
title: "Platform-Specific Strategy",
description: "Optimizing content for each platform's unique characteristics and audience preferences.",
tips: [
"Adapt content format for each platform",
"Optimize posting times per platform",
"Maintain brand consistency across platforms",
"Leverage platform-specific features"
],
icon: <DevicesIcon />
};
default:
return {
title: "Calendar Generation",
description: "Creating your comprehensive content calendar with strategic alignment and optimization.",
tips: [
"Review all generated content",
"Validate strategic alignment",
"Check quality scores and recommendations",
"Customize based on your preferences"
],
icon: <ScheduleIcon />
};
}
};
const stepContent = getStepEducationalContent(currentStep);
return (
<Paper elevation={1} sx={{ p: 2 }}>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="h6">
Educational Content
</Typography>
<Chip
label={`Step ${currentStep}`}
size="small"
color="primary"
icon={stepContent.icon}
/>
</Box>
<IconButton onClick={onToggleExpanded} size="small">
{isExpanded ? <CheckCircleIcon /> : <SchoolIcon />}
</IconButton>
</Box>
{isExpanded && (
<Box>
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="subtitle1" fontWeight="bold">
{stepContent.title}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="text.secondary" gutterBottom>
{stepContent.description}
</Typography>
<Box mt={2}>
<Typography variant="subtitle2" gutterBottom>
Key Tips:
</Typography>
<Box display="flex" flexDirection="column" gap={1}>
{stepContent.tips.map((tip: string, index: number) => (
<Box key={index} display="flex" alignItems="center" gap={1}>
<CheckCircleIcon fontSize="small" color="primary" />
<Typography variant="body2" color="text.secondary">
{tip}
</Typography>
</Box>
))}
</Box>
</Box>
</AccordionDetails>
</Accordion>
{/* Additional educational content from backend */}
{content.length > 0 && content[0].title !== stepContent.title && (
<Box mt={2}>
<Typography variant="subtitle2" gutterBottom>
Additional Insights:
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
{content[0].description}
</Typography>
{content[0].tips && content[0].tips.length > 0 && (
<Box display="flex" flexWrap="wrap" gap={1} mt={1}>
{content[0].tips.map((tip: string, index: number) => (
<Chip key={index} label={tip} size="small" variant="outlined" />
))}
</Box>
)}
</Box>
)}
</Box>
)}
</Paper>
);
};
export default EducationalPanel;

View File

@@ -0,0 +1,370 @@
import React from 'react';
import {
Paper,
Typography,
Box,
Grid,
LinearProgress,
Chip,
CircularProgress,
Card
} from '@mui/material';
import { motion } from 'framer-motion';
// Import styles
import {
progressBarContainerStyles,
stepProgressBarStyles,
getStepCardStyles,
stepCircleBaseStyles,
getStepCircleColor,
activityIndicatorStyles,
animationDurations,
animationEasing,
springConfig,
staggerDelay,
smallPulseAnimation,
colorPulseAnimation,
stepProgressOverlayStyles
} from '../CalendarGenerationModal.styles';
// Types
interface QualityScores {
overall: number;
step1: number;
step2: number;
step3: number;
step4: number;
step5: number;
step6: number;
step7: number;
step8: number;
step9: number;
step10: number;
step11: number;
step12: number;
}
interface CalendarGenerationProgress {
status: 'initializing' | 'step1' | 'step2' | 'step3' | 'completed' | 'error';
currentStep: number;
stepProgress: number;
overallProgress: number;
stepResults: Record<number, any>;
qualityScores: QualityScores;
transparencyMessages: string[];
educationalContent: any[];
errors: any[];
warnings: any[];
}
interface LiveProgressPanelProps {
progress: CalendarGenerationProgress;
isPolling: boolean;
}
const LiveProgressPanel: React.FC<LiveProgressPanelProps> = ({ progress, isPolling }) => (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Live Progress
</Typography>
{/* Current Status with Enhanced Animation */}
<Box mb={3}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Box display="flex" alignItems="center" gap={2} mb={2}>
<motion.div
animate={{
rotate: isPolling ? 360 : 0,
scale: isPolling ? [1, 1.2, 1] : 1
}}
transition={{
rotate: { duration: 2, repeat: Infinity, ease: "linear" },
scale: { duration: 1, repeat: Infinity, ease: "easeInOut" }
}}
>
<CircularProgress size={20} />
</motion.div>
<motion.div
key={progress.status}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<Typography variant="subtitle1">
Current Step: {progress.currentStep} - {progress.status}
</Typography>
</motion.div>
</Box>
</motion.div>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="body2" color="text.secondary">
Step Progress:
</Typography>
<Box sx={progressBarContainerStyles}>
<LinearProgress
variant="determinate"
value={progress.stepProgress}
sx={stepProgressBarStyles}
/>
<motion.div
initial={{ scaleX: 0 }}
animate={{ scaleX: progress.stepProgress / 100 }}
transition={{ duration: 0.6, ease: animationEasing.easeOut }}
style={stepProgressOverlayStyles}
/>
</Box>
<motion.div
key={progress.stepProgress}
initial={{ scale: 1.2, color: '#1976d2' }}
animate={{ scale: 1, color: 'inherit' }}
transition={{ duration: 0.3 }}
>
<Typography variant="body2" color="text.secondary">
{Math.round(progress.stepProgress)}%
</Typography>
</motion.div>
</Box>
</Box>
{/* Step-by-Step Progress with Staggered Animation */}
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Step-by-Step Progress
</Typography>
<Grid container spacing={2}>
{[1, 2, 3].map((step, index) => (
<Grid item xs={12} md={4} key={step}>
<motion.div
initial={{ opacity: 0, y: 50, scale: 0.8 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{
delay: index * 0.2,
duration: 0.6,
ease: "easeOut",
type: "spring",
stiffness: 100
}}
whileHover={{
scale: 1.02,
y: -5,
transition: { duration: 0.2 }
}}
>
<Card
variant="outlined"
sx={getStepCardStyles(progress.currentStep, step)}
>
<Box display="flex" alignItems="center" gap={2} mb={1}>
<motion.div
animate={{
rotate: progress.currentStep === step ? [0, 10, -10, 0] : 0,
scale: progress.currentStep === step ? 1.1 : 1,
backgroundColor: progress.currentStep > step ? '#4caf50' :
progress.currentStep === step ? '#1976d2' : '#9e9e9e'
}}
transition={{
rotate: { duration: 0.5 },
scale: { duration: 0.3 },
backgroundColor: { duration: 0.3 }
}}
style={{
...stepCircleBaseStyles,
backgroundColor: getStepCircleColor(progress.currentStep, step)
}}
>
{progress.currentStep > step ? (
<motion.div
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: "spring", stiffness: 200, damping: 10 }}
>
</motion.div>
) : (
step
)}
</motion.div>
<Typography variant="subtitle2">
Step {step}
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" gutterBottom>
{step === 1 ? 'Content Strategy Analysis' :
step === 2 ? 'Gap Analysis & Opportunities' :
'Audience & Platform Strategy'}
</Typography>
{progress.currentStep >= step && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.3, type: "spring", stiffness: 200 }}
>
<Box display="flex" alignItems="center" gap={1}>
<motion.div
animate={smallPulseAnimation}
transition={{
duration: 2,
repeat: Infinity,
ease: animationEasing.easeInOut
}}
>
<Chip
label={`${Math.round(progress.qualityScores[`step${step}` as keyof QualityScores] * 100)}%`}
size="small"
color={progress.qualityScores[`step${step}` as keyof QualityScores] >= 0.9 ? 'success' :
progress.qualityScores[`step${step}` as keyof QualityScores] >= 0.8 ? 'warning' : 'error'}
/>
</motion.div>
<Typography variant="caption" color="text.secondary">
Quality Score
</Typography>
</Box>
</motion.div>
)}
</Card>
</motion.div>
</Grid>
))}
</Grid>
</Box>
{/* Recent Activity with Staggered Animation */}
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Recent Activity
</Typography>
<Box sx={{ maxHeight: 200, overflowY: 'auto' }}>
{progress.transparencyMessages.map((message, index) => (
<motion.div
key={`${message}-${index}`}
initial={{ opacity: 0, x: -20, scale: 0.95 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
transition={{
delay: index * 0.1,
duration: 0.4,
ease: "easeOut"
}}
>
<Box display="flex" alignItems="flex-start" gap={2} mb={1}>
<motion.div
animate={colorPulseAnimation}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
style={activityIndicatorStyles}
/>
<Typography variant="body2">
{message}
</Typography>
</Box>
</motion.div>
))}
</Box>
</Box>
{/* Performance Metrics with Counter Animation */}
<Box>
<Typography variant="subtitle1" gutterBottom>
Performance Metrics
</Typography>
<Grid container spacing={2}>
{[
{
value: progress.overallProgress,
label: 'Overall Progress',
color: 'primary.main',
suffix: '%'
},
{
value: progress.qualityScores.overall * 100,
label: 'Quality Score',
color: 'success.main',
suffix: '%'
},
{
value: progress.currentStep,
label: 'Steps Completed',
color: 'info.main',
suffix: '/3'
},
{
value: progress.errors.length,
label: 'Issues Found',
color: 'secondary.main',
suffix: ''
}
].map((metric, index) => (
<Grid item xs={12} md={3} key={index}>
<motion.div
initial={{ opacity: 0, y: 30, scale: 0.8 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{
delay: index * staggerDelay,
duration: animationDurations.medium,
type: springConfig.type,
stiffness: 100
}}
whileHover={{ scale: 1.05 }}
>
<Box textAlign="center" p={2}>
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
delay: index * 0.1 + 0.3,
type: "spring",
stiffness: 200,
damping: 10
}}
>
<Typography
variant="h5"
sx={{ color: metric.color }}
gutterBottom
>
<motion.span
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
delay: index * 0.1 + 0.5,
duration: 0.5,
ease: "easeOut"
}}
>
{Math.round(metric.value)}
</motion.span>
{metric.suffix}
</Typography>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: index * 0.1 + 0.8, duration: 0.5 }}
>
<Typography variant="body2">
{metric.label}
</Typography>
</motion.div>
</Box>
</motion.div>
</Grid>
))}
</Grid>
</Box>
</Paper>
);
export default LiveProgressPanel;

View File

@@ -0,0 +1,307 @@
import React from 'react';
import {
Paper,
Typography,
Box,
Grid,
Chip,
Card,
Accordion,
AccordionSummary,
AccordionDetails
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Error as ErrorIcon,
Schedule as ScheduleIcon,
ViewModule as ViewModuleIcon,
Devices as DevicesIcon,
ExpandMore as ExpandMoreIcon,
TrendingUp as TrendingUpIcon,
DataUsage as DataUsageIcon,
School as SchoolIcon
} from '@mui/icons-material';
// Import styles
import {
qualityScoreContainerStyles,
getQualityScoreBackground,
qualityScoreInnerStyles
} from '../CalendarGenerationModal.styles';
// Types
interface QualityScores {
overall: number;
step1: number;
step2: number;
step3: number;
step4: number;
step5: number;
step6: number;
step7: number;
step8: number;
step9: number;
step10: number;
step11: number;
step12: number;
}
interface QualityGatesPanelProps {
qualityScores: QualityScores;
stepResults: Record<number, any>;
currentStep?: number;
}
const QualityGatesPanel: React.FC<QualityGatesPanelProps> = ({
qualityScores,
stepResults,
currentStep = 1
}) => {
// Get step-specific quality gates
const getQualityGatesForStep = (step: number) => {
const gates = [];
// Phase 1 Quality Gates (Steps 1-3)
if (step >= 1) {
gates.push({
id: 'strategy_alignment',
title: 'Strategy Alignment',
description: 'Content strategy alignment with business goals',
score: qualityScores.step1,
icon: <SchoolIcon />,
category: 'Phase 1: Foundation'
});
}
if (step >= 2) {
gates.push({
id: 'content_quality',
title: 'Content Gap Analysis',
description: 'Content gaps and opportunity identification quality',
score: qualityScores.step2,
icon: <DataUsageIcon />,
category: 'Phase 1: Foundation'
});
}
if (step >= 3) {
gates.push({
id: 'platform_optimization',
title: 'Audience & Platform Strategy',
description: 'Platform-specific content optimization and audience alignment',
score: qualityScores.step3,
icon: <TrendingUpIcon />,
category: 'Phase 1: Foundation'
});
}
// Phase 2 Quality Gates (Steps 4-6)
if (step >= 4) {
gates.push({
id: 'calendar_framework',
title: 'Calendar Framework Quality',
description: 'Calendar structure, timeline optimization, and duration control',
score: qualityScores.step4,
icon: <ScheduleIcon />,
category: 'Phase 2: Structure'
});
}
if (step >= 5) {
gates.push({
id: 'pillar_distribution',
title: 'Content Pillar Distribution',
description: 'Balanced content pillar mapping and theme variety',
score: qualityScores.step5,
icon: <ViewModuleIcon />,
category: 'Phase 2: Structure'
});
}
if (step >= 6) {
gates.push({
id: 'platform_strategy',
title: 'Platform-Specific Strategy',
description: 'Cross-platform coordination and content adaptation quality',
score: qualityScores.step6,
icon: <DevicesIcon />,
category: 'Phase 2: Structure'
});
}
return gates;
};
const getQualityStatus = (score: number) => {
if (score >= 0.9) return { label: 'EXCELLENT', color: 'success' as const, icon: <CheckCircleIcon /> };
if (score >= 0.8) return { label: 'GOOD', color: 'warning' as const, icon: <CheckCircleIcon /> };
if (score >= 0.7) return { label: 'ACCEPTABLE', color: 'warning' as const, icon: <WarningIcon /> };
if (score > 0) return { label: 'NEEDS IMPROVEMENT', color: 'error' as const, icon: <ErrorIcon /> };
return { label: 'PENDING', color: 'default' as const, icon: <ScheduleIcon /> };
};
const currentQualityGates = getQualityGatesForStep(currentStep);
const gatesByCategory = currentQualityGates.reduce((acc, gate) => {
if (!acc[gate.category]) acc[gate.category] = [];
acc[gate.category].push(gate);
return acc;
}, {} as Record<string, typeof currentQualityGates>);
return (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Quality Gates & Validation
</Typography>
<Box mb={3}>
<Typography variant="body2" color="text.secondary" gutterBottom>
Quality gates ensure your calendar meets high standards for strategy alignment, content quality, and performance optimization.
{currentStep <= 6 && (
<span> Currently showing quality gates through <strong>Step {currentStep}</strong>.</span>
)}
</Typography>
</Box>
{/* Overall Quality Score */}
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Overall Quality Score
</Typography>
<Box display="flex" alignItems="center" gap={2}>
<Box
sx={{
...qualityScoreContainerStyles,
background: getQualityScoreBackground(qualityScores.overall)
}}
>
<Box sx={qualityScoreInnerStyles}>
{Math.round(qualityScores.overall * 100)}%
</Box>
</Box>
<Box>
<Typography variant="h6">
{qualityScores.overall >= 0.9 ? 'Excellent' : qualityScores.overall >= 0.8 ? 'Good' : 'Needs Improvement'}
</Typography>
<Typography variant="body2" color="text.secondary">
Calendar quality meets {qualityScores.overall >= 0.9 ? 'excellent' : qualityScores.overall >= 0.8 ? 'good' : 'minimum'} standards
</Typography>
</Box>
</Box>
</Box>
{/* Phase-Based Quality Gates */}
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Quality Gate Validation by Phase
</Typography>
{Object.entries(gatesByCategory).map(([category, gates]) => (
<Accordion key={category} defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="subtitle2" fontWeight="bold">
{category} ({gates.length} gates)
</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={2}>
{gates.map((gate) => {
const status = getQualityStatus(gate.score);
return (
<Grid item xs={12} md={6} key={gate.id}>
<Card variant="outlined" sx={{ p: 2 }}>
<Box display="flex" alignItems="center" gap={2} mb={1}>
<Box sx={{ color: status.color === 'success' ? 'success.main' : status.color === 'error' ? 'error.main' : 'warning.main' }}>
{gate.icon}
</Box>
<Typography variant="subtitle2">{gate.title}</Typography>
</Box>
<Typography variant="body2" color="text.secondary" gutterBottom>
{gate.description}
</Typography>
<Box display="flex" alignItems="center" gap={1}>
<Chip
label={status.label}
size="small"
color={status.color}
icon={status.icon}
/>
<Typography variant="caption" color="text.secondary">
Score: {gate.score > 0 ? `${Math.round(gate.score * 100)}%` : 'Pending'}
</Typography>
</Box>
</Card>
</Grid>
);
})}
</Grid>
</AccordionDetails>
</Accordion>
))}
</Box>
{/* Phase 2 Specific Quality Recommendations */}
{currentStep >= 4 && (
<Box mb={3}>
<Typography variant="subtitle1" gutterBottom>
Phase 2 Quality Recommendations
</Typography>
<Card variant="outlined" sx={{ p: 2 }}>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
<Box component="li" mb={1}>
<Typography variant="body2">
<strong>Calendar Framework:</strong> Ensure posting frequency aligns with audience engagement patterns
</Typography>
</Box>
<Box component="li" mb={1}>
<Typography variant="body2">
<strong>Content Pillar Balance:</strong> Maintain 30-40% educational, 25-35% thought leadership content
</Typography>
</Box>
<Box component="li" mb={1}>
<Typography variant="body2">
<strong>Platform Strategy:</strong> Customize content format and timing for each platform's best practices
</Typography>
</Box>
<Box component="li">
<Typography variant="body2">
<strong>Timeline Optimization:</strong> Consider timezone differences for global audiences
</Typography>
</Box>
</Box>
</Card>
</Box>
)}
{/* Quality Metrics Summary */}
<Box>
<Typography variant="subtitle1" gutterBottom>
Quality Metrics Summary
</Typography>
<Grid container spacing={2}>
{currentQualityGates.slice(0, 6).map((gate, index) => (
<Grid item xs={12} md={2} key={gate.id}>
<Box textAlign="center" p={2}>
<Typography
variant="h5"
color={gate.score >= 0.9 ? 'success.main' : gate.score >= 0.8 ? 'warning.main' : gate.score > 0 ? 'error.main' : 'text.secondary'}
gutterBottom
>
{gate.score > 0 ? `${Math.round(gate.score * 100)}%` : '--'}
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
{gate.title}
</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
</Paper>
);
};
export default QualityGatesPanel;

View File

@@ -0,0 +1,151 @@
import React from 'react';
import {
Paper,
Typography,
Box,
Chip,
Card
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
// Import styles
import {
stepResultsCardStyles,
stepResultsHeaderStyles,
stepResultsContentStyles
} from '../CalendarGenerationModal.styles';
// Types
interface QualityScores {
overall: number;
step1: number;
step2: number;
step3: number;
step4: number;
step5: number;
step6: number;
step7: number;
step8: number;
step9: number;
step10: number;
step11: number;
step12: number;
}
interface StepResultsPanelProps {
stepResults: Record<number, any>;
qualityScores: QualityScores;
}
const StepResultsPanel: React.FC<StepResultsPanelProps> = ({ stepResults, qualityScores }) => (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Step Results
</Typography>
{Object.entries(stepResults).map(([stepNumber, results]) => (
<Box key={stepNumber} mb={3}>
<Card variant="outlined" sx={stepResultsCardStyles}>
{/* Step Header */}
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={stepResultsHeaderStyles}>
{stepNumber}
</Box>
<Box>
<Typography variant="h6">
{results.stepName}
</Typography>
<Typography variant="body2" color="text.secondary">
Execution Time: {results.executionTime}
</Typography>
</Box>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Chip
label={`${Math.round(results.qualityScore * 100)}%`}
color={results.qualityScore >= 0.9 ? 'success' : results.qualityScore >= 0.8 ? 'warning' : 'error'}
size="small"
/>
<CheckCircleIcon color="success" />
</Box>
</Box>
{/* Data Sources Used */}
<Box mb={2}>
<Typography variant="subtitle2" gutterBottom>
Data Sources Used:
</Typography>
<Box display="flex" flexWrap="wrap" gap={1}>
{results.dataSourcesUsed.map((source: string, index: number) => (
<Chip
key={index}
label={source}
size="small"
variant="outlined"
color="primary"
/>
))}
</Box>
</Box>
{/* Step Results */}
<Box mb={2}>
<Typography variant="subtitle2" gutterBottom>
Results:
</Typography>
<Box sx={stepResultsContentStyles}>
{Object.entries(results.results).map(([key, value]) => (
<Box key={key} mb={1}>
<Typography variant="body2" fontWeight="bold" color="text.secondary">
{key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}:
</Typography>
<Typography variant="body2">
{Array.isArray(value) ? value.join(', ') : String(value)}
</Typography>
</Box>
))}
</Box>
</Box>
{/* Insights */}
<Box mb={2}>
<Typography variant="subtitle2" gutterBottom>
Key Insights:
</Typography>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{results.insights.map((insight: string, index: number) => (
<Box component="li" key={index} mb={0.5}>
<Typography variant="body2">
{insight}
</Typography>
</Box>
))}
</Box>
</Box>
{/* Recommendations */}
<Box>
<Typography variant="subtitle2" gutterBottom>
Recommendations:
</Typography>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{results.recommendations.map((rec: string, index: number) => (
<Box component="li" key={index} mb={0.5}>
<Typography variant="body2" color="primary">
{rec}
</Typography>
</Box>
))}
</Box>
</Box>
</Card>
</Box>
))}
</Paper>
);
export default StepResultsPanel;

View File

@@ -0,0 +1,7 @@
export { default as LiveProgressPanel } from './LiveProgressPanel';
export { default as QualityGatesPanel } from './QualityGatesPanel';
export { default as DataSourcePanel } from './DataSourcePanel';
export { default as StepResultsPanel } from './StepResultsPanel';
export { default as EducationalPanel } from './EducationalPanel';
export { default as useCalendarGenerationPolling } from './useCalendarGenerationPolling';
export type { CalendarGenerationProgress, QualityScores } from './useCalendarGenerationPolling';

View File

@@ -0,0 +1,98 @@
import { useState, useCallback } from 'react';
// Types
interface QualityScores {
overall: number;
step1: number;
step2: number;
step3: number;
step4: number;
step5: number;
step6: number;
step7: number;
step8: number;
step9: number;
step10: number;
step11: number;
step12: number;
}
interface CalendarGenerationProgress {
status: 'initializing' | 'step1' | 'step2' | 'step3' | 'completed' | 'error';
currentStep: number;
stepProgress: number;
overallProgress: number;
stepResults: Record<number, any>;
qualityScores: QualityScores;
transparencyMessages: string[];
educationalContent: any[];
errors: any[];
warnings: any[];
}
// Polling hook for calendar generation progress
const useCalendarGenerationPolling = (sessionId: string) => {
const [progress, setProgress] = useState<CalendarGenerationProgress | null>(null);
const [isPolling, setIsPolling] = useState(false);
const [error, setError] = useState<string | null>(null);
const startPolling = useCallback(async () => {
setIsPolling(true);
setError(null);
const poll = async () => {
try {
const response = await fetch(`/api/content-planning/calendar-generation/progress/${sessionId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Transform backend data to frontend format
const transformedProgress: CalendarGenerationProgress = {
status: data.status,
currentStep: data.current_step,
stepProgress: data.step_progress,
overallProgress: data.overall_progress,
stepResults: data.step_results,
qualityScores: data.quality_scores,
transparencyMessages: data.transparency_messages,
educationalContent: data.educational_content,
errors: data.errors,
warnings: data.warnings
};
setProgress(transformedProgress);
if (data.status === 'completed' || data.status === 'error') {
setIsPolling(false);
if (data.status === 'error') {
setError(data.errors?.[0]?.message || 'Unknown error occurred');
}
return;
}
// Continue polling every 2 seconds
setTimeout(poll, 2000);
} catch (error) {
console.error('Calendar generation polling error:', error);
setError(error instanceof Error ? error.message : 'Polling failed');
// Retry after 5 seconds
setTimeout(poll, 5000);
}
};
poll();
}, [sessionId]);
const stopPolling = useCallback(() => {
setIsPolling(false);
}, []);
return { progress, isPolling, error, startPolling, stopPolling };
};
export default useCalendarGenerationPolling;
export type { CalendarGenerationProgress, QualityScores };

View File

@@ -0,0 +1,3 @@
export { default as CalendarGenerationModal } from './CalendarGenerationModal';
export { default as TestCalendarGenerationModal } from './TestModal';
export * from './types';

View File

@@ -0,0 +1,755 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Box,
Grid,
Paper,
LinearProgress,
Chip,
IconButton,
Alert,
CircularProgress
} from '@mui/material';
import {
Close as CloseIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Refresh as RefreshIcon,
Schedule as ScheduleIcon,
TrendingUp as TrendingUpIcon,
School as SchoolIcon,
DataUsage as DataUsageIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
// Import existing components for reuse
import DataSourceTransparency from '../DataSourceTransparency';
import ProgressIndicator from '../ProgressIndicator';
// Types
interface CalendarGenerationModalProps {
open: boolean;
onClose: () => void;
sessionId: string;
initialConfig: CalendarConfig;
onComplete: (results: CalendarGenerationResults) => void;
onError: (error: string) => void;
}
interface CalendarConfig {
userId: string;
strategyId: string;
calendarType: 'monthly' | 'quarterly' | 'yearly';
platforms: string[];
duration: number;
postingFrequency: 'daily' | 'weekly' | 'biweekly';
}
interface CalendarGenerationResults {
calendar: CalendarData;
qualityScores: QualityScores;
insights: GenerationInsights;
recommendations: Recommendations;
exportData: ExportData;
}
interface CalendarData {
id: string;
title: string;
description: string;
startDate: string;
endDate: string;
content: CalendarContent[];
themes: Theme[];
platforms: Platform[];
}
interface CalendarContent {
id: string;
title: string;
description: string;
contentType: string;
platform: string;
scheduledDate: string;
theme: string;
keywords: string[];
}
interface Theme {
id: string;
name: string;
description: string;
weekNumber: number;
contentTypes: string[];
}
interface Platform {
id: string;
name: string;
contentCount: number;
postingSchedule: PostingSchedule[];
}
interface PostingSchedule {
day: string;
time: string;
contentType: string;
}
interface QualityScores {
overall: number;
step1: number;
step2: number;
step3: number;
step4: number;
step5: number;
step6: number;
step7: number;
step8: number;
step9: number;
step10: number;
step11: number;
step12: number;
}
interface GenerationInsights {
contentGaps: ContentGap[];
keywordOpportunities: KeywordOpportunity[];
audienceInsights: AudienceInsight[];
platformPerformance: PlatformPerformance[];
}
interface ContentGap {
id: string;
title: string;
description: string;
impact: number;
priority: 'high' | 'medium' | 'low';
estimatedTraffic: number;
}
interface KeywordOpportunity {
id: string;
keyword: string;
searchVolume: number;
competition: number;
relevance: number;
estimatedTraffic: number;
}
interface AudienceInsight {
id: string;
segment: string;
demographics: string[];
preferences: string[];
engagementRate: number;
bestTimes: string[];
}
interface PlatformPerformance {
id: string;
platform: string;
engagementRate: number;
reach: number;
conversionRate: number;
bestContentTypes: string[];
}
interface Recommendations {
contentMix: ContentMixRecommendation;
postingSchedule: PostingScheduleRecommendation;
platformStrategy: PlatformStrategyRecommendation;
optimizationTips: string[];
}
interface ContentMixRecommendation {
educational: number;
thoughtLeadership: number;
engagement: number;
promotional: number;
reasoning: string;
}
interface PostingScheduleRecommendation {
bestDays: string[];
bestTimes: string[];
frequency: string;
reasoning: string;
}
interface PlatformStrategyRecommendation {
primaryPlatforms: string[];
contentDistribution: Record<string, number>;
crossPlatformStrategy: string;
}
interface ExportData {
calendarJson: string;
insightsCsv: string;
recommendationsPdf: string;
qualityReport: string;
}
// Polling hook for calendar generation progress
const useCalendarGenerationPolling = (sessionId: string) => {
const [progress, setProgress] = useState<CalendarGenerationProgress | null>(null);
const [isPolling, setIsPolling] = useState(false);
const [error, setError] = useState<string | null>(null);
const startPolling = useCallback(async () => {
setIsPolling(true);
setError(null);
const poll = async () => {
try {
const response = await fetch(`/api/content-planning/calendar-generation/progress/${sessionId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
setProgress(data);
if (data.status === 'completed' || data.status === 'error') {
setIsPolling(false);
if (data.status === 'error') {
setError(data.error || 'Unknown error occurred');
}
return;
}
// Continue polling every 2 seconds
setTimeout(poll, 2000);
} catch (error) {
console.error('Calendar generation polling error:', error);
setError(error instanceof Error ? error.message : 'Polling failed');
// Retry after 5 seconds
setTimeout(poll, 5000);
}
};
poll();
}, [sessionId]);
const stopPolling = useCallback(() => {
setIsPolling(false);
}, []);
return { progress, isPolling, error, startPolling, stopPolling };
};
// Mock progress data for development (remove when backend is ready)
interface CalendarGenerationProgress {
status: 'initializing' | 'step1' | 'step2' | 'step3' | 'completed' | 'error';
currentStep: number;
stepProgress: number;
overallProgress: number;
stepResults: Record<number, any>;
qualityScores: QualityScores;
transparencyMessages: string[];
educationalContent: any[];
errors: any[];
warnings: any[];
}
// Mock progress data for Phase 1 testing
const mockProgressData: CalendarGenerationProgress = {
status: 'step1',
currentStep: 1,
stepProgress: 75,
overallProgress: 25,
stepResults: {
1: {
stepNumber: 1,
stepName: 'Content Strategy Analysis',
results: {
contentPillars: ['Educational', 'Thought Leadership', 'Product Updates', 'Industry Insights'],
targetAudience: ['Marketing Professionals', 'Business Owners', 'Content Creators'],
businessGoals: ['Increase Brand Awareness', 'Generate Leads', 'Establish Thought Leadership'],
strategyAlignment: 0.94
},
qualityScore: 0.94,
executionTime: '2.3s',
dataSourcesUsed: ['Content Strategy', 'Onboarding Data', 'AI Analysis'],
insights: [
'Content strategy shows strong alignment with business goals',
'4 content pillars identified with clear focus areas',
'3 distinct audience segments with specific preferences'
],
recommendations: [
'Focus on educational content (40%) for lead generation',
'Increase thought leadership content (30%) for brand awareness',
'Optimize content mix for platform-specific performance'
]
}
},
qualityScores: {
overall: 0.94,
step1: 0.94,
step2: 0.0,
step3: 0.0,
step4: 0.0,
step5: 0.0,
step6: 0.0,
step7: 0.0,
step8: 0.0,
step9: 0.0,
step10: 0.0,
step11: 0.0,
step12: 0.0
},
transparencyMessages: [
'Starting content strategy analysis...',
'Analyzing your content pillars and target audience...',
'Generating strategic insights with AI analysis...',
'Content strategy analysis completed with 94% quality score'
],
educationalContent: [
{
title: 'Content Strategy Analysis',
description: 'Understanding how your content strategy influences calendar generation',
level: 'intermediate',
category: 'strategy',
tips: [
'Your content pillars define the main themes for your calendar',
'Target audience data helps determine content timing and platforms',
'Business goals influence content mix and promotional frequency'
],
examples: [
'Educational content: 40% of calendar based on your strategy',
'Thought leadership: 30% aligned with your expertise areas',
'Engagement content: 20% to build audience relationships'
]
}
],
errors: [],
warnings: []
};
const CalendarGenerationModal: React.FC<CalendarGenerationModalProps> = ({
open,
onClose,
sessionId,
initialConfig,
onComplete,
onError
}) => {
const [activeTab, setActiveTab] = useState(0);
const [educationalPanelExpanded, setEducationalPanelExpanded] = useState(false);
const [expandedSections, setExpandedSections] = useState({
dataSources: true,
progress: true,
educational: false,
messages: true,
stepResults: true
});
// Use polling hook (replace with real implementation when backend is ready)
const { progress, isPolling, error, startPolling, stopPolling } = useCalendarGenerationPolling(sessionId);
// For development, use mock data
const currentProgress = progress || mockProgressData;
useEffect(() => {
if (open && sessionId) {
// For development, simulate polling
// startPolling();
} else if (!open) {
stopPolling();
}
}, [open, sessionId, startPolling, stopPolling]);
useEffect(() => {
if (currentProgress.status === 'completed') {
// Handle completion
console.log('Calendar generation completed');
} else if (currentProgress.status === 'error') {
onError(currentProgress.errors[0]?.message || 'Unknown error');
}
}, [currentProgress.status, currentProgress.errors, onError]);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
};
const getQualityColor = (score: number) => {
if (score >= 0.9) return 'success';
if (score >= 0.8) return 'warning';
return 'error';
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
return 'success';
case 'error':
return 'error';
case 'initializing':
return 'info';
default:
return 'primary';
}
};
const getStepIcon = (stepNumber: number) => {
switch (stepNumber) {
case 1:
return <SchoolIcon />;
case 2:
return <DataUsageIcon />;
case 3:
return <TrendingUpIcon />;
default:
return <ScheduleIcon />;
}
};
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="xl"
fullWidth
PaperProps={{
sx: {
height: '90vh',
maxHeight: '90vh'
}
}}
>
<DialogTitle>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Typography variant="h5">
Calendar Generation Progress
</Typography>
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
</Box>
</DialogTitle>
<DialogContent>
<Grid container spacing={2}>
{/* Header Section */}
<Grid item xs={12}>
<Paper elevation={1} sx={{ p: 2, mb: 2 }}>
<Grid container spacing={2} alignItems="center">
{/* Progress Bar */}
<Grid item xs={12}>
<Box display="flex" alignItems="center" gap={2}>
<Typography variant="body2" color="text.secondary">
Overall Progress
</Typography>
<LinearProgress
variant="determinate"
value={currentProgress.overallProgress}
sx={{ flexGrow: 1 }}
/>
<Typography variant="body2" color="text.secondary">
{Math.round(currentProgress.overallProgress)}%
</Typography>
</Box>
</Grid>
{/* Step Indicators */}
<Grid item xs={12}>
<Box display="flex" alignItems="center" gap={1}>
{[1, 2, 3].map((step) => (
<Box
key={step}
display="flex"
alignItems="center"
gap={1}
sx={{
p: 1,
borderRadius: 1,
backgroundColor: currentProgress.currentStep === step ? 'primary.light' : 'grey.100',
color: currentProgress.currentStep === step ? 'primary.contrastText' : 'text.secondary'
}}
>
{getStepIcon(step)}
<Typography variant="body2">
Step {step}
</Typography>
{currentProgress.qualityScores[`step${step}` as keyof QualityScores] > 0 && (
<Chip
label={`${Math.round(currentProgress.qualityScores[`step${step}` as keyof QualityScores] * 100)}%`}
size="small"
color={getQualityColor(currentProgress.qualityScores[`step${step}` as keyof QualityScores])}
/>
)}
</Box>
))}
</Box>
</Grid>
{/* Quality Score and Status */}
<Grid item xs={6}>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="body2" color="text.secondary">
Overall Quality:
</Typography>
<Chip
label={`${Math.round(currentProgress.qualityScores.overall * 100)}%`}
color={getQualityColor(currentProgress.qualityScores.overall)}
size="small"
/>
</Box>
</Grid>
<Grid item xs={6}>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="body2" color="text.secondary">
Status:
</Typography>
<Chip
label={currentProgress.status}
color={getStatusColor(currentProgress.status)}
size="small"
/>
</Box>
</Grid>
</Grid>
</Paper>
</Grid>
{/* Main Content Area */}
<Grid item xs={12}>
<Box sx={{ width: '100%' }}>
{/* Tabs */}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
<Grid container spacing={1}>
<Grid item>
<Button
variant={activeTab === 0 ? 'contained' : 'outlined'}
size="small"
onClick={() => setActiveTab(0)}
>
Live Progress
</Button>
</Grid>
<Grid item>
<Button
variant={activeTab === 1 ? 'contained' : 'outlined'}
size="small"
onClick={() => setActiveTab(1)}
>
Step Results
</Button>
</Grid>
<Grid item>
<Button
variant={activeTab === 2 ? 'contained' : 'outlined'}
size="small"
onClick={() => setActiveTab(2)}
>
Data Sources
</Button>
</Grid>
<Grid item>
<Button
variant={activeTab === 3 ? 'contained' : 'outlined'}
size="small"
onClick={() => setActiveTab(3)}
>
Quality Gates
</Button>
</Grid>
</Grid>
</Box>
{/* Tab Content */}
<AnimatePresence mode="wait">
{activeTab === 0 && (
<motion.div
key="live-progress"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<LiveProgressPanel
progress={currentProgress}
isPolling={isPolling}
/>
</motion.div>
)}
{activeTab === 1 && (
<motion.div
key="step-results"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<StepResultsPanel
stepResults={currentProgress.stepResults}
qualityScores={currentProgress.qualityScores}
/>
</motion.div>
)}
{activeTab === 2 && (
<motion.div
key="data-sources"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<DataSourcePanel />
</motion.div>
)}
{activeTab === 3 && (
<motion.div
key="quality-gates"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<QualityGatesPanel
qualityScores={currentProgress.qualityScores}
stepResults={currentProgress.stepResults}
/>
</motion.div>
)}
</AnimatePresence>
</Box>
</Grid>
{/* Educational Panel */}
<Grid item xs={12}>
<EducationalPanel
content={currentProgress.educationalContent}
currentStep={currentProgress.currentStep}
isExpanded={educationalPanelExpanded}
onToggleExpanded={() => setEducationalPanelExpanded(!educationalPanelExpanded)}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Box display="flex" gap={1}>
<Button
variant="outlined"
onClick={onClose}
>
Close
</Button>
{currentProgress.status === 'completed' && (
<Button
variant="contained"
onClick={() => {
// Handle completion
console.log('Calendar generation completed');
}}
>
View Calendar
</Button>
)}
</Box>
</DialogActions>
</Dialog>
);
};
// Placeholder components (to be implemented)
const LiveProgressPanel: React.FC<{ progress: CalendarGenerationProgress; isPolling: boolean }> = ({ progress, isPolling }) => (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Live Progress
</Typography>
<Box display="flex" alignItems="center" gap={2} mb={2}>
{isPolling && <CircularProgress size={20} />}
<Typography variant="body2">
Current Step: {progress.currentStep} - {progress.status}
</Typography>
</Box>
<Typography variant="body2" color="text.secondary">
Step Progress: {progress.stepProgress}%
</Typography>
</Paper>
);
const StepResultsPanel: React.FC<{ stepResults: Record<number, any>; qualityScores: QualityScores }> = ({ stepResults, qualityScores }) => (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Step Results
</Typography>
{Object.entries(stepResults).map(([stepNumber, results]) => (
<Box key={stepNumber} mb={2}>
<Typography variant="subtitle1">
Step {stepNumber}: {results.stepName}
</Typography>
<Typography variant="body2" color="text.secondary">
Quality Score: {Math.round(results.qualityScore * 100)}%
</Typography>
</Box>
))}
</Paper>
);
const DataSourcePanel: React.FC = () => (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Data Sources
</Typography>
<Typography variant="body2" color="text.secondary">
Data source transparency information will be displayed here.
</Typography>
</Paper>
);
const QualityGatesPanel: React.FC<{ qualityScores: QualityScores; stepResults: Record<number, any> }> = ({ qualityScores, stepResults }) => (
<Paper elevation={1} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Quality Gates
</Typography>
<Typography variant="body2" color="text.secondary">
Quality gate validation results will be displayed here.
</Typography>
</Paper>
);
const EducationalPanel: React.FC<{
content: any[];
currentStep: number;
isExpanded: boolean;
onToggleExpanded: () => void;
}> = ({ content, currentStep, isExpanded, onToggleExpanded }) => (
<Paper elevation={1} sx={{ p: 2 }}>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={1}>
<Typography variant="h6">
Educational Content
</Typography>
<IconButton onClick={onToggleExpanded} size="small">
{isExpanded ? <CheckCircleIcon /> : <SchoolIcon />}
</IconButton>
</Box>
{isExpanded && content.length > 0 && (
<Box>
<Typography variant="subtitle1" gutterBottom>
{content[0].title}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
{content[0].description}
</Typography>
<Typography variant="body2" color="text.secondary">
Tips: {content[0].tips.join(', ')}
</Typography>
</Box>
)}
</Paper>
);
export default CalendarGenerationModal;

View File

@@ -0,0 +1,251 @@
// Calendar Generation Modal Types
export interface CalendarGenerationModalProps {
open: boolean;
onClose: () => void;
sessionId: string;
initialConfig: CalendarConfig;
onComplete: (results: CalendarGenerationResults) => void;
onError: (error: string) => void;
}
export interface CalendarConfig {
userId: string;
strategyId: string;
calendarType: 'monthly' | 'quarterly' | 'yearly';
platforms: string[];
duration: number;
postingFrequency: 'daily' | 'weekly' | 'biweekly';
}
export interface CalendarGenerationResults {
calendar: CalendarData;
qualityScores: QualityScores;
insights: GenerationInsights;
recommendations: Recommendations;
exportData: ExportData;
}
export interface CalendarData {
id: string;
title: string;
description: string;
startDate: string;
endDate: string;
content: CalendarContent[];
themes: Theme[];
platforms: Platform[];
}
export interface CalendarContent {
id: string;
title: string;
description: string;
contentType: string;
platform: string;
scheduledDate: string;
theme: string;
keywords: string[];
}
export interface Theme {
id: string;
name: string;
description: string;
weekNumber: number;
contentTypes: string[];
}
export interface Platform {
id: string;
name: string;
contentCount: number;
postingSchedule: PostingSchedule[];
}
export interface PostingSchedule {
day: string;
time: string;
contentType: string;
}
export interface QualityScores {
overall: number;
step1: number;
step2: number;
step3: number;
step4: number;
step5: number;
step6: number;
step7: number;
step8: number;
step9: number;
step10: number;
step11: number;
step12: number;
}
export interface GenerationInsights {
contentGaps: ContentGap[];
keywordOpportunities: KeywordOpportunity[];
audienceInsights: AudienceInsight[];
platformPerformance: PlatformPerformance[];
}
export interface ContentGap {
id: string;
title: string;
description: string;
impact: number;
priority: 'high' | 'medium' | 'low';
estimatedTraffic: number;
}
export interface KeywordOpportunity {
id: string;
keyword: string;
searchVolume: number;
competition: number;
relevance: number;
estimatedTraffic: number;
}
export interface AudienceInsight {
id: string;
segment: string;
demographics: string[];
preferences: string[];
engagementRate: number;
bestTimes: string[];
}
export interface PlatformPerformance {
id: string;
platform: string;
engagementRate: number;
reach: number;
conversionRate: number;
bestContentTypes: string[];
}
export interface Recommendations {
contentMix: ContentMixRecommendation;
postingSchedule: PostingScheduleRecommendation;
platformStrategy: PlatformStrategyRecommendation;
optimizationTips: string[];
}
export interface ContentMixRecommendation {
educational: number;
thoughtLeadership: number;
engagement: number;
promotional: number;
reasoning: string;
}
export interface PostingScheduleRecommendation {
bestDays: string[];
bestTimes: string[];
frequency: string;
reasoning: string;
}
export interface PlatformStrategyRecommendation {
primaryPlatforms: string[];
contentDistribution: Record<string, number>;
crossPlatformStrategy: string;
}
export interface ExportData {
calendarJson: string;
insightsCsv: string;
recommendationsPdf: string;
qualityReport: string;
}
export interface CalendarGenerationProgress {
status: 'initializing' | 'step1' | 'step2' | 'step3' | 'completed' | 'error';
currentStep: number;
stepProgress: number;
overallProgress: number;
stepResults: Record<number, StepResult>;
qualityScores: QualityScores;
transparencyMessages: string[];
educationalContent: EducationalContent[];
errors: ErrorInfo[];
warnings: WarningInfo[];
}
export interface StepResult {
stepNumber: number;
stepName: string;
results: any;
qualityScore: number;
executionTime: string;
dataSourcesUsed: string[];
insights: string[];
recommendations: string[];
}
export interface EducationalContent {
title: string;
description: string;
level: 'beginner' | 'intermediate' | 'advanced';
category: 'strategy' | 'analysis' | 'optimization' | 'quality';
tips: string[];
examples: string[];
relatedConcepts: string[];
nextSteps: string[];
}
export interface ErrorInfo {
id: string;
message: string;
step: number;
timestamp: string;
severity: 'low' | 'medium' | 'high' | 'critical';
}
export interface WarningInfo {
id: string;
message: string;
step: number;
timestamp: string;
severity: 'low' | 'medium' | 'high';
}
export interface DataSourceAttribution {
id: string;
name: string;
type: 'strategy' | 'onboarding' | 'gap_analysis' | 'ai_analysis' | 'performance' | 'competitor';
confidence: number;
lastUpdated: string;
dataPoints: DataPoint[];
}
export interface DataPoint {
id: string;
name: string;
value: any;
confidence: number;
source: string;
timestamp: string;
}
export interface QualityGateResult {
id: string;
name: string;
status: 'passed' | 'failed' | 'warning';
score: number;
threshold: number;
details: string;
recommendations: string[];
}
export interface UserPreferences {
transparencyLevel: 'basic' | 'detailed' | 'expert';
educationalLevel: 'beginner' | 'intermediate' | 'advanced';
autoExpandResults: boolean;
showQualityDetails: boolean;
enableNotifications: boolean;
}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import {
Box,
Typography,
@@ -15,7 +15,10 @@ import {
Tooltip,
IconButton,
Alert,
FormHelperText
FormHelperText,
Button,
Chip,
LinearProgress
} from '@mui/material';
import {
CalendarToday as CalendarIcon,
@@ -24,12 +27,27 @@ import {
TrendingUp as TrendingUpIcon,
Public as PublicIcon,
AccessTime as AccessTimeIcon,
ContentPaste as ContentPasteIcon
ContentPaste as ContentPasteIcon,
AutoAwesome as AutoAwesomeIcon,
Lightbulb as LightbulbIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
// Import calendar-specific types
import { type CalendarConfig } from './types';
// Import simplified mapper
import {
generateSmartDefaults,
generateUserGuidance,
generateTransparencyIndicators,
applySmartDefaultsToConfig,
type SmartDefaults,
type UserGuidance,
type TransparencyIndicators
} from '../../../../services/strategyCalendarMapper';
interface CalendarConfigurationStepProps {
calendarConfig: CalendarConfig;
onConfigUpdate: (updates: Partial<CalendarConfig>) => void;
@@ -158,6 +176,87 @@ const CalendarConfigurationStep: React.FC<CalendarConfigurationStepProps> = ({
strategyContext,
isFromStrategyActivation = false
}) => {
// Smart defaults and guidance state
const [smartDefaults, setSmartDefaults] = useState<SmartDefaults | null>(null);
const [userGuidance, setUserGuidance] = useState<UserGuidance | null>(null);
const [transparencyIndicators, setTransparencyIndicators] = useState<TransparencyIndicators | null>(null);
const [showSmartDefaults, setShowSmartDefaults] = useState(true);
const [showUserGuidance, setShowUserGuidance] = useState(true);
const [showTransparency, setShowTransparency] = useState(true);
// Generate smart defaults and guidance when strategy context changes
useEffect(() => {
console.log('🎯 CalendarConfigurationStep: Strategy context changed:', {
hasStrategyContext: !!strategyContext,
hasStrategyData: !!strategyContext?.strategyData,
strategyDataType: strategyContext?.strategyData ? typeof strategyContext.strategyData : 'none'
});
if (strategyContext?.strategyData) {
console.log('🎯 CalendarConfigurationStep: Generating smart defaults from strategy data');
console.log('🎯 CalendarConfigurationStep: About to call generateSmartDefaults with:', strategyContext.strategyData);
const defaults = generateSmartDefaults(strategyContext.strategyData);
console.log('🎯 CalendarConfigurationStep: generateSmartDefaults returned:', defaults);
const guidance = generateUserGuidance(strategyContext.strategyData);
const transparency = generateTransparencyIndicators(strategyContext.strategyData);
console.log('🎯 CalendarConfigurationStep: Generated data:', {
defaults: defaults,
guidance: {
warnings: guidance.warnings.length,
recommendations: guidance.recommendations.length,
missingData: guidance.missingData.length
},
transparency: {
integrationLevel: transparency.integrationStatus.integrationLevel,
alignmentScore: transparency.strategyAlignment.alignmentScore
}
});
setSmartDefaults(defaults);
setUserGuidance(guidance);
setTransparencyIndicators(transparency);
console.log('🎯 CalendarConfigurationStep: Smart defaults generated:', {
calendarType: defaults.suggestedCalendarType,
postingFrequency: defaults.suggestedPostingFrequency,
platforms: defaults.suggestedPlatforms,
guidanceCount: guidance.warnings.length + guidance.recommendations.length + guidance.missingData.length
});
} else {
console.log('🎯 CalendarConfigurationStep: No strategy context available, using default defaults');
const defaults = generateSmartDefaults(null);
const guidance = generateUserGuidance(null);
const transparency = generateTransparencyIndicators(null);
setSmartDefaults(defaults);
setUserGuidance(guidance);
setTransparencyIndicators(transparency);
}
}, [strategyContext]);
// Validate timezone on component mount and when timezone changes
useEffect(() => {
const validTimezones = timeZones.map(tz => tz.value);
if (!validTimezones.includes(calendarConfig.timeZone)) {
console.log('🎯 CalendarConfigurationStep: Invalid timezone detected, fixing:', calendarConfig.timeZone);
// Fix invalid timezone
onConfigUpdate({ timeZone: 'America/New_York' });
}
}, [calendarConfig.timeZone, onConfigUpdate]);
// Apply smart defaults to configuration
const handleApplySmartDefaults = (applyAll: boolean = false) => {
if (smartDefaults) {
const updates = applySmartDefaultsToConfig(calendarConfig, smartDefaults, applyAll);
onConfigUpdate(updates);
console.log('🎯 CalendarConfigurationStep: Applied smart defaults:', updates);
}
};
// Enhanced calendar-specific handlers
const handleCalendarTypeChange = (type: 'weekly' | 'monthly' | 'quarterly') => {
onConfigUpdate({ calendarType: type });
@@ -190,7 +289,14 @@ const CalendarConfigurationStep: React.FC<CalendarConfigurationStepProps> = ({
};
const handleTimeZoneChange = (timezone: string) => {
onConfigUpdate({ timeZone: timezone });
// Ensure the timezone is valid
const validTimezones = timeZones.map(tz => tz.value);
if (validTimezones.includes(timezone)) {
onConfigUpdate({ timeZone: timezone });
} else {
// Fallback to a valid timezone
onConfigUpdate({ timeZone: 'America/New_York' });
}
};
const handleContentDistributionChange = (distribution: 'even' | 'frontloaded' | 'backloaded') => {
@@ -255,6 +361,308 @@ const CalendarConfigurationStep: React.FC<CalendarConfigurationStepProps> = ({
)}
</Box>
{/* Smart Defaults Section */}
{smartDefaults && (
<Card sx={{ ...ENHANCED_STYLES.card, mb: 3, background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%)' }}>
<CardContent sx={ENHANCED_STYLES.cardContent}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Box sx={{
p: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<AutoAwesomeIcon sx={{ color: 'white', fontSize: 24 }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#2c3e50' }}>
Smart Defaults
</Typography>
</Box>
<Button
variant="outlined"
size="small"
onClick={() => setShowSmartDefaults(!showSmartDefaults)}
startIcon={<LightbulbIcon />}
sx={{
borderColor: '#667eea',
color: '#667eea',
'&:hover': { borderColor: '#764ba2', color: '#764ba2' }
}}
>
{showSmartDefaults ? 'Hide' : 'Show'} Suggestions
</Button>
</Box>
{showSmartDefaults && (
<Box>
<Typography variant="body2" color="#666" sx={{ mb: 3 }}>
Based on your strategy data, here are our smart suggestions for optimal calendar configuration:
</Typography>
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={3}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.7)', borderRadius: 2, border: '1px solid rgba(102, 126, 234, 0.2)' }}>
<Typography variant="caption" color="#666" display="block">Suggested Calendar Type</Typography>
<Typography variant="body1" fontWeight={600} color="#2c3e50">
{smartDefaults.suggestedCalendarType.charAt(0).toUpperCase() + smartDefaults.suggestedCalendarType.slice(1)}
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={3}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.7)', borderRadius: 2, border: '1px solid rgba(102, 126, 234, 0.2)' }}>
<Typography variant="caption" color="#666" display="block">Suggested Posts per Week</Typography>
<Typography variant="body1" fontWeight={600} color="#2c3e50">
{smartDefaults.suggestedPostingFrequency}
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={3}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.7)', borderRadius: 2, border: '1px solid rgba(102, 126, 234, 0.2)' }}>
<Typography variant="caption" color="#666" display="block">Suggested Duration</Typography>
<Typography variant="body1" fontWeight={600} color="#2c3e50">
{smartDefaults.suggestedDuration} {smartDefaults.suggestedCalendarType === 'weekly' ? 'weeks' :
smartDefaults.suggestedCalendarType === 'monthly' ? 'months' : 'quarters'}
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={3}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.7)', borderRadius: 2, border: '1px solid rgba(102, 126, 234, 0.2)' }}>
<Typography variant="caption" color="#666" display="block">Suggested Platforms</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 0.5 }}>
{smartDefaults.suggestedPlatforms.slice(0, 2).map((platform: string, index: number) => (
<Chip
key={index}
label={platform}
size="small"
sx={{
bgcolor: 'rgba(102, 126, 234, 0.1)',
color: '#667eea',
fontSize: '0.7rem'
}}
/>
))}
{smartDefaults.suggestedPlatforms.length > 2 && (
<Chip
label={`+${smartDefaults.suggestedPlatforms.length - 2}`}
size="small"
sx={{
bgcolor: 'rgba(102, 126, 234, 0.1)',
color: '#667eea',
fontSize: '0.7rem'
}}
/>
)}
</Box>
</Box>
</Grid>
</Grid>
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Button
variant="contained"
size="small"
onClick={() => handleApplySmartDefaults(false)}
startIcon={<AutoAwesomeIcon />}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'&:hover': { background: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)' }
}}
>
Apply Smart Defaults
</Button>
<Button
variant="outlined"
size="small"
onClick={() => handleApplySmartDefaults(true)}
sx={{
borderColor: '#667eea',
color: '#667eea',
'&:hover': { borderColor: '#764ba2', color: '#764ba2' }
}}
>
Apply All Suggestions
</Button>
</Box>
</Box>
)}
</CardContent>
</Card>
)}
{/* User Guidance Section */}
{userGuidance && (
<Card sx={{ ...ENHANCED_STYLES.card, mb: 3, background: 'rgba(255, 248, 220, 0.3)' }}>
<CardContent sx={ENHANCED_STYLES.cardContent}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<Box sx={{
p: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<LightbulbIcon sx={{ color: 'white', fontSize: 24 }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#2c3e50' }}>
Strategy Guidance
</Typography>
</Box>
<Grid container spacing={2}>
{userGuidance.warnings.length > 0 && (
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 193, 7, 0.1)', borderRadius: 2, border: '1px solid rgba(255, 193, 7, 0.3)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<WarningIcon sx={{ color: '#f57c00', fontSize: 20 }} />
<Typography variant="subtitle2" fontWeight={600} color="#f57c00">
Warnings ({userGuidance.warnings.length})
</Typography>
</Box>
{userGuidance.warnings.slice(0, 2).map((warning: any, index: number) => (
<Typography key={index} variant="body2" color="#666" sx={{ mb: 0.5 }}>
{warning.message}
</Typography>
))}
</Box>
</Grid>
)}
{userGuidance.recommendations.length > 0 && (
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, bgcolor: 'rgba(76, 175, 80, 0.1)', borderRadius: 2, border: '1px solid rgba(76, 175, 80, 0.3)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<LightbulbIcon sx={{ color: '#2e7d32', fontSize: 20 }} />
<Typography variant="subtitle2" fontWeight={600} color="#2e7d32">
Recommendations ({userGuidance.recommendations.length})
</Typography>
</Box>
{userGuidance.recommendations.slice(0, 2).map((rec: any, index: number) => (
<Typography key={index} variant="body2" color="#666" sx={{ mb: 0.5 }}>
{rec.message}
</Typography>
))}
</Box>
</Grid>
)}
{userGuidance.missingData.length > 0 && (
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, bgcolor: 'rgba(33, 150, 243, 0.1)', borderRadius: 2, border: '1px solid rgba(33, 150, 243, 0.3)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<HelpIcon sx={{ color: '#1976d2', fontSize: 20 }} />
<Typography variant="subtitle2" fontWeight={600} color="#1976d2">
Missing Data ({userGuidance.missingData.length})
</Typography>
</Box>
{userGuidance.missingData.slice(0, 2).map((missing: any, index: number) => (
<Typography key={index} variant="body2" color="#666" sx={{ mb: 0.5 }}>
{missing.message}
</Typography>
))}
</Box>
</Grid>
)}
{/* Show success state when no issues */}
{userGuidance.warnings.length === 0 && userGuidance.recommendations.length === 0 && userGuidance.missingData.length === 0 && (
<Grid item xs={12}>
<Box sx={{ p: 2, bgcolor: 'rgba(76, 175, 80, 0.1)', borderRadius: 2, border: '1px solid rgba(76, 175, 80, 0.3)', textAlign: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1, mb: 1 }}>
<CheckCircleIcon sx={{ color: '#2e7d32', fontSize: 20 }} />
<Typography variant="subtitle2" fontWeight={600} color="#2e7d32">
Strategy Analysis Complete
</Typography>
</Box>
<Typography variant="body2" color="#666">
Your strategy data is comprehensive and ready for calendar generation. No issues or missing data detected.
</Typography>
</Box>
</Grid>
)}
</Grid>
</CardContent>
</Card>
)}
{/* Transparency Indicators */}
{transparencyIndicators && (
<Card sx={{ ...ENHANCED_STYLES.card, mb: 3, background: 'rgba(240, 248, 255, 0.3)' }}>
<CardContent sx={ENHANCED_STYLES.cardContent}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<Box sx={{
p: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #2196f3 0%, #1976d2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<TrendingUpIcon sx={{ color: 'white', fontSize: 24 }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#2c3e50' }}>
Strategy Integration Status
</Typography>
</Box>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.7)', borderRadius: 2 }}>
<Typography variant="subtitle2" fontWeight={600} color="#2c3e50" gutterBottom>
Integration Level
</Typography>
<Chip
label={transparencyIndicators.integrationStatus.integrationLevel.toUpperCase()}
color={
transparencyIndicators.integrationStatus.integrationLevel === 'full' ? 'success' :
transparencyIndicators.integrationStatus.integrationLevel === 'enhanced' ? 'primary' :
transparencyIndicators.integrationStatus.integrationLevel === 'basic' ? 'warning' : 'default'
}
size="small"
/>
{transparencyIndicators.integrationStatus.integrationBenefits.length > 0 && (
<Typography variant="body2" color="#666" sx={{ mt: 1 }}>
{transparencyIndicators.integrationStatus.integrationBenefits[0]}
</Typography>
)}
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.7)', borderRadius: 2 }}>
<Typography variant="subtitle2" fontWeight={600} color="#2c3e50" gutterBottom>
Strategy Alignment
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<LinearProgress
variant="determinate"
value={transparencyIndicators.strategyAlignment.alignmentScore}
sx={{
flexGrow: 1,
height: 8,
borderRadius: 4,
bgcolor: 'rgba(0, 0, 0, 0.1)',
'& .MuiLinearProgress-bar': {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
}
}}
/>
<Typography variant="body2" color="#666">
{transparencyIndicators.strategyAlignment.alignmentScore}%
</Typography>
</Box>
<Typography variant="body2" color="#666">
{transparencyIndicators.strategyAlignment.isAligned ? 'Strategy aligned' : 'Strategy not aligned'}
</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
)}
{/* Basic Calendar Setup */}
<Card sx={{ ...ENHANCED_STYLES.card, mb: 3 }}>
<CardContent sx={ENHANCED_STYLES.cardContent}>

View File

@@ -441,21 +441,7 @@ const GenerateCalendarStep: React.FC<GenerateCalendarStepProps> = ({
</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 */}
{/* Note: Generate button is handled by the stepper navigation above */}
{loading && (
<Box sx={{ mt: 3 }}>
<LinearProgress />

View File

@@ -260,6 +260,49 @@ export const useCalendarWizardState = (
return [state, actions];
};
// Helper function to get a valid timezone from user's timezone or fallback to default
const getValidTimezone = (userTimezone: string): string => {
const validTimezones = [
'America/New_York',
'America/Chicago',
'America/Denver',
'America/Los_Angeles',
'Europe/London',
'Europe/Paris',
'Asia/Tokyo',
'Asia/Shanghai',
'Australia/Sydney'
];
// If user's timezone is in our valid list, use it
if (validTimezones.includes(userTimezone)) {
return userTimezone;
}
// Otherwise, try to map common timezones to our valid options
const timezoneMap: { [key: string]: string } = {
'Asia/Calcutta': 'Asia/Tokyo', // Map IST to JST as closest option
'Asia/Kolkata': 'Asia/Tokyo', // Alternative IST name
'Asia/Colombo': 'Asia/Tokyo', // Sri Lanka time
'Asia/Dhaka': 'Asia/Tokyo', // Bangladesh time
'Asia/Karachi': 'Asia/Tokyo', // Pakistan time
'UTC': 'Europe/London', // UTC to GMT
'GMT': 'Europe/London', // GMT to London
'EST': 'America/New_York', // EST to Eastern Time
'PST': 'America/Los_Angeles', // PST to Pacific Time
'CST': 'America/Chicago', // CST to Central Time
'MST': 'America/Denver', // MST to Mountain Time
};
// Check if we have a mapping for the user's timezone
if (timezoneMap[userTimezone]) {
return timezoneMap[userTimezone];
}
// Default fallback
return 'America/New_York';
};
// Helper function to create default calendar config
const createDefaultConfig = (): CalendarConfig => {
return {
@@ -274,7 +317,7 @@ const createDefaultConfig = (): CalendarConfig => {
// Platform Scheduling
priorityPlatforms: ['LinkedIn', 'Twitter'],
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, // User's timezone
timeZone: getValidTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone), // User's timezone or fallback
// Calendar Preferences
excludeDates: [],

View File

@@ -146,25 +146,25 @@ const HeaderSection: React.FC<HeaderSectionProps> = ({
<Box sx={{ position: 'relative', zIndex: 1 }}>
{/* Main Header */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ flex: 1 }}>
<Typography
variant="h4"
gutterBottom
sx={{
fontWeight: 'bold',
background: 'linear-gradient(45deg, #fff, #f0f0f0)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
textShadow: '0 0 20px rgba(255,255,255,0.5)',
mb: 1
}}
>
AI Content Strategy Co-pilot
</Typography>
<Box sx={{ flex: 1 }}>
<Typography
variant="h4"
gutterBottom
sx={{
fontWeight: 'bold',
background: 'linear-gradient(45deg, #fff, #f0f0f0)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
textShadow: '0 0 20px rgba(255,255,255,0.5)',
mb: 1
}}
>
AI Content Strategy Co-pilot
</Typography>
<Typography variant="body1" sx={{ opacity: 0.9, fontSize: '0.9rem' }}>
Build a comprehensive content strategy with 30+ strategic inputs
</Typography>
Build a comprehensive content strategy with 30+ strategic inputs
</Typography>
</Box>
</Box>
@@ -300,41 +300,41 @@ const HeaderSection: React.FC<HeaderSectionProps> = ({
{/* Enhanced Status Chips */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2, flexWrap: 'wrap' }}>
{cacheStatus === 'cached' && (
<Chip
icon={<CheckCircleIcon />}
label={`${Object.keys(autoPopulatedFields).length} fields auto-populated`}
sx={{
backgroundColor: 'rgba(76, 175, 80, 0.2)',
color: 'white',
border: '1px solid rgba(76, 175, 80, 0.3)',
{cacheStatus === 'cached' && (
<Chip
icon={<CheckCircleIcon />}
label={`${Object.keys(autoPopulatedFields).length} fields auto-populated`}
sx={{
backgroundColor: 'rgba(76, 175, 80, 0.2)',
color: 'white',
border: '1px solid rgba(76, 175, 80, 0.3)',
'& .MuiChip-icon': { color: 'rgba(76, 175, 80, 0.8)' },
fontWeight: 500,
fontSize: '0.8rem'
}}
/>
)}
{dataSource && (
<Tooltip title="Click to view data source information">
<Chip
icon={<InfoIcon />}
label={`Source: ${dataSource}`}
onClick={() => setShowDataInfo(!showDataInfo)}
sx={{
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
cursor: 'pointer',
fontWeight: 500,
fontSize: '0.8rem',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.2)'
}
}}
/>
</Tooltip>
)}
)}
{dataSource && (
<Tooltip title="Click to view data source information">
<Chip
icon={<InfoIcon />}
label={`Source: ${dataSource}`}
onClick={() => setShowDataInfo(!showDataInfo)}
sx={{
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
cursor: 'pointer',
fontWeight: 500,
fontSize: '0.8rem',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.2)'
}
}}
/>
</Tooltip>
)}
{/* Category Distribution Chips */}
{Object.keys(fieldCountByCategory).length > 0 && (
@@ -351,155 +351,155 @@ const HeaderSection: React.FC<HeaderSectionProps> = ({
}}
/>
)}
</Box>
</Box>
{/* Data Source Information */}
<Collapse in={showDataInfo}>
<Alert
severity="info"
sx={{
mb: 2,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
border: '1px solid rgba(255, 255, 255, 0.2)',
color: 'white',
'& .MuiAlert-icon': { color: 'rgba(255, 255, 255, 0.8)' }
}}
>
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Data Source:</strong> {dataSource || 'Onboarding Database'}
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Input Data Points:</strong> {Object.keys(inputDataPoints).length} available
</Typography>
<Typography variant="body2">
<strong>Adaptive Monitoring:</strong> ALwrity continuously monitors databases for new data points to ensure you have the latest information.
</Typography>
</Alert>
</Collapse>
{/* Data Source Information */}
<Collapse in={showDataInfo}>
<Alert
severity="info"
sx={{
mb: 2,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
border: '1px solid rgba(255, 255, 255, 0.2)',
color: 'white',
'& .MuiAlert-icon': { color: 'rgba(255, 255, 255, 0.8)' }
}}
>
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Data Source:</strong> {dataSource || 'Onboarding Database'}
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Input Data Points:</strong> {Object.keys(inputDataPoints).length} available
</Typography>
<Typography variant="body2">
<strong>Adaptive Monitoring:</strong> ALwrity continuously monitors databases for new data points to ensure you have the latest information.
</Typography>
</Alert>
</Collapse>
{/* Conditional Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
{cacheStatus === 'cached' ? (
// Case 1: Data exists in cache - show refresh vs continue options
<>
{/* Conditional Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
{cacheStatus === 'cached' ? (
// Case 1: Data exists in cache - show refresh vs continue options
<>
<Tooltip title="Refresh with latest database data and AI analysis">
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={onRefreshAutofill}
disabled={loading}
sx={{
color: 'white',
borderColor: 'rgba(255, 255, 255, 0.3)',
'&:hover': {
borderColor: 'rgba(255, 255, 255, 0.5)',
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}}
>
{loading ? 'Refreshing...' : 'Refresh & Autofill Inputs'}
</Button>
</Tooltip>
<Tooltip title="Continue with current autofilled values">
<Button
variant="contained"
startIcon={<PlayArrowIcon />}
onClick={onContinueWithPresent}
sx={{
backgroundColor: 'rgba(255, 255, 255, 0.2)',
color: 'white',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.3)'
}
}}
>
Continue with Present Values
</Button>
</Tooltip>
</>
) : cacheStatus === 'partial' ? (
// Case 2: Partial data - show refresh option
<Tooltip title="Refresh with latest database data and AI analysis">
<Button
variant="outlined"
variant="contained"
startIcon={<RefreshIcon />}
onClick={onRefreshAutofill}
disabled={loading}
sx={{
backgroundColor: 'rgba(255, 193, 7, 0.8)',
color: 'white',
borderColor: 'rgba(255, 255, 255, 0.3)',
'&:hover': {
borderColor: 'rgba(255, 255, 255, 0.5)',
backgroundColor: 'rgba(255, 193, 7, 0.9)'
}
}}
>
{loading ? 'Refreshing...' : 'Refresh & Autofill Strategy Inputs'}
</Button>
</Tooltip>
) : (
// Case 3: No data - show initial autofill
<Tooltip title="Fetch latest data from database and autofill strategy inputs">
<Button
variant="contained"
startIcon={<RefreshIcon />}
onClick={onRefreshAutofill}
disabled={loading}
sx={{
backgroundColor: 'rgba(76, 175, 80, 0.8)',
color: 'white',
'&:hover': {
backgroundColor: 'rgba(76, 175, 80, 0.9)'
}
}}
>
{loading ? 'Autofilling...' : 'Refresh & Autofill Strategy Inputs'}
</Button>
</Tooltip>
)}
{/* Next Step Button - shown after autofill completion */}
{showNextButton && (
<Tooltip title="Scroll to review section and mark inputs as reviewed">
<Button
variant="contained"
startIcon={<ArrowDownwardIcon />}
onClick={onScrollToReview}
sx={{
background: 'linear-gradient(135deg, #4caf50 0%, #66bb6a 50%, #81c784 100%)',
color: 'white',
fontWeight: 600,
'&:hover': {
background: 'linear-gradient(135deg, #66bb6a 0%, #81c784 50%, #a5d6a7 100%)',
transform: 'translateY(-1px)'
},
transition: 'all 0.3s ease'
}}
>
Next: Review Strategy Inputs & Create Strategy
</Button>
</Tooltip>
)}
{/* Know More Details Button - shown when autofill data exists */}
{hasAutofillData && Object.keys(autoPopulatedFields).length > 0 && (
<Tooltip title="View detailed information about autofill data sources and AI analysis">
<Button
variant="text"
startIcon={<VisibilityIcon />}
onClick={() => setShowTransparencyModal(true)}
sx={{
color: 'rgba(255, 255, 255, 0.8)',
'&:hover': {
color: 'white',
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}}
>
{loading ? 'Refreshing...' : 'Refresh & Autofill Inputs'}
Know More Details
</Button>
</Tooltip>
<Tooltip title="Continue with current autofilled values">
<Button
variant="contained"
startIcon={<PlayArrowIcon />}
onClick={onContinueWithPresent}
sx={{
backgroundColor: 'rgba(255, 255, 255, 0.2)',
color: 'white',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.3)'
}
}}
>
Continue with Present Values
</Button>
</Tooltip>
</>
) : cacheStatus === 'partial' ? (
// Case 2: Partial data - show refresh option
<Tooltip title="Refresh with latest database data and AI analysis">
<Button
variant="contained"
startIcon={<RefreshIcon />}
onClick={onRefreshAutofill}
disabled={loading}
sx={{
backgroundColor: 'rgba(255, 193, 7, 0.8)',
color: 'white',
'&:hover': {
backgroundColor: 'rgba(255, 193, 7, 0.9)'
}
}}
>
{loading ? 'Refreshing...' : 'Refresh & Autofill Strategy Inputs'}
</Button>
</Tooltip>
) : (
// Case 3: No data - show initial autofill
<Tooltip title="Fetch latest data from database and autofill strategy inputs">
<Button
variant="contained"
startIcon={<RefreshIcon />}
onClick={onRefreshAutofill}
disabled={loading}
sx={{
backgroundColor: 'rgba(76, 175, 80, 0.8)',
color: 'white',
'&:hover': {
backgroundColor: 'rgba(76, 175, 80, 0.9)'
}
}}
>
{loading ? 'Autofilling...' : 'Refresh & Autofill Strategy Inputs'}
</Button>
</Tooltip>
)}
{/* Next Step Button - shown after autofill completion */}
{showNextButton && (
<Tooltip title="Scroll to review section and mark inputs as reviewed">
<Button
variant="contained"
startIcon={<ArrowDownwardIcon />}
onClick={onScrollToReview}
sx={{
background: 'linear-gradient(135deg, #4caf50 0%, #66bb6a 50%, #81c784 100%)',
color: 'white',
fontWeight: 600,
'&:hover': {
background: 'linear-gradient(135deg, #66bb6a 0%, #81c784 50%, #a5d6a7 100%)',
transform: 'translateY(-1px)'
},
transition: 'all 0.3s ease'
}}
>
Next: Review Strategy Inputs & Create Strategy
</Button>
</Tooltip>
)}
{/* Know More Details Button - shown when autofill data exists */}
{hasAutofillData && Object.keys(autoPopulatedFields).length > 0 && (
<Tooltip title="View detailed information about autofill data sources and AI analysis">
<Button
variant="text"
startIcon={<VisibilityIcon />}
onClick={() => setShowTransparencyModal(true)}
sx={{
color: 'rgba(255, 255, 255, 0.8)',
'&:hover': {
color: 'white',
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}}
>
Know More Details
</Button>
</Tooltip>
)}
)}
</Box>
</Box>
</Paper>

View File

@@ -0,0 +1,335 @@
import React from 'react';
import {
Box,
Card,
CardContent,
CardHeader,
Typography,
Grid,
Avatar,
Chip
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Speed as SpeedIcon,
BugReport as BugReportIcon,
Storage as StorageIcon,
Timeline as TimelineIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
BarChart, Bar, PieChart, Pie, Cell, AreaChart, Area, RadarChart, PolarGrid,
PolarAngleAxis, PolarRadiusAxis, Radar
} from 'recharts';
interface ChartData {
name: string;
requests: number;
avgTime: number;
errors: number;
hitRate: number;
}
interface MonitoringChartsProps {
chartData: ChartData[];
cachePerformance: {
hits: number;
misses: number;
hit_rate: number;
};
apiPerformance: {
recent_requests: number;
recent_errors: number;
error_rate: number;
};
}
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
const MonitoringCharts: React.FC<MonitoringChartsProps> = ({
chartData,
cachePerformance,
apiPerformance
}) => {
// Generate time series data for line chart
const timeSeriesData = chartData.map((item, index) => ({
time: `${index + 1}`,
requests: item.requests,
errors: item.errors,
avgTime: item.avgTime * 1000, // Convert to milliseconds for better visualization
}));
// Generate radar chart data
const radarData = [
{ metric: 'Performance', value: 100 - apiPerformance.error_rate },
{ metric: 'Reliability', value: 100 - (apiPerformance.recent_errors / Math.max(apiPerformance.recent_requests, 1)) * 100 },
{ metric: 'Cache Hit Rate', value: cachePerformance.hit_rate },
{ metric: 'Response Time', value: Math.max(0, 100 - (chartData.reduce((acc, item) => acc + item.avgTime, 0) / Math.max(chartData.length, 1)) * 1000) },
{ metric: 'Error Rate', value: Math.max(0, 100 - apiPerformance.error_rate) },
];
// Generate pie chart data for cache performance
const cacheData = [
{ name: 'Cache Hits', value: cachePerformance.hits, color: '#00C49F' },
{ name: 'Cache Misses', value: cachePerformance.misses, color: '#FF8042' },
];
return (
<Grid container spacing={3}>
{/* Line Chart - Request Trends */}
<Grid item xs={12} md={6}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<Card sx={{ height: '100%', background: 'rgba(255,255,255,0.95)' }}>
<CardHeader
avatar={<Avatar sx={{ bgcolor: 'primary.main' }}><TimelineIcon /></Avatar>}
title="Request Trends"
subheader="Real-time request and error patterns"
/>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<LineChart data={timeSeriesData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="requests"
stroke="#8884d8"
strokeWidth={2}
dot={{ fill: '#8884d8', strokeWidth: 2, r: 4 }}
/>
<Line
type="monotone"
dataKey="errors"
stroke="#ff0000"
strokeWidth={2}
dot={{ fill: '#ff0000', strokeWidth: 2, r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
</motion.div>
</Grid>
{/* Area Chart - Response Times */}
<Grid item xs={12} md={6}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<Card sx={{ height: '100%', background: 'rgba(255,255,255,0.95)' }}>
<CardHeader
avatar={<Avatar sx={{ bgcolor: 'info.main' }}><SpeedIcon /></Avatar>}
title="Response Times"
subheader="Average response time trends"
/>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<AreaChart data={timeSeriesData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Area
type="monotone"
dataKey="avgTime"
stroke="#82ca9d"
fill="#82ca9d"
fillOpacity={0.6}
/>
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
</motion.div>
</Grid>
{/* Bar Chart - Endpoint Performance */}
<Grid item xs={12} md={8}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<Card sx={{ background: 'rgba(255,255,255,0.95)' }}>
<CardHeader
avatar={<Avatar sx={{ bgcolor: 'success.main' }}><TrendingUpIcon /></Avatar>}
title="Endpoint Performance"
subheader="Request volume and error distribution"
/>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="requests" fill="#8884d8" radius={[4, 4, 0, 0]} />
<Bar dataKey="errors" fill="#ff0000" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</motion.div>
</Grid>
{/* Pie Chart - Cache Performance */}
<Grid item xs={12} md={4}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<Card sx={{ height: '100%', background: 'rgba(255,255,255,0.95)' }}>
<CardHeader
avatar={<Avatar sx={{ bgcolor: 'warning.main' }}><StorageIcon /></Avatar>}
title="Cache Performance"
subheader="Hit vs Miss distribution"
/>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={cacheData}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) => `${name} ${((percent || 0) * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{cacheData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
<Box sx={{ textAlign: 'center', mt: 2 }}>
<Typography variant="h6" color="primary">
{cachePerformance.hit_rate.toFixed(1)}%
</Typography>
<Typography variant="body2" color="text.secondary">
Overall Hit Rate
</Typography>
</Box>
</CardContent>
</Card>
</motion.div>
</Grid>
{/* Radar Chart - System Health */}
<Grid item xs={12}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<Card sx={{ background: 'rgba(255,255,255,0.95)' }}>
<CardHeader
avatar={<Avatar sx={{ bgcolor: 'error.main' }}><BugReportIcon /></Avatar>}
title="System Health Overview"
subheader="Multi-dimensional performance metrics"
action={
<Chip
label={`${100 - apiPerformance.error_rate}% Healthy`}
color={apiPerformance.error_rate > 5 ? 'error' : 'success'}
size="small"
/>
}
/>
<CardContent>
<ResponsiveContainer width="100%" height={400}>
<RadarChart data={radarData}>
<PolarGrid />
<PolarAngleAxis dataKey="metric" />
<PolarRadiusAxis angle={90} domain={[0, 100]} />
<Radar
name="Performance"
dataKey="value"
stroke="#8884d8"
fill="#8884d8"
fillOpacity={0.6}
/>
<Tooltip />
</RadarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</motion.div>
</Grid>
{/* Performance Metrics Cards */}
<Grid item xs={12}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={3}>
<Card sx={{ background: 'rgba(255,255,255,0.95)' }}>
<CardContent sx={{ textAlign: 'center' }}>
<Typography variant="h4" color="primary" gutterBottom>
{apiPerformance.recent_requests}
</Typography>
<Typography variant="body2" color="text.secondary">
Recent Requests
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Card sx={{ background: 'rgba(255,255,255,0.95)' }}>
<CardContent sx={{ textAlign: 'center' }}>
<Typography variant="h4" color="error" gutterBottom>
{apiPerformance.recent_errors}
</Typography>
<Typography variant="body2" color="text.secondary">
Recent Errors
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Card sx={{ background: 'rgba(255,255,255,0.95)' }}>
<CardContent sx={{ textAlign: 'center' }}>
<Typography variant="h4" color="success.main" gutterBottom>
{cachePerformance.hit_rate.toFixed(1)}%
</Typography>
<Typography variant="body2" color="text.secondary">
Cache Hit Rate
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Card sx={{ background: 'rgba(255,255,255,0.95)' }}>
<CardContent sx={{ textAlign: 'center' }}>
<Typography variant="h4" color="info.main" gutterBottom>
{chartData.length}
</Typography>
<Typography variant="body2" color="text.secondary">
Active Endpoints
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</motion.div>
</Grid>
</Grid>
);
};
export default MonitoringCharts;

View File

@@ -7,12 +7,7 @@ import {
Chip,
IconButton,
Collapse,
List,
ListItem,
ListItemIcon,
ListItemText,
Alert,
Button
Alert
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
@@ -26,223 +21,171 @@ import {
CalendarToday as CalendarIcon,
HealthAndSafety as HealthIcon
} from '@mui/icons-material';
import { ServiceStatus } from '../../../services/contentPlanningOrchestrator';
interface ProgressIndicatorProps {
serviceStatuses: ServiceStatus[];
onRefreshService: (serviceName: string) => void;
expanded?: boolean;
onToggleExpanded?: () => void;
}
const ProgressIndicator: React.FC<ProgressIndicatorProps> = ({
serviceStatuses,
onRefreshService,
expanded = false,
onToggleExpanded
}) => {
const getServiceIcon = (serviceName: string) => {
switch (serviceName) {
case 'Content Strategies':
return <StrategyIcon />;
case 'Gap Analysis':
return <SearchIcon />;
case 'AI Analytics':
return <AnalyticsIcon />;
case 'Calendar Events':
return <CalendarIcon />;
case 'System Health':
return <HealthIcon />;
default:
return <AnalyticsIcon />;
}
// Simple loading state for dashboard initialization
const [loadingProgress, setLoadingProgress] = React.useState(0);
const [loadingMessage, setLoadingMessage] = React.useState('Initializing dashboard...');
React.useEffect(() => {
// Simulate loading progress
const interval = setInterval(() => {
setLoadingProgress(prev => {
if (prev >= 100) {
clearInterval(interval);
return 100;
}
return prev + 10;
});
}, 200);
// Update loading messages
const messageInterval = setInterval(() => {
setLoadingMessage(prev => {
if (prev.includes('Loading content strategies...')) {
return 'Loading calendar data...';
} else if (prev.includes('Loading calendar data...')) {
return 'Loading analytics...';
} else if (prev.includes('Loading analytics...')) {
return 'Loading gap analysis...';
} else if (prev.includes('Loading gap analysis...')) {
return 'Dashboard ready!';
}
return 'Loading content strategies...';
});
}, 800);
return () => {
clearInterval(interval);
clearInterval(messageInterval);
};
}, []);
const getStatusColor = () => {
if (loadingProgress >= 100) return 'success';
if (loadingProgress >= 75) return 'primary';
if (loadingProgress >= 50) return 'warning';
return 'info';
};
const getStatusColor = (status: string) => {
switch (status) {
case 'success':
return 'success';
case 'error':
return 'error';
case 'loading':
return 'primary';
default:
return 'primary';
}
const getStatusIcon = () => {
if (loadingProgress >= 100) return <CheckCircleIcon color="success" />;
return <RefreshIcon sx={{ animation: 'spin 1s linear infinite' }} />;
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'success':
return <CheckCircleIcon color="success" />;
case 'error':
return <ErrorIcon color="error" />;
case 'loading':
return <RefreshIcon sx={{ animation: 'spin 1s linear infinite' }} />;
default:
return null;
}
};
const isLoading = serviceStatuses.some(status => status.status === 'loading');
const hasErrors = serviceStatuses.some(status => status.status === 'error');
const allComplete = serviceStatuses.every(status => status.status === 'success');
const overallProgress = serviceStatuses.reduce((acc, status) => acc + status.progress, 0) / serviceStatuses.length;
return (
<Paper
elevation={2}
sx={{
p: 2,
mb: 2,
border: hasErrors ? '1px solid #f44336' : '1px solid transparent',
backgroundColor: hasErrors ? 'rgba(244, 67, 54, 0.05)' : 'background.paper',
'@keyframes spin': {
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' }
}
border: '1px solid transparent',
backgroundColor: 'background.paper'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{isLoading && <RefreshIcon sx={{ animation: 'spin 1s linear infinite' }} />}
Content Planning Progress
{allComplete && <CheckCircleIcon color="success" />}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip
label={`${Math.round(overallProgress)}%`}
color={allComplete ? 'success' : isLoading ? 'primary' : 'default'}
{getStatusIcon()}
<Typography variant="subtitle2">
Dashboard Loading
</Typography>
<Chip
label={`${loadingProgress}%`}
size="small"
color={getStatusColor()}
variant="outlined"
/>
{onToggleExpanded && (
<IconButton size="small" onClick={onToggleExpanded}>
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
)}
</Box>
{onToggleExpanded && (
<IconButton size="small" onClick={onToggleExpanded}>
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
)}
</Box>
{/* Overall Progress Bar */}
<Box sx={{ mb: 2 }}>
<LinearProgress
variant="determinate"
value={overallProgress}
color={allComplete ? 'success' : isLoading ? 'primary' : 'inherit'}
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
<LinearProgress
variant="determinate"
value={loadingProgress}
color={getStatusColor()}
sx={{ mb: 2 }}
/>
{/* Status Messages */}
{isLoading && (
<Alert severity="info" sx={{ mb: 2 }}>
<Typography variant="body2">
Loading content planning data... This may take a few moments as we analyze your content strategy.
</Typography>
</Alert>
)}
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{loadingMessage}
</Typography>
{hasErrors && (
<Alert severity="error" sx={{ mb: 2 }}>
<Typography variant="body2">
Some services encountered errors. You can refresh individual services below.
</Typography>
</Alert>
)}
{allComplete && (
<Alert severity="success" sx={{ mb: 2 }}>
<Typography variant="body2">
All content planning services are ready! Your dashboard is fully loaded.
</Typography>
</Alert>
)}
{/* Detailed Service Status */}
<Collapse in={expanded}>
<List dense>
{serviceStatuses.map((status, index) => (
<ListItem
key={index}
sx={{
border: '1px solid',
borderColor: getStatusColor(status.status) === 'error' ? 'error.main' : 'divider',
borderRadius: 1,
mb: 1,
backgroundColor: getStatusColor(status.status) === 'error' ? 'rgba(244, 67, 54, 0.05)' : 'transparent'
}}
>
<ListItemIcon>
{getServiceIcon(status.name)}
</ListItemIcon>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="body2" fontWeight="medium">
{status.name}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{getStatusIcon(status.status)}
<Chip
label={`${status.progress}%`}
size="small"
color={getStatusColor(status.status)}
variant="outlined"
/>
</Box>
</Box>
}
secondary={
<Box sx={{ mt: 1 }}>
<Typography variant="caption" color="text.secondary">
{status.message}
</Typography>
{status.error && (
<Typography variant="caption" color="error" sx={{ display: 'block', mt: 0.5 }}>
Error: {status.error}
</Typography>
)}
<Box sx={{ mt: 1 }}>
<LinearProgress
variant="determinate"
value={status.progress}
color={getStatusColor(status.status)}
sx={{ height: 4, borderRadius: 2 }}
/>
</Box>
</Box>
}
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" gutterBottom>
Loading Components:
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<StrategyIcon fontSize="small" />
<Typography variant="body2">Content Strategy</Typography>
<Chip
label={loadingProgress >= 20 ? "✓" : "..."}
size="small"
color={loadingProgress >= 20 ? "success" : "default"}
/>
{status.status === 'error' && (
<IconButton
size="small"
onClick={() => onRefreshService(status.name.toLowerCase().replace(' ', ''))}
color="primary"
>
<RefreshIcon />
</IconButton>
)}
</ListItem>
))}
</List>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CalendarIcon fontSize="small" />
<Typography variant="body2">Calendar</Typography>
<Chip
label={loadingProgress >= 40 ? "✓" : "..."}
size="small"
color={loadingProgress >= 40 ? "success" : "default"}
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AnalyticsIcon fontSize="small" />
<Typography variant="body2">Analytics</Typography>
<Chip
label={loadingProgress >= 60 ? "✓" : "..."}
size="small"
color={loadingProgress >= 60 ? "success" : "default"}
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SearchIcon fontSize="small" />
<Typography variant="body2">Gap Analysis</Typography>
<Chip
label={loadingProgress >= 80 ? "✓" : "..."}
size="small"
color={loadingProgress >= 80 ? "success" : "default"}
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<HealthIcon fontSize="small" />
<Typography variant="body2">System Health</Typography>
<Chip
label={loadingProgress >= 100 ? "✓" : "..."}
size="small"
color={loadingProgress >= 100 ? "success" : "default"}
/>
</Box>
</Box>
</Box>
</Collapse>
{/* Quick Actions */}
{hasErrors && (
<Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
<Button
variant="outlined"
size="small"
onClick={() => serviceStatuses.forEach(status => {
if (status.status === 'error') {
onRefreshService(status.name.toLowerCase().replace(' ', ''));
}
})}
>
Refresh All Failed Services
</Button>
</Box>
{loadingProgress >= 100 && (
<Alert severity="success" sx={{ mt: 2 }}>
Dashboard loaded successfully! You can now start using all features.
</Alert>
)}
</Paper>
);

View File

@@ -1,137 +0,0 @@
import React from 'react';
import {
Box,
Paper,
Typography,
LinearProgress,
IconButton,
Chip,
Collapse,
Alert
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Refresh as RefreshIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
Warning as WarningIcon
} from '@mui/icons-material';
import { ServiceStatus } from '../../../services/contentPlanningOrchestrator';
interface ServiceStatusPanelProps {
serviceStatuses: ServiceStatus[];
onRefreshService: (serviceName: string) => void;
expanded: boolean;
onToggleExpanded: () => void;
}
const ServiceStatusPanel: React.FC<ServiceStatusPanelProps> = ({
serviceStatuses,
onRefreshService,
expanded,
onToggleExpanded
}) => {
const getStatusColor = (status: string) => {
switch (status) {
case 'success': return 'success';
case 'error': return 'error';
case 'loading': return 'primary';
default: return 'primary';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'success': return <CheckCircleIcon fontSize="small" />;
case 'error': return <ErrorIcon fontSize="small" />;
case 'loading': return <WarningIcon fontSize="small" />;
default: return null;
}
};
const getOverallStatus = () => {
const hasErrors = serviceStatuses.some(s => s.status === 'error');
const hasLoading = serviceStatuses.some(s => s.status === 'loading');
const allSuccess = serviceStatuses.every(s => s.status === 'success');
if (hasErrors) return { status: 'error', text: 'Some services failed' };
if (hasLoading) return { status: 'loading', text: 'Services loading' };
if (allSuccess) return { status: 'success', text: 'All services operational' };
return { status: 'idle', text: 'Services idle' };
};
const overallStatus = getOverallStatus();
return (
<Paper sx={{ mb: 2 }}>
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{getStatusIcon(overallStatus.status)}
<Typography variant="subtitle2">
System Status: {overallStatus.text}
</Typography>
<Chip
label={`${serviceStatuses.filter(s => s.status === 'success').length}/${serviceStatuses.length}`}
size="small"
color={getStatusColor(overallStatus.status)}
variant="outlined"
/>
</Box>
<IconButton size="small" onClick={onToggleExpanded}>
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Box>
</Box>
<Collapse in={expanded}>
<Box sx={{ p: 2 }}>
{serviceStatuses.map((service) => (
<Box key={service.name} sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{getStatusIcon(service.status)}
<Typography variant="body2" fontWeight="medium">
{service.name}
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="caption" color="text.secondary">
{service.progress}%
</Typography>
<IconButton
size="small"
onClick={() => onRefreshService(service.name.toLowerCase().replace(/\s+/g, ''))}
disabled={service.status === 'loading'}
>
<RefreshIcon fontSize="small" />
</IconButton>
</Box>
</Box>
<LinearProgress
variant="determinate"
value={service.progress}
color={getStatusColor(service.status)}
sx={{ mb: 1 }}
/>
<Typography variant="caption" color="text.secondary">
{service.message}
</Typography>
{service.error && (
<Alert severity="error" sx={{ mt: 1 }}>
{service.error}
</Alert>
)}
</Box>
))}
</Box>
</Collapse>
</Paper>
);
};
export default ServiceStatusPanel;

View File

@@ -26,6 +26,8 @@ import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
import EnhancedStrategyActivationButton from './EnhancedStrategyActivationButton';
import { StrategyData } from '../types/strategy.types';
import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
import { useStrategyCalendarContext } from '../../../../../contexts/StrategyCalendarContext';
import { useStrategyBuilderStore } from '../../../../../stores/strategyBuilderStore';
interface ReviewProgressHeaderProps {
strategyData?: StrategyData | null;
@@ -44,6 +46,12 @@ const ReviewProgressHeader: React.FC<ReviewProgressHeaderProps> = ({ strategyDat
// Initialize navigation orchestrator
const navigationOrchestrator = useNavigationOrchestrator();
// Get strategy calendar context
const { setStrategyContext } = useStrategyCalendarContext();
// Get actual strategy data from strategy builder store
const strategyBuilderData = useStrategyBuilderStore(state => state.currentStrategy);
// Extract domain name from strategy data
const getDomainName = () => {
@@ -96,16 +104,37 @@ const ReviewProgressHeader: React.FC<ReviewProgressHeaderProps> = ({ strategyDat
const handleGenerateContentCalendar = () => {
// Use actual strategy data from strategy builder store
const actualStrategyData = strategyBuilderData || strategyData;
console.log('🎯 ReviewProgressHeader: Strategy data for calendar generation:', {
strategyBuilderData: !!strategyBuilderData,
strategyData: !!strategyData,
actualStrategyData: !!actualStrategyData
});
// Prepare strategy context for navigation
const strategyContext = {
strategyId: (strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1').toString(),
strategyData: strategyData,
strategyId: (() => {
if (actualStrategyData && 'id' in actualStrategyData) {
return actualStrategyData.id.toString();
} else if (actualStrategyData && 'strategy_metadata' in actualStrategyData) {
return actualStrategyData.strategy_metadata?.user_id?.toString() || '1';
} else if (actualStrategyData && 'metadata' in actualStrategyData) {
return actualStrategyData.metadata?.user_id?.toString() || '1';
}
return '1';
})(),
strategyData: actualStrategyData,
activationStatus: 'active' as const,
activationTimestamp: new Date().toISOString(),
userPreferences: {},
strategicIntelligence: {}
};
// Set strategy context in the StrategyCalendarContext
setStrategyContext(strategyContext);
// Navigate to calendar wizard using navigation orchestrator
navigationOrchestrator.navigateToCalendarWizard(
strategyContext.strategyId,

View File

@@ -19,6 +19,8 @@ import { useStrategyReviewStore, StrategyComponent } from '../../../../../stores
import { ANALYSIS_CARD_STYLES } from '../styles';
import EnhancedStrategyActivationButton from './EnhancedStrategyActivationButton';
import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
import { useStrategyCalendarContext } from '../../../../../contexts/StrategyCalendarContext';
import { useStrategyBuilderStore } from '../../../../../stores/strategyBuilderStore';
interface ReviewProgressSectionProps {
strategyData: StrategyData;
@@ -38,6 +40,12 @@ const ReviewProgressSection: React.FC<ReviewProgressSectionProps> = ({ strategyD
// Initialize navigation orchestrator
const navigationOrchestrator = useNavigationOrchestrator();
// Get strategy calendar context
const { setStrategyContext } = useStrategyCalendarContext();
// Get actual strategy data from strategy builder store
const strategyBuilderData = useStrategyBuilderStore(state => state.currentStrategy);
// Extract domain name from strategy data
const getDomainName = () => {
@@ -102,16 +110,37 @@ const ReviewProgressSection: React.FC<ReviewProgressSectionProps> = ({ strategyD
const handleGenerateContentCalendar = () => {
console.log('🎯 Generate content calendar clicked');
// Use actual strategy data from strategy builder store
const actualStrategyData = strategyBuilderData || strategyData;
console.log('🎯 ReviewProgressSection: Strategy data for calendar generation:', {
strategyBuilderData: !!strategyBuilderData,
strategyData: !!strategyData,
actualStrategyData: !!actualStrategyData
});
// Prepare strategy context for navigation
const strategyContext = {
strategyId: (strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1').toString(),
strategyData: strategyData,
strategyId: (() => {
if (actualStrategyData && 'id' in actualStrategyData) {
return actualStrategyData.id.toString();
} else if (actualStrategyData && 'strategy_metadata' in actualStrategyData) {
return actualStrategyData.strategy_metadata?.user_id?.toString() || '1';
} else if (actualStrategyData && 'metadata' in actualStrategyData) {
return actualStrategyData.metadata?.user_id?.toString() || '1';
}
return '1';
})(),
strategyData: actualStrategyData,
activationStatus: 'active' as const,
activationTimestamp: new Date().toISOString(),
userPreferences: {},
strategicIntelligence: {}
};
// Set strategy context in the StrategyCalendarContext
setStrategyContext(strategyContext);
// Navigate to calendar wizard using navigation orchestrator
navigationOrchestrator.navigateToCalendarWizard(
strategyContext.strategyId,

View File

@@ -0,0 +1,420 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Tooltip,
Typography,
Chip,
CircularProgress,
Alert,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Grid,
Paper,
LinearProgress,
List,
ListItem,
ListItemText,
ListItemIcon,
Divider,
Card,
CardContent,
CardHeader,
Avatar,
Badge
} from '@mui/material';
import {
CheckCircle as HealthyIcon,
Warning as WarningIcon,
Error as CriticalIcon,
Help as UnknownIcon,
Refresh as RefreshIcon,
Close as CloseIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Speed as SpeedIcon,
BugReport as BugReportIcon,
Storage as StorageIcon,
Timeline as TimelineIcon,
Analytics as AnalyticsIcon,
NetworkCheck as NetworkCheckIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import MonitoringCharts from './MonitoringCharts';
interface SystemStatusData {
status: 'healthy' | 'warning' | 'critical' | 'unknown';
icon: string;
recent_requests: number;
recent_errors: number;
error_rate: number;
timestamp: string;
}
interface DetailedStatsData {
overview: {
total_requests: number;
total_errors: number;
recent_requests: number;
recent_errors: number;
};
cache_performance: {
hits: number;
misses: number;
hit_rate: number;
};
top_endpoints: Array<{
endpoint: string;
count: number;
avg_time: number;
errors: number;
last_called: string;
cache_hit_rate: number;
}>;
recent_errors: Array<{
timestamp: string;
path: string;
method: string;
status_code: number;
duration: number;
}>;
system_health: {
status: string;
error_rate: number;
};
}
interface SystemStatusIndicatorProps {
className?: string;
}
const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className }) => {
const [statusData, setStatusData] = useState<SystemStatusData | null>(null);
const [detailedStats, setDetailedStats] = useState<DetailedStatsData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [dashboardOpen, setDashboardOpen] = useState(false);
const [chartData, setChartData] = useState<any[]>([]);
const fetchStatus = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/content-planning/monitoring/lightweight-stats');
if (!response.ok) {
throw new Error('Failed to fetch system status');
}
const result = await response.json();
if (result.status === 'success') {
setStatusData(result.data);
} else {
throw new Error(result.message || 'Failed to get system status');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
setStatusData({
status: 'unknown',
icon: '⚪',
recent_requests: 0,
recent_errors: 0,
error_rate: 0,
timestamp: new Date().toISOString()
});
} finally {
setLoading(false);
}
};
const fetchDetailedStats = async () => {
try {
const response = await fetch('/api/content-planning/monitoring/api-stats');
if (response.ok) {
const result = await response.json();
if (result.status === 'success') {
setDetailedStats(result.data);
// Generate chart data
const chartData = result.data.top_endpoints.slice(0, 5).map((endpoint: any, index: number) => ({
name: endpoint.endpoint.split(' ')[1].split('/').pop() || 'API',
requests: endpoint.count,
avgTime: endpoint.avg_time,
errors: endpoint.errors,
hitRate: endpoint.cache_hit_rate
}));
setChartData(chartData);
}
}
} catch (err) {
console.error('Error fetching detailed stats:', err);
}
};
useEffect(() => {
fetchStatus();
// Refresh every 30 seconds
const interval = setInterval(fetchStatus, 30000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (dashboardOpen) {
fetchDetailedStats();
const interval = setInterval(fetchDetailedStats, 10000); // Refresh every 10 seconds when dashboard is open
return () => clearInterval(interval);
}
}, [dashboardOpen]);
const getStatusIcon = (status: string) => {
switch (status) {
case 'healthy':
return <HealthyIcon sx={{ color: 'success.main', fontSize: 20 }} />;
case 'warning':
return <WarningIcon sx={{ color: 'warning.main', fontSize: 20 }} />;
case 'critical':
return <CriticalIcon sx={{ color: 'error.main', fontSize: 20 }} />;
default:
return <UnknownIcon sx={{ color: 'grey.500', fontSize: 20 }} />;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'healthy':
return 'success';
case 'warning':
return 'warning';
case 'critical':
return 'error';
default:
return 'default';
}
};
const formatTimestamp = (timestamp: string) => {
return new Date(timestamp).toLocaleTimeString();
};
const tooltipContent = statusData ? (
<Box sx={{ p: 1, maxWidth: 300 }}>
<Typography variant="subtitle2" gutterBottom>
System Status: {statusData.status.toUpperCase()}
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Recent Requests: {statusData.recent_requests}
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Recent Errors: {statusData.recent_errors}
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Error Rate: {statusData.error_rate}%
</Typography>
<Typography variant="caption" color="text.secondary">
Last Updated: {formatTimestamp(statusData.timestamp)}
</Typography>
<Typography variant="caption" color="primary" sx={{ display: 'block', mt: 1 }}>
Click for detailed dashboard
</Typography>
</Box>
) : (
<Typography>Loading system status...</Typography>
);
const handleDashboardClick = () => {
console.log('Dashboard clicked, setting dashboardOpen to true');
setDashboardOpen(true);
};
if (loading && !statusData) {
return (
<Box className={className} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CircularProgress size={16} />
<Typography variant="caption">System Status</Typography>
</Box>
);
}
return (
<>
<Box className={className} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Tooltip title={tooltipContent} arrow placement="bottom">
<Box
sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
onClick={handleDashboardClick}
>
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
{statusData ? getStatusIcon(statusData.status) : <UnknownIcon />}
</motion.div>
</Box>
</Tooltip>
<Chip
label={statusData?.status || 'Unknown'}
size="small"
color={getStatusColor(statusData?.status || 'unknown')}
variant="outlined"
sx={{ height: 24, fontSize: '0.75rem' }}
/>
<IconButton
size="small"
onClick={fetchStatus}
disabled={loading}
sx={{ p: 0.5 }}
>
<RefreshIcon sx={{ fontSize: 16 }} />
</IconButton>
{/* Debug button to test dashboard opening */}
<IconButton
size="small"
onClick={handleDashboardClick}
sx={{ p: 0.5, color: 'primary.main' }}
title="Open Dashboard (Debug)"
>
<AnalyticsIcon sx={{ fontSize: 16 }} />
</IconButton>
{error && (
<Alert severity="error" sx={{ fontSize: '0.75rem', py: 0 }}>
{error}
</Alert>
)}
</Box>
{/* Enhanced Monitoring Dashboard */}
<Dialog
open={dashboardOpen}
onClose={() => {
console.log('Dialog closing, setting dashboardOpen to false');
setDashboardOpen(false);
}}
maxWidth="lg"
fullWidth
sx={{
'& .MuiDialog-paper': {
borderRadius: 3,
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
maxHeight: '90vh'
}
}}
>
<DialogTitle sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
borderRadius: '12px 12px 0 0'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<AnalyticsIcon />
<Typography variant="h6">System Monitoring Dashboard</Typography>
{statusData && (
<Chip
label={statusData.status.toUpperCase()}
color={getStatusColor(statusData.status)}
size="small"
sx={{ color: 'white' }}
/>
)}
</Box>
<IconButton onClick={() => setDashboardOpen(false)} sx={{ color: 'white' }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent sx={{ p: 3, overflow: 'auto' }}>
<Typography variant="body1" sx={{ mb: 2 }}>
Dashboard is open! Status: {dashboardOpen ? 'Open' : 'Closed'}
</Typography>
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.5 }}
>
{detailedStats ? (
<MonitoringCharts
chartData={chartData}
cachePerformance={detailedStats.cache_performance}
apiPerformance={{
recent_requests: detailedStats.overview.recent_requests,
recent_errors: detailedStats.overview.recent_errors,
error_rate: detailedStats.system_health.error_rate
}}
/>
) : (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 }}>
<CircularProgress size={60} />
</Box>
)}
{/* Recent Errors Section */}
{detailedStats?.recent_errors && detailedStats.recent_errors.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7 }}
>
<Card sx={{ mt: 3, background: 'rgba(255,255,255,0.95)' }}>
<CardHeader
avatar={<Avatar sx={{ bgcolor: 'error.main' }}><BugReportIcon /></Avatar>}
title="Recent Errors"
subheader="Latest API errors and issues"
/>
<CardContent>
<List>
{detailedStats.recent_errors.slice(0, 5).map((error, index) => (
<ListItem key={index} sx={{ border: '1px solid #f0f0f0', borderRadius: 1, mb: 1 }}>
<ListItemIcon>
<CriticalIcon color="error" />
</ListItemIcon>
<ListItemText
primary={`${error.method} ${error.path}`}
secondary={`Status: ${error.status_code} | Duration: ${error.duration.toFixed(3)}s | ${formatTimestamp(error.timestamp)}`}
/>
</ListItem>
))}
</List>
</CardContent>
</Card>
</motion.div>
)}
</motion.div>
</AnimatePresence>
</DialogContent>
<DialogActions sx={{ p: 3, background: 'rgba(255,255,255,0.9)' }}>
<Button
onClick={() => setDashboardOpen(false)}
variant="outlined"
startIcon={<CloseIcon />}
>
Close
</Button>
<Button
onClick={fetchDetailedStats}
variant="contained"
startIcon={<RefreshIcon />}
disabled={loading}
>
Refresh Data
</Button>
</DialogActions>
</Dialog>
</>
);
};
export default SystemStatusIndicator;

View File

@@ -58,7 +58,8 @@ import {
Insights as InsightsIcon,
Assessment as AssessmentIcon,
Campaign as CampaignIcon,
Speed as SpeedIcon
Speed as SpeedIcon,
AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
@@ -101,7 +102,8 @@ const CalendarTab: React.FC = () => {
contentRepurposing,
aiInsights,
calendarGenerationError,
dataLoading
dataLoading,
calendarGenerationLoading
} = useContentPlanningStore();
const [tabValue, setTabValue] = useState(0);
@@ -257,6 +259,226 @@ const CalendarTab: React.FC = () => {
}
};
const renderGeneratedCalendar = () => {
if (!generatedCalendar) {
return (
<Box sx={{ textAlign: 'center', py: 4 }}>
<AutoAwesomeIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" color="text.secondary" gutterBottom>
No AI-generated calendar available
</Typography>
<Typography variant="body2" color="text.secondary">
Generate a calendar using the Calendar Wizard to see AI-powered content recommendations
</Typography>
</Box>
);
}
return (
<Grid container spacing={3}>
{/* Calendar Overview */}
<Grid item xs={12}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<AutoAwesomeIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
AI-Generated Content Calendar
</Typography>
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
<Chip label={`Type: ${generatedCalendar.calendar_type || 'Monthly'}`} variant="outlined" />
<Chip label={`Industry: ${generatedCalendar.industry || 'Technology'}`} variant="outlined" />
<Chip label={`Business Size: ${generatedCalendar.business_size || 'SME'}`} variant="outlined" />
</Box>
{generatedCalendar.metadata && (
<Typography variant="body2" color="text.secondary">
Generated: {new Date(generatedCalendar.metadata.generated_at).toLocaleString()}
</Typography>
)}
</Paper>
</Grid>
{/* Daily Schedule */}
{generatedCalendar.daily_schedule && generatedCalendar.daily_schedule.length > 0 && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<ScheduleIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Daily Schedule
</Typography>
<List>
{generatedCalendar.daily_schedule.map((item: any, index: number) => (
<ListItem key={index} divider>
<ListItemIcon>
<EventIcon />
</ListItemIcon>
<ListItemText
primary={item.topic || item.content_type}
secondary={`${item.platform}${item.content_type}${item.estimated_engagement}% engagement`}
/>
</ListItem>
))}
</List>
</Paper>
</Grid>
)}
{/* Weekly Themes */}
{generatedCalendar.weekly_themes && generatedCalendar.weekly_themes.length > 0 && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TimelineIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Weekly Themes
</Typography>
<List>
{generatedCalendar.weekly_themes.map((theme: any, index: number) => (
<ListItem key={index} divider>
<ListItemIcon>
<LightbulbIcon />
</ListItemIcon>
<ListItemText
primary={`Week ${theme.week}: ${theme.theme}`}
secondary={`${theme.content_count} pieces • ${theme.platforms?.join(', ')}`}
/>
</ListItem>
))}
</List>
</Paper>
</Grid>
)}
{/* Content Recommendations */}
{generatedCalendar.content_recommendations && generatedCalendar.content_recommendations.length > 0 && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Recommendations
</Typography>
<List>
{generatedCalendar.content_recommendations.map((rec: any, index: number) => (
<ListItem key={index} divider>
<ListItemIcon>
<CheckCircleIcon />
</ListItemIcon>
<ListItemText
primary={`${rec.type}: ${rec.topic}`}
secondary={`Priority: ${rec.priority} • ROI: ${(rec.estimated_roi * 100).toFixed(0)}%`}
/>
</ListItem>
))}
</List>
</Paper>
</Grid>
)}
{/* Performance Predictions */}
{generatedCalendar.performance_predictions && (
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Performance Predictions
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Box>
<Typography variant="body2" color="text.secondary">Estimated Engagement</Typography>
<LinearProgress
variant="determinate"
value={generatedCalendar.performance_predictions.estimated_engagement || 0}
sx={{ height: 8, borderRadius: 4 }}
/>
<Typography variant="body2">{generatedCalendar.performance_predictions.estimated_engagement || 0}%</Typography>
</Box>
<Box>
<Typography variant="body2" color="text.secondary">Estimated Reach</Typography>
<Typography variant="h6">{generatedCalendar.performance_predictions.estimated_reach || 0}</Typography>
</Box>
<Box>
<Typography variant="body2" color="text.secondary">Estimated Conversions</Typography>
<Typography variant="h6">{generatedCalendar.performance_predictions.estimated_conversions || 0}</Typography>
</Box>
</Box>
</Paper>
</Grid>
)}
{/* AI Insights */}
{generatedCalendar.ai_insights && generatedCalendar.ai_insights.length > 0 && (
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<PsychologyIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
AI Insights
</Typography>
<Grid container spacing={2}>
{generatedCalendar.ai_insights.map((insight: any, index: number) => (
<Grid item xs={12} md={4} key={index}>
<Card variant="outlined">
<CardContent>
<Typography variant="body2" color="text.secondary" gutterBottom>
Confidence: {(insight.confidence * 100).toFixed(0)}%
</Typography>
<Typography variant="body1" gutterBottom>
{insight.insight}
</Typography>
<Typography variant="body2" color="primary">
Action: {insight.action}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Paper>
</Grid>
)}
{/* Strategy Data */}
{generatedCalendar.strategy_data && Object.keys(generatedCalendar.strategy_data).length > 0 && (
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Strategy Integration
</Typography>
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>Strategy Analysis & Quality Indicators</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={2}>
{generatedCalendar.strategy_analysis && (
<Grid item xs={12} md={6}>
<Typography variant="subtitle2" gutterBottom>Strategy Analysis</Typography>
<Typography variant="body2">
Completion: {generatedCalendar.strategy_analysis.completion_percentage || 0}%
</Typography>
<Typography variant="body2">
Filled Fields: {generatedCalendar.strategy_analysis.filled_fields || 0}/{generatedCalendar.strategy_analysis.total_fields || 30}
</Typography>
</Grid>
)}
{generatedCalendar.quality_indicators && (
<Grid item xs={12} md={6}>
<Typography variant="subtitle2" gutterBottom>Quality Indicators</Typography>
<Typography variant="body2">
Overall Quality: {generatedCalendar.quality_indicators.overall_quality_score || 0}%
</Typography>
<Typography variant="body2">
Strategic Alignment: {generatedCalendar.quality_indicators.strategic_alignment || 0}%
</Typography>
</Grid>
)}
</Grid>
</AccordionDetails>
</Accordion>
</Paper>
</Grid>
)}
</Grid>
);
};
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
@@ -294,9 +516,19 @@ const CalendarTab: React.FC = () => {
</Alert>
)}
{calendarGenerationLoading && (
<Alert severity="info" sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<CircularProgress size={20} />
<Typography>Generating AI-powered content calendar...</Typography>
</Box>
</Alert>
)}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={(e, newValue) => setTabValue(newValue)}>
<Tab label="Calendar Events" icon={<CalendarIcon />} iconPosition="start" />
<Tab label="AI-Generated Calendar" icon={<AutoAwesomeIcon />} iconPosition="start" />
</Tabs>
</Box>
@@ -389,6 +621,17 @@ const CalendarTab: React.FC = () => {
)}
</TabPanel>
<TabPanel value={tabValue} index={1}>
{/* AI-Generated Calendar Tab */}
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : (
renderGeneratedCalendar()
)}
</TabPanel>
{/* Event Dialog */}
<Dialog open={dialogOpen} onClose={handleCloseDialog} maxWidth="sm" fullWidth>
<DialogTitle>

View File

@@ -1,29 +1,36 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import React, { useState, useEffect, useCallback } from 'react';
import {
Box,
Typography,
Tabs,
Tab,
Typography
Tab
} from '@mui/material';
import {
AutoAwesome as AutoAwesomeIcon,
CalendarToday as CalendarIcon
} from '@mui/icons-material';
import { useLocation } from 'react-router-dom';
// Import components
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
import CalendarGenerationWizard from '../components/CalendarGenerationWizard';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import { useStrategyCalendarContext } from '../../../contexts/StrategyCalendarContext';
import { CalendarGenerationModal } from '../components/CalendarGenerationModal';
// Import hooks and services
import { useStrategyCalendarContext } from '../../../contexts/StrategyCalendarContext';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
// Import types
import { type CalendarConfig } from '../components/CalendarWizardSteps/types';
// TabPanel component
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
const TabPanel: React.FC<TabPanelProps> = ({ children, value, index, ...other }) => {
return (
<div
role="tabpanel"
@@ -35,53 +42,27 @@ function TabPanel(props: TabPanelProps) {
{value === index && <Box>{children}</Box>}
</div>
);
}
};
const CreateTab: React.FC = () => {
const [userData, setUserData] = useState<any>({});
const [tabValue, setTabValue] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
const [currentCalendarConfig, setCurrentCalendarConfig] = useState<CalendarConfig | null>(null);
const [sessionId, setSessionId] = useState<string>('');
const location = useLocation();
// Get strategy context from the provider
const { state: { strategyContext }, isFromStrategyActivation } = useStrategyCalendarContext();
// Removed verbose logging for cleaner console
// 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
const [userData, setUserData] = useState<any>({});
// Handle navigation from strategy activation
useEffect(() => {
// 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
// Auto-switch to Calendar Wizard tab when coming from strategy activation
useEffect(() => {
// Removed verbose logging for cleaner console
// Check multiple sources for strategy activation status
const fromStrategyActivation = isFromStrategyActivation();
const isFromStrategy = fromStrategyActivation ||
(strategyContext?.activationStatus === 'active') ||
(location.state as any)?.fromStrategyActivation;
console.log('🔍 CreateTab: Strategy activation check:', {
(location.state as any)?.fromStrategyActivation ||
(location.state as any)?.strategyContext;
console.log('🔍 CreateTab: Navigation state check:', {
fromStrategyActivation,
strategyContextActivationStatus: strategyContext?.activationStatus,
windowLocationState: location.state || 'N/A',
isFromStrategy
});
@@ -90,7 +71,7 @@ const CreateTab: React.FC = () => {
console.log('🎯 CreateTab: Switching to Calendar Wizard tab (index 1)');
setTabValue(1); // Switch to Calendar Wizard tab
}
}, [fromStrategyActivation, strategyContext?.activationStatus]);
}, [isFromStrategyActivation, strategyContext?.activationStatus]);
// Also check on mount for immediate navigation state
useEffect(() => {
@@ -112,22 +93,77 @@ const CreateTab: React.FC = () => {
return () => clearTimeout(timer);
}, [location.state]);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleGenerateCalendar = useCallback(async (calendarConfig: any) => {
const handleGenerateCalendar = useCallback(async (calendarConfig: CalendarConfig) => {
try {
await contentPlanningApi.generateComprehensiveCalendar({
...calendarConfig,
userData
// Transform calendarConfig to match backend CalendarGenerationRequest format
const requestData = {
user_id: 1, // Default user ID
strategy_id: strategyContext?.strategyId ? parseInt(strategyContext.strategyId) : undefined,
calendar_type: calendarConfig.calendarType || 'monthly',
industry: userData?.industry || 'technology',
business_size: 'sme',
force_refresh: false
};
console.log('🎯 Starting calendar generation with modal:', requestData);
// Call the new start endpoint to get session ID
const startResponse = await fetch('/api/content-planning/calendar-generation/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData),
});
if (!startResponse.ok) {
throw new Error(`Failed to start calendar generation: ${startResponse.statusText}`);
}
const startData = await startResponse.json();
const sessionId = startData.session_id;
// Store the session ID and calendar config for the modal
setSessionId(sessionId);
setCurrentCalendarConfig(calendarConfig);
// Open the modal to show progress
setIsModalOpen(true);
} catch (error) {
console.error('Error generating calendar:', error);
console.error('Error starting calendar generation:', error);
// The modal will handle error display
}
}, [userData]);
}, [userData, strategyContext]);
const handleModalComplete = useCallback((results: any) => {
console.log('🎉 Calendar generation completed:', results);
setIsModalOpen(false);
setCurrentCalendarConfig(null);
setSessionId('');
// TODO: Handle the completed calendar results
// This could include navigating to a calendar view, showing success message, etc.
}, []);
const handleModalError = useCallback((error: string) => {
console.error('❌ Calendar generation error:', error);
setIsModalOpen(false);
setCurrentCalendarConfig(null);
setSessionId('');
// TODO: Handle error display (could show a toast notification)
}, []);
const handleModalClose = useCallback(() => {
setIsModalOpen(false);
setCurrentCalendarConfig(null);
setSessionId('');
}, []);
return (
<Box sx={{ p: 3 }}>
@@ -166,9 +202,31 @@ const CreateTab: React.FC = () => {
onGenerateCalendar={handleGenerateCalendar}
loading={false}
strategyContext={strategyContext}
fromStrategyActivation={fromStrategyActivation}
fromStrategyActivation={isFromStrategyActivation()}
/>
</TabPanel>
{/* Calendar Generation Modal */}
{currentCalendarConfig && (
<CalendarGenerationModal
open={isModalOpen}
onClose={handleModalClose}
sessionId={sessionId}
initialConfig={{
userId: userData?.id?.toString() || '1',
strategyId: strategyContext?.strategyId || '',
calendarType: currentCalendarConfig.calendarType === 'weekly' ? 'monthly' :
currentCalendarConfig.calendarType === 'quarterly' ? 'quarterly' : 'monthly',
platforms: currentCalendarConfig.priorityPlatforms || [],
duration: currentCalendarConfig.calendarDuration || 30,
postingFrequency: currentCalendarConfig.postingFrequency ?
(currentCalendarConfig.postingFrequency >= 7 ? 'daily' :
currentCalendarConfig.postingFrequency >= 3 ? 'biweekly' : 'weekly') : 'weekly'
}}
onComplete={handleModalComplete}
onError={handleModalError}
/>
)}
</Box>
);
};