ALwrity version 0.5.6

This commit is contained in:
ajaysi
2025-08-16 20:40:09 +05:30
parent 234eefb4bc
commit 5f104bf427
33 changed files with 2558 additions and 3630 deletions

View File

@@ -160,6 +160,34 @@ const ContentStrategyBuilder: React.FC = () => {
handleShowEducationalInfo
} = useEventHandlers();
// Create a state for educational modal that can be passed to both hooks
const [showEducationalModal, setShowEducationalModal] = useState(false);
const [showEnterpriseModal, setShowEnterpriseModal] = useState(false);
// Persist enterprise modal state across hot reloads
useEffect(() => {
const savedModalState = sessionStorage.getItem('showEnterpriseModal');
if (savedModalState === 'true') {
console.log('🎯 Restoring enterprise modal state from sessionStorage');
setShowEnterpriseModal(true);
}
}, []);
// Save modal state to sessionStorage when it changes
useEffect(() => {
sessionStorage.setItem('showEnterpriseModal', showEnterpriseModal.toString());
}, [showEnterpriseModal]);
// Cleanup sessionStorage on component unmount
useEffect(() => {
return () => {
// Only clear if we're not in the middle of showing the modal
if (!showEnterpriseModal) {
sessionStorage.removeItem('showEnterpriseModal');
}
};
}, [showEnterpriseModal]);
// Use strategy creation hook first
const { originalHandleCreateStrategy, handleSaveStrategy } = useStrategyCreation({
formData,
@@ -171,7 +199,7 @@ const ContentStrategyBuilder: React.FC = () => {
setSaving,
setGenerationProgress: setStoreGenerationProgress,
setEducationalContent: setStoreEducationalContent,
setShowEducationalModal: () => {}, // Temporary placeholder
setShowEducationalModal, // Pass the actual setShowEducationalModal function
validateAllFields,
getCompletionStats,
generateAIRecommendations: (strategyId: string) => generateAIRecommendations(strategyId),
@@ -180,15 +208,12 @@ const ContentStrategyBuilder: React.FC = () => {
});
const {
showEducationalModal,
setShowEducationalModal,
showEnterpriseModal,
setShowEnterpriseModal,
handleProceedWithCurrentStrategy,
handleAddEnterpriseDatapoints
} = useModalManagement({
aiGenerating,
originalHandleCreateStrategy
originalHandleCreateStrategy,
setShowEnterpriseModal
});
const {

View File

@@ -0,0 +1,198 @@
import React, { useState, useEffect } from 'react';
import { Box, Paper } from '@mui/material';
import { motion, AnimatePresence } from 'framer-motion';
import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
interface CardExpansionWrapperProps {
children: React.ReactNode;
isExpanded?: boolean;
onExpand?: (expanded: boolean) => void;
gridSize?: {
xs?: number;
sm?: number;
md?: number;
lg?: number;
};
}
const CardExpansionWrapper: React.FC<CardExpansionWrapperProps> = ({
children,
isExpanded = false,
onExpand,
gridSize = { xs: 12, sm: 12, md: 6, lg: 4 }
}) => {
const [isHovered, setIsHovered] = useState(false);
const [isExpandedState, setIsExpandedState] = useState(false);
useEffect(() => {
console.log('🎯 CardExpansionWrapper mounted');
}, []);
const handleMouseEnter = () => {
console.log('🖱️ Mouse entered card');
setIsHovered(true);
setIsExpandedState(true);
onExpand?.(true);
};
const handleMouseLeave = () => {
console.log('🖱️ Mouse left card');
setIsHovered(false);
setIsExpandedState(false);
onExpand?.(false);
};
const handleClick = () => {
console.log('🖱️ Card clicked');
};
const isExpandedFinal = isExpanded || isExpandedState;
console.log('🎯 Card expansion state:', { isExpandedFinal, isHovered, isExpandedState });
return (
<div
style={{
position: 'relative',
width: '100%',
height: '100%',
gridColumn: isExpandedFinal ? '1 / -1' : 'auto',
transition: 'grid-column 0.3s ease',
zIndex: isExpandedFinal ? 10 : 1,
border: '1px solid blue', // Debug border
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
>
<motion.div
animate={{
scale: isExpandedFinal ? 1.02 : 1,
boxShadow: isExpandedFinal
? '0 8px 32px rgba(0, 0, 0, 0.15)'
: '0 2px 8px rgba(0, 0, 0, 0.1)',
}}
transition={{
type: "spring",
stiffness: 300,
damping: 30,
}}
style={{
height: '100%',
width: '100%',
}}
>
<Paper
sx={{
p: isExpandedFinal ? 3 : 2,
height: '100%',
width: '100%',
borderRadius: 2,
background: isExpandedFinal
? 'linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)'
: 'background.paper',
border: isExpandedFinal ? '3px solid' : '1px solid',
borderColor: isExpandedFinal ? 'primary.main' : 'divider',
transition: 'all 0.3s ease',
cursor: 'pointer',
position: 'relative',
overflow: 'hidden',
'&:hover': {
borderColor: 'primary.main',
},
'&::before': isExpandedFinal ? {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '5px',
background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
zIndex: 1,
} : {},
// Debug styling
...(isExpandedFinal && {
outline: '2px solid red',
outlineOffset: '2px',
}),
}}
>
{/* Hover Hint - Only show when not expanded */}
<AnimatePresence>
{!isExpandedFinal && isHovered && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
style={{
position: 'absolute',
top: '8px',
right: '8px',
background: 'rgba(25, 118, 210, 0.9)',
color: 'white',
borderRadius: '12px',
padding: '4px 8px',
fontSize: '0.7rem',
fontWeight: 500,
zIndex: 4,
display: 'flex',
alignItems: 'center',
gap: '4px',
pointerEvents: 'none',
}}
>
<ExpandMoreIcon sx={{ fontSize: '0.8rem' }} />
Expand
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{isExpandedFinal && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
background: 'rgba(25, 118, 210, 0.1)',
borderRadius: '8px 8px 0 0',
padding: '8px 16px',
fontSize: '0.75rem',
color: 'primary.main',
fontWeight: 500,
zIndex: 2,
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
<span style={{ fontSize: '1rem' }}></span>
Expanded for better readability
</motion.div>
)}
</AnimatePresence>
<Box sx={{
position: 'relative',
zIndex: 3,
mt: isExpandedFinal ? 3 : 0,
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
}}>
{children}
</Box>
</Paper>
</motion.div>
</div>
);
};
export default CardExpansionWrapper;

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Box,
Typography,
@@ -12,17 +12,19 @@ import {
ListItem,
ListItemIcon,
ListItemText,
Grid,
CircularProgress
CircularProgress,
Paper,
Grid
} from '@mui/material';
import {
School as SchoolIcon,
Lightbulb as LightbulbIcon,
Psychology as PsychologyIcon,
Timeline as TimelineIcon,
CheckCircle as CheckCircleIcon
CheckCircle as CheckCircleIcon,
ExpandMore as ExpandMoreIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { motion, AnimatePresence } from 'framer-motion';
import StrategicInputField from '../StrategicInputField';
import { CategoryDetailViewProps, EducationalInfoDialogProps } from '../types/contentStrategy.types';
import { useEnhancedStrategyStore } from '../../../../../stores/enhancedStrategyStore';
@@ -116,8 +118,11 @@ const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
getCategoryColor,
getEducationalContent
}) => {
const [expandedCard, setExpandedCard] = useState<string | null>(null);
// Get confidence scores from store
const { confidenceScores } = useEnhancedStrategyStore();
if (!activeCategory) {
return (
<motion.div
@@ -138,6 +143,8 @@ const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
);
}
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === activeCategory);
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -167,47 +174,45 @@ const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
getEducationalContent={getEducationalContent}
/>
{/* Category Fields */}
{/* Category Fields with Hover Expansion */}
<Box sx={{ mt: 1 }}>
<Grid container spacing={2}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field, index) => {
// Determine grid size based on field type for better layout organization
const type = field.type;
const isWideField = type === 'json';
const isMediumField = type === 'multiselect' || type === 'select' || type === 'text';
const isCompactField = type === 'number' || type === 'boolean';
const forceFullWidth = field.id === 'content_budget' || field.id === 'team_size';
{categoryFields.map((field, index) => {
// Determine grid size based on field type
const type = field.type;
const isWideField = type === 'json';
const isMediumField = type === 'multiselect' || type === 'select' || type === 'text';
const isCompactField = type === 'number' || type === 'boolean';
const forceFullWidth = field.id === 'content_budget' || field.id === 'team_size';
const gridSm = isWideField ? 12 : isMediumField ? 6 : 4;
const gridMd = isWideField ? 12 : isMediumField ? 6 : 4;
const gridLg = isWideField ? 12 : isMediumField ? 6 : 4;
const gridMd = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridLg = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridSm = 12;
return (
<Grid item xs={12} sm={gridSm} md={gridMd} lg={gridLg} key={field.id}>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: index * 0.03 }}
>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={confidenceScores[field.id] || (autoPopulatedFields[field.id] ? 0.8 : undefined)}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
personalizationData={personalizationData[field.id]}
onChange={(value: any) => onUpdateFormField(field.id, value)}
onValidate={() => onValidateFormField(field.id)}
onShowTooltip={() => onShowTooltip(field.id)}
onViewDataSource={() => onViewDataSource(field.id)}
accentColorKey={getCategoryColor(activeCategory) as any}
isCompact={isCompactField}
/>
</motion.div>
<Grid item xs={12} sm={gridSm} md={gridMd} lg={gridLg} key={field.id}>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: index * 0.03 }}
>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={confidenceScores[field.id] || (autoPopulatedFields[field.id] ? 0.8 : undefined)}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
personalizationData={personalizationData[field.id]}
onChange={(value: any) => onUpdateFormField(field.id, value)}
onValidate={() => onValidateFormField(field.id)}
onShowTooltip={() => onShowTooltip(field.id)}
onViewDataSource={() => onViewDataSource(field.id)}
accentColorKey={getCategoryColor(activeCategory) as any}
isCompact={isCompactField}
/>
</motion.div>
</Grid>
);
})}
@@ -218,18 +223,10 @@ const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
{(() => {
const isReviewed = reviewedCategories.has(activeCategory);
console.log('🔍 Category review status:', {
activeCategory,
isReviewed,
reviewedCategories: Array.from(reviewedCategories)
});
return !isReviewed ? (
<Button
variant="contained"
onClick={() => {
console.log('🔘 Button clicked! activeCategory:', activeCategory);
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
onConfirmCategoryReview();
}}
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}

View File

@@ -1,55 +1,29 @@
import { useState, useEffect } from 'react';
import { useEffect, useRef } from 'react';
interface UseModalManagementProps {
aiGenerating: boolean;
originalHandleCreateStrategy: (() => Promise<void>) | null;
originalHandleCreateStrategy?: (() => Promise<void>) | null;
setShowEnterpriseModal: (show: boolean) => void;
}
export const useModalManagement = ({ aiGenerating, originalHandleCreateStrategy }: UseModalManagementProps) => {
const [showEducationalModal, setShowEducationalModal] = useState(false);
const [showEnterpriseModal, setShowEnterpriseModal] = useState(false);
export const useModalManagement = ({
aiGenerating,
originalHandleCreateStrategy,
setShowEnterpriseModal
}: UseModalManagementProps) => {
const originalHandleCreateStrategyRef = useRef<(() => Promise<void>) | null>(null);
// Persist enterprise modal state across hot reloads
// Update ref when originalHandleCreateStrategy changes
useEffect(() => {
const savedModalState = sessionStorage.getItem('showEnterpriseModal');
if (savedModalState === 'true') {
console.log('🎯 Restoring enterprise modal state from sessionStorage');
setShowEnterpriseModal(true);
if (originalHandleCreateStrategy) {
originalHandleCreateStrategyRef.current = originalHandleCreateStrategy;
}
}, []);
// Save modal state to sessionStorage when it changes
useEffect(() => {
sessionStorage.setItem('showEnterpriseModal', showEnterpriseModal.toString());
}, [showEnterpriseModal]);
// Cleanup sessionStorage on component unmount
useEffect(() => {
return () => {
// Only clear if we're not in the middle of showing the modal
if (!showEnterpriseModal) {
sessionStorage.removeItem('showEnterpriseModal');
}
};
}, [showEnterpriseModal]);
}, [originalHandleCreateStrategy]);
// Monitor enterprise modal state for debugging
// Monitor aiGenerating state for debugging
useEffect(() => {
console.log('🎯 Enterprise modal state changed - showEnterpriseModal:', showEnterpriseModal);
// If modal was unexpectedly closed, log it
if (!showEnterpriseModal && aiGenerating) {
console.warn('🎯 WARNING: Enterprise modal closed while AI is generating');
}
// Only warn about unexpected closure if it's not due to hot reload
if (!showEnterpriseModal && !aiGenerating) {
const savedModalState = sessionStorage.getItem('showEnterpriseModal');
if (savedModalState !== 'true') {
console.warn('🎯 WARNING: Enterprise modal closed unexpectedly (not due to hot reload)');
}
}
}, [showEnterpriseModal, aiGenerating]);
console.log('🎯 useModalManagement: aiGenerating state changed:', aiGenerating);
}, [aiGenerating]);
// Handle proceed with current strategy (30 fields)
const handleProceedWithCurrentStrategy = async () => {
@@ -62,9 +36,9 @@ export const useModalManagement = ({ aiGenerating, originalHandleCreateStrategy
console.log('🎯 Calling original handleCreateStrategy after enterprise modal closes');
try {
// Ensure we're not already generating
if (!aiGenerating && originalHandleCreateStrategy) {
if (!aiGenerating && originalHandleCreateStrategyRef.current) {
console.log('🎯 Starting strategy generation...');
await originalHandleCreateStrategy();
await originalHandleCreateStrategyRef.current();
} else {
console.log('🎯 Already generating, skipping duplicate call');
}
@@ -86,8 +60,8 @@ export const useModalManagement = ({ aiGenerating, originalHandleCreateStrategy
console.log('🎯 Calling original handleCreateStrategy for enterprise datapoints');
try {
// Ensure we're not already generating
if (!aiGenerating && originalHandleCreateStrategy) {
await originalHandleCreateStrategy();
if (!aiGenerating && originalHandleCreateStrategyRef.current) {
await originalHandleCreateStrategyRef.current();
} else {
console.log('🎯 Already generating, skipping duplicate call');
}
@@ -98,10 +72,6 @@ export const useModalManagement = ({ aiGenerating, originalHandleCreateStrategy
};
return {
showEducationalModal,
setShowEducationalModal,
showEnterpriseModal,
setShowEnterpriseModal,
handleProceedWithCurrentStrategy,
handleAddEnterpriseDatapoints
};

View File

@@ -1,41 +1,22 @@
import React from 'react';
import { Box, CircularProgress, Alert, Typography, Grid } from '@mui/material';
import React, { useState } from 'react';
import { Box, CircularProgress, Alert, Typography } from '@mui/material';
import { useStrategyData } from './hooks/useStrategyData';
import { useStrategyActions } from './hooks/useStrategyActions';
import StrategyHeader from './components/StrategyHeader';
import StrategicInsightsCard from './components/StrategicInsightsCard';
import CompetitiveAnalysisCard from './components/CompetitiveAnalysisCard';
import PerformancePredictionsCard from './components/PerformancePredictionsCard';
import ImplementationRoadmapCard from './components/ImplementationRoadmapCard';
import RiskAssessmentCard from './components/RiskAssessmentCard';
import StrategyActions from './components/StrategyActions';
import ConfirmationDialog from './components/ConfirmationDialog';
import ReviewProgressHeader from './components/ReviewProgressHeader';
const StrategyIntelligenceTab: React.FC = () => {
const { strategyData, loading, error, loadStrategyData } = useStrategyData();
const {
strategyConfirmed,
showConfirmDialog,
setShowConfirmDialog,
handleConfirmStrategy,
confirmStrategy,
handleGenerateContentCalendar
} = useStrategyActions();
const { strategyData, loading, error } = useStrategyData();
// State to control review progress visibility
const [showReviewProgress, setShowReviewProgress] = useState(false);
const handleConfirmStrategyClick = () => {
handleConfirmStrategy();
};
const handleConfirmStrategyAction = async () => {
await confirmStrategy(strategyData);
};
const handleGenerateContentCalendarAction = async () => {
try {
await handleGenerateContentCalendar(strategyData);
} catch (error) {
console.error('Error generating content calendar:', error);
}
const handleStartReviewProcess = () => {
setShowReviewProgress(true);
};
if (loading) {
@@ -70,32 +51,52 @@ const StrategyIntelligenceTab: React.FC = () => {
return (
<Box sx={{ p: 3 }}>
{/* Header Section */}
<StrategyHeader strategyData={strategyData} strategyConfirmed={strategyConfirmed} />
<StrategyHeader
strategyData={strategyData}
strategyConfirmed={false}
onStartReview={handleStartReviewProcess}
/>
{/* Review Progress Header - Only shown when review process is started */}
{showReviewProgress && <ReviewProgressHeader />}
{/* Strategy Components Grid */}
<Grid container spacing={2}>
<Box
sx={{
display: 'grid',
gridTemplateColumns: {
xs: '1fr',
sm: '1fr',
md: 'repeat(2, 1fr)',
lg: 'repeat(2, 1fr)',
xl: 'repeat(3, 1fr)'
},
gridAutoRows: 'minmax(min-content, auto)',
gap: 3,
position: 'relative',
minHeight: '400px',
padding: 2,
'& > *': {
minHeight: 'fit-content',
position: 'relative',
zIndex: 1,
transition: 'z-index 0.3s ease, transform 0.3s ease',
},
'& > *:hover': {
zIndex: 10,
}
}}
>
<StrategicInsightsCard strategyData={strategyData} />
<CompetitiveAnalysisCard strategyData={strategyData} />
<PerformancePredictionsCard strategyData={strategyData} />
<ImplementationRoadmapCard strategyData={strategyData} />
<RiskAssessmentCard strategyData={strategyData} />
</Grid>
</Box>
{/* Action Buttons */}
<StrategyActions
strategyData={strategyData}
strategyConfirmed={strategyConfirmed}
onConfirmStrategy={handleConfirmStrategyClick}
onGenerateContentCalendar={handleGenerateContentCalendarAction}
onRefreshData={loadStrategyData}
/>
{/* Action Buttons - Removed, functionality moved to "Confirm & Activate Strategy" button in ReviewProgressHeader */}
{/* Confirmation Dialog */}
<ConfirmationDialog
open={showConfirmDialog}
onClose={() => setShowConfirmDialog(false)}
onConfirm={handleConfirmStrategyAction}
/>
{/* Confirmation Dialog - Removed, functionality moved to "Confirm & Activate Strategy" button */}
</Box>
);
};

View File

@@ -70,29 +70,27 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
if (!strategyData?.competitive_analysis) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Competitive Analysis"
subtitle="Market positioning insights"
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Competitive analysis data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Competitive Analysis"
subtitle="Market positioning insights"
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Competitive analysis data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
);
}
@@ -630,7 +628,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
);
return (
<Grid item xs={12} lg={6}>
<>
<ProgressiveCard
title="Competitive Analysis"
subtitle="Market positioning insights"
@@ -639,6 +637,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
componentId="competitive_analysis"
/>
{/* Chip Modal for detailed view */}
@@ -681,7 +680,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
{chipModal.content}
</Typography>
</Popover>
</Grid>
</>
);
};

View File

@@ -52,29 +52,27 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
if (!strategyData?.implementation_roadmap) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Project timeline and phases"
icon={<TimelineIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Implementation roadmap data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Strategic execution plan"
icon={<TimelineIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Implementation roadmap data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
);
}
@@ -550,17 +548,16 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Project timeline and phases"
icon={<TimelineIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Project timeline and phases"
icon={<TimelineIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
componentId="implementation_roadmap"
/>
);
};

View File

@@ -36,29 +36,27 @@ const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({
if (!strategyData?.performance_predictions) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Performance Predictions"
subtitle="ROI and success metrics"
icon={<ShowChartIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Performance predictions data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Performance Predictions"
subtitle="AI-powered forecasting"
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Performance predictions data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
);
}
@@ -80,8 +78,7 @@ const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({
display: 'flex',
alignItems: 'center',
flex: 1,
minWidth: 0,
overflow: 'hidden'
minWidth: 0
}}>
<Box sx={{
width: 40,
@@ -102,8 +99,7 @@ const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({
</Box>
<Box sx={{
minWidth: 0,
flex: 1,
overflow: 'hidden'
flex: 1
}}>
<Typography variant="h6" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.primary,
@@ -558,17 +554,16 @@ const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Performance Predictions"
subtitle="ROI and success metrics"
icon={<ShowChartIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Performance Predictions"
subtitle="ROI and success metrics"
icon={<ShowChartIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
componentId="performance_predictions"
/>
);
};

View File

@@ -19,6 +19,9 @@ import {
getAnalysisCardStyles,
getEnhancedChipStyles
} from '../styles';
import ReviewStatusIndicator from './ReviewStatusIndicator';
import ReviewConfirmationDialog from './ReviewConfirmationDialog';
import { useStrategyReviewStore } from '../../../../../stores/strategyReviewStore';
interface ProgressiveCardProps {
summary: React.ReactNode;
@@ -29,6 +32,7 @@ interface ProgressiveCardProps {
icon?: React.ReactNode;
autoCollapseDelay?: number; // milliseconds
className?: string;
componentId?: string; // For review functionality
}
const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
@@ -39,14 +43,30 @@ const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
subtitle,
icon,
autoCollapseDelay = 3000, // 3 seconds default
className
className,
componentId
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [showReviewDialog, setShowReviewDialog] = useState(false);
const [isConfirmingReview, setIsConfirmingReview] = useState(false);
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const theme = useTheme();
const cardStyles = getAnalysisCardStyles();
// Get review state for this component
const {
components,
isReviewing,
startReview,
completeReview,
resetReview
} = useStrategyReviewStore();
const component = componentId ? components.find(c => c.id === componentId) : null;
const componentStatus = component?.status || 'not_reviewed';
const componentReviewedAt = component?.reviewedAt;
// Handle hover interactions
const handleMouseEnter = () => {
if (trigger === 'hover') {
@@ -76,183 +96,277 @@ const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
}
};
// Review handlers
const handleStartReview = () => {
if (componentId) {
// Open the review dialog directly instead of setting to "in_review"
setShowReviewDialog(true);
}
};
const handleCompleteReview = () => {
if (componentId) {
setShowReviewDialog(true);
}
};
const handleResetReview = () => {
if (componentId) {
resetReview(componentId);
}
};
const handleConfirmReview = async (notes?: string) => {
if (componentId) {
setIsConfirmingReview(true);
try {
// Complete the review directly from "not_reviewed" to "reviewed"
completeReview(componentId, notes);
setShowReviewDialog(false);
} finally {
setIsConfirmingReview(false);
}
}
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
whileHover={{ y: -4 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={className}
>
<Card sx={{
...cardStyles.card,
'& .shimmer-text': {
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
animation: 'shimmer 2s ease-in-out infinite'
},
'& .bounce-icon': {
animation: 'bounce 2s infinite'
},
'@keyframes shimmer': {
'0%, 100%': { opacity: 1 },
'50%': { opacity: 0.8 }
},
'@keyframes bounce': {
'0%, 20%, 50%, 80%, 100%': { transform: 'translateY(0)' },
'40%': { transform: 'translateY(-4px)' },
'60%': { transform: 'translateY(-2px)' }
}
}}>
<CardContent sx={cardStyles.cardContent}>
{/* Header Section */}
{title && (
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 2
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{icon && (
<Box sx={{
p: 1,
borderRadius: 2,
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
mr: 1.5,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.primary}30`
}}>
{icon}
</Box>
)}
<Box>
<Typography variant="h6" className="shimmer-text" sx={{
fontWeight: 600
}}>
{title}
</Typography>
{subtitle && (
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.75rem'
}}>
{subtitle}
</Typography>
)}
</Box>
</Box>
{/* Trigger Button */}
{trigger === 'click' && (
<Button
onClick={handleToggle}
variant="text"
size="small"
sx={{
color: ANALYSIS_CARD_STYLES.colors.primary,
'&:hover': {
background: 'rgba(102, 126, 234, 0.1)'
},
minWidth: 'auto',
px: 1.5,
py: 0.5,
borderRadius: 2,
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'none'
}}
endIcon={
isExpanded ? (
<ExpandLessIcon sx={{ fontSize: 16 }} />
) : (
<ExpandMoreIcon sx={{ fontSize: 16 }} />
)
}
>
{isExpanded ? 'Show Less' : 'Read More'}
</Button>
)}
</Box>
)}
{/* Summary Section - Always Visible */}
<Box sx={{ mb: trigger === 'click' ? 2 : 0 }}>
{summary}
</Box>
{/* Progressive Details Section */}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{
height: 0,
opacity: 0,
overflow: 'hidden'
}}
animate={{
height: 'auto',
opacity: 1,
overflow: 'visible'
}}
exit={{
height: 0,
opacity: 0,
overflow: 'hidden'
}}
transition={{
duration: 0.4,
ease: [0.4, 0.0, 0.2, 1],
opacity: { duration: 0.3 }
}}
>
<Fade in={isExpanded} timeout={300}>
<Box sx={{
pt: 2,
borderTop: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
opacity: 0.9
}}>
{details}
</Box>
</Fade>
</motion.div>
)}
</AnimatePresence>
{/* Hover Indicator (for hover trigger) */}
{trigger === 'hover' && !isExpanded && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
transition={{ duration: 0.3 }}
>
<>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
whileHover={{ y: -4 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={className}
style={{
gridColumn: isExpanded && trigger === 'hover' ? '1 / -1' : 'auto',
zIndex: isExpanded && trigger === 'hover' ? 10 : 1,
transition: 'grid-column 0.3s ease, z-index 0.3s ease, margin 0.3s ease',
margin: isExpanded && trigger === 'hover' ? '16px 0' : '0',
padding: isExpanded && trigger === 'hover' ? '8px 0' : '0',
}}
>
<Card sx={{
...cardStyles.card,
transform: isExpanded && trigger === 'hover' ? 'scale(1.02)' : 'scale(1)',
boxShadow: isExpanded && trigger === 'hover'
? '0 8px 32px rgba(0, 0, 0, 0.15)'
: cardStyles.card.boxShadow,
transition: 'all 0.3s ease',
margin: isExpanded && trigger === 'hover' ? '8px 0' : '0',
'& .bounce-icon': {
animation: 'bounce 2s infinite'
},
'@keyframes bounce': {
'0%, 20%, 50%, 80%, 100%': { transform: 'translateY(0)' },
'40%': { transform: 'translateY(-4px)' },
'60%': { transform: 'translateY(-2px)' }
}
}}>
<CardContent sx={cardStyles.cardContent}>
{/* Header Section */}
{title && (
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: 1,
py: 0.5
justifyContent: 'space-between',
mb: 2
}}>
<ArrowDownIcon className="bounce-icon" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: 16
}} />
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
ml: 0.5,
fontSize: '0.7rem'
}}>
Hover to see more
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{icon && (
<Box sx={{
p: 1,
borderRadius: 2,
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
mr: 1.5,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.primary}30`
}}>
{icon}
</Box>
)}
<Box>
<Typography variant="h6" sx={{
fontWeight: 600,
color: ANALYSIS_CARD_STYLES.colors.text.primary
}}>
{title}
</Typography>
{subtitle && (
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.75rem'
}}>
{subtitle}
</Typography>
)}
</Box>
</Box>
{/* Review Status Indicator */}
{componentId && (
<Box sx={{ ml: 2 }}>
<ReviewStatusIndicator
status={componentStatus}
reviewedAt={componentReviewedAt}
onStartReview={handleStartReview}
onCompleteReview={handleCompleteReview}
onResetReview={handleResetReview}
isReviewing={isReviewing}
/>
</Box>
)}
{/* Trigger Button */}
{trigger === 'click' && (
<Button
onClick={handleToggle}
variant="text"
size="small"
sx={{
color: ANALYSIS_CARD_STYLES.colors.primary,
'&:hover': {
background: 'rgba(102, 126, 234, 0.1)'
},
minWidth: 'auto',
px: 1.5,
py: 0.5,
borderRadius: 2,
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'none'
}}
endIcon={
isExpanded ? (
<ExpandLessIcon sx={{ fontSize: 16 }} />
) : (
<ExpandMoreIcon sx={{ fontSize: 16 }} />
)
}
>
{isExpanded ? 'Show Less' : 'Read More'}
</Button>
)}
</Box>
</motion.div>
)}
</CardContent>
</Card>
</motion.div>
)}
{/* Summary Section - Always Visible */}
<Box sx={{ mb: trigger === 'click' ? 2 : 0 }}>
{summary}
</Box>
{/* Progressive Details Section */}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{
height: 0,
opacity: 0,
overflow: 'hidden'
}}
animate={{
height: 'auto',
opacity: 1,
overflow: 'visible'
}}
exit={{
height: 0,
opacity: 0,
overflow: 'hidden'
}}
transition={{
duration: 0.4,
ease: [0.4, 0.0, 0.2, 1],
opacity: { duration: 0.3 }
}}
>
<Fade in={isExpanded} timeout={300}>
<Box sx={{
pt: 2,
borderTop: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
opacity: 0.9
}}>
{details}
</Box>
</Fade>
</motion.div>
)}
</AnimatePresence>
{/* Hover Indicator (for hover trigger) */}
{trigger === 'hover' && !isExpanded && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
transition={{ duration: 0.3 }}
>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: 1,
py: 0.5
}}>
<ArrowDownIcon className="bounce-icon" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: 16
}} />
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
ml: 0.5,
fontSize: '0.7rem'
}}>
Hover to see more
</Typography>
</Box>
</motion.div>
)}
{/* Full Width Expansion Indicator */}
{trigger === 'hover' && isExpanded && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: 1,
py: 0.5,
background: 'rgba(102, 126, 234, 0.1)',
borderRadius: 1,
border: '1px solid rgba(102, 126, 234, 0.2)'
}}>
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.primary,
fontSize: '0.7rem',
fontWeight: 500
}}>
Expanded to full width for better readability
</Typography>
</Box>
</motion.div>
)}
</CardContent>
</Card>
</motion.div>
{/* Review Confirmation Dialog */}
{componentId && (
<ReviewConfirmationDialog
open={showReviewDialog}
onClose={() => setShowReviewDialog(false)}
onConfirm={handleConfirmReview}
componentId={componentId}
componentTitle={title || ''}
componentSubtitle={subtitle || ''}
isConfirming={isConfirmingReview}
/>
)}
</>
);
};

View File

@@ -0,0 +1,326 @@
import React, { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Box,
TextField,
Chip,
List,
ListItem,
ListItemIcon,
ListItemText,
CircularProgress
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
Lightbulb as LightbulbIcon,
TrendingUp as TrendingUpIcon,
ShowChart as ShowChartIcon,
Timeline as TimelineIcon,
Warning as WarningIcon,
Close as CloseIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { ANALYSIS_CARD_STYLES } from '../styles';
interface ReviewConfirmationDialogProps {
open: boolean;
onClose: () => void;
onConfirm: (notes?: string) => void;
componentId: string;
componentTitle: string;
componentSubtitle: string;
isConfirming?: boolean;
}
const ReviewConfirmationDialog: React.FC<ReviewConfirmationDialogProps> = ({
open,
onClose,
onConfirm,
componentId,
componentTitle,
componentSubtitle,
isConfirming = false
}) => {
const [notes, setNotes] = useState('');
const getComponentIcon = (id: string) => {
switch (id) {
case 'strategic_insights':
return <LightbulbIcon />;
case 'competitive_analysis':
return <TrendingUpIcon />;
case 'performance_predictions':
return <ShowChartIcon />;
case 'implementation_roadmap':
return <TimelineIcon />;
case 'risk_assessment':
return <WarningIcon />;
default:
return <CheckCircleIcon />;
}
};
const getComponentSummary = (id: string) => {
switch (id) {
case 'strategic_insights':
return [
'Market positioning analysis',
'Growth potential assessment',
'SWOT analysis summary',
'Content opportunities identification'
];
case 'competitive_analysis':
return [
'Competitor landscape analysis',
'Market gaps identification',
'Competitive advantages',
'Strategic recommendations'
];
case 'performance_predictions':
return [
'ROI projections',
'Traffic growth forecasts',
'Engagement metrics predictions',
'Success probability assessment'
];
case 'implementation_roadmap':
return [
'Project timeline and phases',
'Resource allocation plan',
'Milestone tracking',
'Success metrics definition'
];
case 'risk_assessment':
return [
'Risk identification and analysis',
'Mitigation strategies',
'Monitoring framework',
'Contingency planning'
];
default:
return ['Strategy component analysis'];
}
};
const handleConfirm = () => {
onConfirm(notes.trim() || undefined);
setNotes('');
};
const handleClose = () => {
setNotes('');
onClose();
};
return (
<Dialog
open={open}
onClose={handleClose}
maxWidth="md"
fullWidth
PaperProps={{
sx: {
borderRadius: 3,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
backdropFilter: 'blur(20px)',
border: '1px solid rgba(102, 126, 234, 0.3)',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15), 0 0 40px rgba(102, 126, 234, 0.1)'
}
}}
>
<DialogTitle sx={{
pb: 2,
display: 'flex',
alignItems: 'center',
gap: 2,
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%)',
borderBottom: '1px solid rgba(102, 126, 234, 0.15)'
}}>
<Box sx={{
p: 1.5,
borderRadius: 2,
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)'
}}>
{getComponentIcon(componentId)}
</Box>
<Box>
<Typography variant="h5" sx={{
color: '#1a1a1a',
fontWeight: 700,
mb: 0.5
}}>
Review Strategy Component
</Typography>
<Typography variant="body1" sx={{
color: '#666666',
fontSize: '1rem',
fontWeight: 500
}}>
{componentSubtitle}
</Typography>
</Box>
</DialogTitle>
<DialogContent sx={{ pt: 3, pb: 2 }}>
<Box sx={{ mb: 4 }}>
<Typography variant="h6" sx={{
color: '#1a1a1a',
mb: 2,
fontWeight: 600,
fontSize: '1.1rem'
}}>
You're reviewing <strong style={{ color: ANALYSIS_CARD_STYLES.colors.primary }}>"{componentTitle}"</strong>
</Typography>
<Typography variant="body1" sx={{
color: '#666666',
mb: 3,
fontSize: '1rem',
fontWeight: 500
}}>
This component includes the following analysis:
</Typography>
<List dense sx={{ mb: 3 }}>
{getComponentSummary(componentId).map((item, index) => (
<ListItem key={index} sx={{ py: 1, px: 0 }}>
<ListItemIcon sx={{ minWidth: 40 }}>
<Box sx={{
width: 8,
height: 8,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.primary,
opacity: 0.8
}} />
</ListItemIcon>
<ListItemText
primary={item}
primaryTypographyProps={{
variant: 'body1',
fontSize: '1rem',
color: '#1a1a1a',
fontWeight: 500
}}
/>
</ListItem>
))}
</List>
<Box sx={{
p: 2.5,
borderRadius: 2,
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%)',
border: '1px solid rgba(102, 126, 234, 0.2)',
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.1)'
}}>
<Typography variant="body1" sx={{
color: ANALYSIS_CARD_STYLES.colors.primary,
fontWeight: 600,
fontSize: '1rem',
lineHeight: 1.5
}}>
💡 Tip: Review all the insights and data, then confirm to mark this component as reviewed.
</Typography>
</Box>
</Box>
<Box sx={{ mb: 3 }}>
<Typography variant="body1" sx={{
color: '#1a1a1a',
mb: 1.5,
fontWeight: 600,
fontSize: '1rem'
}}>
Optional Notes (for your reference):
</Typography>
<TextField
fullWidth
multiline
rows={4}
variant="outlined"
placeholder="Add any notes or observations about this strategy component..."
value={notes}
onChange={(e) => setNotes(e.target.value)}
sx={{
'& .MuiOutlinedInput-root': {
fontSize: '1rem',
borderRadius: 2,
'&:hover fieldset': {
borderColor: ANALYSIS_CARD_STYLES.colors.primary,
borderWidth: '2px'
},
'&.Mui-focused fieldset': {
borderColor: ANALYSIS_CARD_STYLES.colors.primary,
borderWidth: '2px'
}
},
'& .MuiInputBase-input': {
fontSize: '1rem',
lineHeight: 1.5
}
}}
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 2, background: 'rgba(102, 126, 234, 0.05)', borderTop: '1px solid rgba(102, 126, 234, 0.15)' }}>
<Button
onClick={handleClose}
disabled={isConfirming}
startIcon={<CloseIcon />}
sx={{
color: '#666666',
fontWeight: 600,
fontSize: '1rem',
px: 3,
py: 1,
borderRadius: 2,
'&:hover': {
background: 'rgba(0, 0, 0, 0.05)',
transform: 'translateY(-1px)'
}
}}
>
Cancel
</Button>
<Button
onClick={handleConfirm}
disabled={isConfirming}
variant="contained"
startIcon={isConfirming ? <CircularProgress size={18} /> : <CheckCircleIcon />}
sx={{
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.success}80 100%)`,
fontWeight: 600,
fontSize: '1rem',
px: 3,
py: 1,
borderRadius: 2,
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)',
'&:hover': {
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success}80 0%, ${ANALYSIS_CARD_STYLES.colors.success} 100%)`,
boxShadow: '0 6px 16px rgba(76, 175, 80, 0.4)',
transform: 'translateY(-1px)'
},
'&:active': {
transform: 'translateY(0)'
}
}}
>
{isConfirming ? 'Confirming...' : 'Mark as Reviewed'}
</Button>
</DialogActions>
</Dialog>
);
};
export default ReviewConfirmationDialog;

View File

@@ -0,0 +1,489 @@
import React from 'react';
import {
Box,
Typography,
Chip,
IconButton,
Tooltip,
Card,
CardContent,
Badge,
Button,
CircularProgress
} from '@mui/material';
import {
Refresh as RefreshIcon,
CheckCircle as CheckCircleIcon,
Schedule as ScheduleIcon,
Warning as WarningIcon,
PlayArrow as PlayArrowIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { useStrategyReviewStore } from '../../../../../stores/strategyReviewStore';
import { ANALYSIS_CARD_STYLES } from '../styles';
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
const ReviewProgressHeader: React.FC = () => {
const {
components,
reviewProgress,
isAllReviewed,
resetAllReviews,
getUnreviewedComponents,
getReviewedComponents
} = useStrategyReviewStore();
// Extract domain name from strategy data (you can pass this as prop if needed)
const getDomainName = () => {
// For now, return a default domain - you can enhance this to get from strategy data
return "alwrity.com";
};
const unreviewedCount = getUnreviewedComponents().length;
const reviewedCount = getReviewedComponents().length;
const totalCount = components.length;
// Debug logging
console.log('🔍 ReviewProgressHeader Debug:', {
components,
reviewProgress,
unreviewedCount,
reviewedCount,
totalCount,
isAllReviewed: isAllReviewed()
});
const getProgressColor = () => {
if (reviewProgress === 100) return ANALYSIS_CARD_STYLES.colors.success;
if (reviewProgress >= 60) return ANALYSIS_CARD_STYLES.colors.primary;
if (reviewProgress >= 30) return ANALYSIS_CARD_STYLES.colors.warning;
return ANALYSIS_CARD_STYLES.colors.error;
};
const getProgressText = () => {
if (reviewProgress === 100) return 'All components reviewed!';
if (reviewProgress >= 60) return 'Great progress!';
if (reviewProgress >= 30) return 'Making good progress';
return 'Getting started';
};
return (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<Card
sx={{
mb: 3,
background: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%)',
color: 'white',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.5), 0 0 40px rgba(102, 126, 234, 0.3)',
borderRadius: 3,
position: 'relative',
overflow: 'hidden',
border: '1px solid rgba(102, 126, 234, 0.3)',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%)',
pointerEvents: 'none'
},
'&::after': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%)',
animation: 'shimmer 3s infinite',
pointerEvents: 'none'
},
'@keyframes shimmer': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' }
}
}}
>
{/* Animated Border Lights */}
<motion.div
animate={{
boxShadow: [
'0 0 20px rgba(102, 126, 234, 0.5)',
'0 0 40px rgba(102, 126, 234, 0.8)',
'0 0 20px rgba(102, 126, 234, 0.5)'
]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
borderRadius: '12px',
pointerEvents: 'none'
}}
/>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 2 }}>
{/* Header with Circular Progress and Status Chips */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{/* Circular Progress */}
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={reviewProgress}
size={50}
thickness={4}
sx={{
color: getProgressColor(),
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" sx={{
color: getProgressColor(),
fontWeight: 700,
fontSize: '0.7rem'
}}>
{Math.round(reviewProgress)}%
</Typography>
</Box>
</Box>
<Box>
<Typography variant="h6" sx={{
color: 'white',
fontWeight: 600,
mb: 0.25
}}>
Strategy Review Progress
</Typography>
<Typography variant="body2" sx={{
color: 'rgba(255, 255, 255, 0.8)',
fontSize: '0.8rem'
}}>
{getProgressText()}
</Typography>
</Box>
{/* Status Chips */}
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
icon={<CheckCircleIcon />}
label={`${reviewedCount} Reviewed`}
size="small"
sx={{
background: ANALYSIS_CARD_STYLES.colors.success,
color: 'white',
fontWeight: 500,
fontSize: '0.65rem',
height: 24,
'& .MuiChip-icon': {
color: 'white',
fontSize: 14
}
}}
/>
{unreviewedCount > 0 && (
<Chip
icon={<ScheduleIcon />}
label={`${unreviewedCount} Pending`}
size="small"
sx={{
background: ANALYSIS_CARD_STYLES.colors.warning,
color: 'white',
fontWeight: 500,
fontSize: '0.65rem',
height: 24,
'& .MuiChip-icon': {
color: 'white',
fontSize: 14
}
}}
/>
)}
</Box>
</Box>
{/* Reset Button */}
<Tooltip title="Reset all reviews">
<IconButton
onClick={resetAllReviews}
size="small"
sx={{
color: 'rgba(255, 255, 255, 0.8)',
'&:hover': {
color: '#f44336',
background: 'rgba(244, 67, 54, 0.2)'
}
}}
>
<RefreshIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
{/* Progress Summary */}
<Box sx={{ mb: 1.5 }}>
<Typography variant="body2" sx={{
color: 'white',
fontWeight: 500,
fontSize: '0.8rem'
}}>
{reviewedCount} of {totalCount} components reviewed
</Typography>
</Box>
{/* Informative Text */}
<Box sx={{ mb: 1.5 }}>
<Typography variant="body2" sx={{
color: 'rgba(255, 255, 255, 0.9)',
fontSize: '0.8rem',
lineHeight: 1.4,
background: 'rgba(255, 255, 255, 0.05)',
p: 1.5,
borderRadius: 1,
border: '1px solid rgba(255, 255, 255, 0.1)'
}}>
<strong>Complete review by clicking 'Not Reviewed' button and confirming datapoints of 5 analysis components below.</strong>
<br />
<span style={{ color: 'rgba(255, 255, 255, 0.7)' }}>
Important: Content strategy for <strong>{getDomainName()}</strong> will shape content generation next.
</span>
</Typography>
</Box>
{/* Individual Component Status and Activate Strategy Button */}
<Box sx={{ mb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.75 }}>
<Typography variant="caption" sx={{
color: 'rgba(255, 255, 255, 0.8)',
fontWeight: 500,
fontSize: '0.7rem'
}}>
Component Status
</Typography>
{/* Confirm & Activate Strategy Button */}
<Tooltip
title={isAllReviewed() ? "Confirm strategy and activate content generation" : "Complete all component reviews to confirm and activate strategy"}
arrow
>
<Button
variant="contained"
size="small"
disabled={!isAllReviewed()}
startIcon={<PlayArrowIcon />}
onClick={async () => {
if (isAllReviewed()) {
try {
// Handle strategy confirmation and activation
console.log('Confirming and activating strategy...');
// 1. Save the strategy confirmation to backend
// Note: You'll need to get the actual strategy ID from context/props
const strategyId = "current_strategy_id"; // Replace with actual strategy ID
try {
await contentPlanningApi.updateEnhancedStrategy(
strategyId,
{
confirmed: true,
confirmed_at: new Date().toISOString(),
review_completed: true,
review_completed_at: new Date().toISOString()
}
);
console.log('Strategy confirmation saved to backend');
} catch (updateError) {
console.warn('Could not save confirmation to backend:', updateError);
}
// 2. Show success message
alert('Strategy confirmed and activated! You can now proceed to create your content calendar.');
// 3. Navigate to content calendar creation
// You can add navigation logic here
// navigate('/content-calendar');
} catch (error) {
console.error('Error confirming and activating strategy:', error);
alert('Error confirming strategy. Please try again.');
}
}
}}
sx={{
background: isAllReviewed()
? 'linear-gradient(135deg, #4caf50 0%, #66bb6a 100%)'
: 'rgba(255, 255, 255, 0.1)',
color: isAllReviewed() ? 'white' : 'rgba(255, 255, 255, 0.5)',
fontWeight: 600,
fontSize: '0.7rem',
px: 2,
py: 0.5,
borderRadius: 2,
boxShadow: isAllReviewed()
? '0 2px 8px rgba(76, 175, 80, 0.3)'
: 'none',
border: isAllReviewed()
? '1px solid rgba(76, 175, 80, 0.4)'
: '1px solid rgba(255, 255, 255, 0.2)',
textTransform: 'none',
minWidth: 'auto',
'&:hover': {
background: isAllReviewed()
? 'linear-gradient(135deg, #66bb6a 0%, #81c784 100%)'
: 'rgba(255, 255, 255, 0.1)',
boxShadow: isAllReviewed()
? '0 4px 12px rgba(76, 175, 80, 0.4)'
: 'none',
transform: isAllReviewed() ? 'translateY(-1px)' : 'none'
},
'&:active': {
transform: isAllReviewed() ? 'translateY(0)' : 'none'
},
'&:disabled': {
background: 'rgba(255, 255, 255, 0.05)',
color: 'rgba(255, 255, 255, 0.3)',
boxShadow: 'none',
transform: 'none'
},
'& .MuiButton-startIcon': {
marginRight: 0.5
}
}}
>
Confirm & Activate Strategy
</Button>
</Tooltip>
</Box>
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
{components.map((component) => (
<Tooltip
key={component.id}
title={`${component.title}: ${component.status === 'reviewed' ? 'Reviewed' : 'Pending Review'}`}
arrow
>
<Badge
badgeContent={
component.status === 'reviewed' ? (
<CheckCircleIcon sx={{ fontSize: 10, color: 'white' }} />
) : (
<ScheduleIcon sx={{ fontSize: 10, color: 'white' }} />
)
}
color={component.status === 'reviewed' ? 'success' : 'warning'}
>
<Chip
label={component.title}
size="small"
sx={{
background: component.status === 'reviewed'
? 'rgba(76, 175, 80, 0.3)'
: 'rgba(255, 152, 0, 0.3)',
color: component.status === 'reviewed' ? '#4caf50' : '#ff9800',
border: `1px solid ${component.status === 'reviewed' ? 'rgba(76, 175, 80, 0.5)' : 'rgba(255, 152, 0, 0.5)'}`,
fontWeight: 600,
fontSize: '0.65rem',
height: 22,
'&:hover': {
background: component.status === 'reviewed'
? 'rgba(76, 175, 80, 0.4)'
: 'rgba(255, 152, 0, 0.4)',
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
</Badge>
</Tooltip>
))}
</Box>
</Box>
{/* Completion Status */}
{isAllReviewed() && (
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip
icon={<CheckCircleIcon />}
label="Ready for Calendar Creation"
size="small"
sx={{
background: ANALYSIS_CARD_STYLES.colors.success,
color: 'white',
fontWeight: 500,
animation: 'pulse 2s infinite',
fontSize: '0.65rem',
height: 22,
'@keyframes pulse': {
'0%, 100%': { opacity: 1 },
'50%': { opacity: 0.7 }
},
'& .MuiChip-icon': {
color: 'white',
fontSize: 14
}
}}
/>
</Box>
)}
{/* Completion Message */}
{isAllReviewed() && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<Box sx={{
mt: 1.5,
p: 1.5,
borderRadius: 1,
background: 'rgba(76, 175, 80, 0.1)',
border: '1px solid rgba(76, 175, 80, 0.2)',
textAlign: 'center'
}}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.success,
fontWeight: 600,
fontSize: '0.8rem'
}}>
🎉 All strategy components have been reviewed! You can now proceed to create your content calendar.
</Typography>
</Box>
</motion.div>
)}
</CardContent>
</Card>
</motion.div>
);
};
export default ReviewProgressHeader;

View File

@@ -0,0 +1,244 @@
import React from 'react';
import {
Box,
Chip,
IconButton,
Tooltip,
Typography,
Button
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
Schedule as ScheduleIcon,
Warning as WarningIcon,
Edit as EditIcon,
Undo as UndoIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { ReviewStatus } from '../../../../../stores/strategyReviewStore';
import { ANALYSIS_CARD_STYLES } from '../styles';
interface ReviewStatusIndicatorProps {
status: ReviewStatus;
reviewedAt?: Date;
onStartReview?: () => void;
onCompleteReview?: () => void;
onResetReview?: () => void;
isReviewing?: boolean;
}
const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
status,
reviewedAt,
onStartReview,
onCompleteReview,
onResetReview,
isReviewing = false
}) => {
const getStatusConfig = () => {
switch (status) {
case 'reviewed':
return {
icon: <CheckCircleIcon />,
label: 'Reviewed',
color: ANALYSIS_CARD_STYLES.colors.success,
bgColor: 'rgba(76, 175, 80, 0.1)',
borderColor: 'rgba(76, 175, 80, 0.3)',
textColor: ANALYSIS_CARD_STYLES.colors.success
};
case 'not_reviewed':
default:
return {
icon: <WarningIcon />,
label: 'Not Reviewed',
color: ANALYSIS_CARD_STYLES.colors.warning,
bgColor: 'rgba(255, 152, 0, 0.1)',
borderColor: 'rgba(255, 152, 0, 0.3)',
textColor: ANALYSIS_CARD_STYLES.colors.warning
};
}
};
const config = getStatusConfig();
const formatReviewDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date);
};
return (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.2 }}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
p: 1,
borderRadius: 1,
background: config.bgColor,
border: '1px solid',
borderColor: config.borderColor,
minHeight: 32,
cursor: 'pointer',
transition: 'all 0.2s ease',
'&:hover': {
background: `${config.bgColor}80`,
transform: 'translateY(-1px)',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}
}}
onClick={() => {
if (status === 'not_reviewed' && onStartReview) {
onStartReview();
} else if (status === 'reviewed' && onResetReview) {
onResetReview();
}
}}
>
{/* Status Icon */}
<Box sx={{
display: 'flex',
alignItems: 'center',
color: config.color
}}>
{config.icon}
</Box>
{/* Status Label */}
<Typography
variant="caption"
sx={{
color: config.textColor,
fontWeight: 600,
fontSize: '0.75rem',
flex: 1
}}
>
{config.label}
</Typography>
{/* Review Date */}
{status === 'reviewed' && reviewedAt && (
<Typography
variant="caption"
sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.7rem',
fontStyle: 'italic'
}}
>
{formatReviewDate(reviewedAt)}
</Typography>
)}
{/* Action Buttons - Enhanced for better UX */}
<Box sx={{ display: 'flex', gap: 0.5 }}>
{status === 'not_reviewed' && onStartReview && (
<Tooltip title="Review Component">
<Button
variant="contained"
size="small"
onClick={(e) => {
e.stopPropagation();
onStartReview();
}}
disabled={isReviewing}
startIcon={<EditIcon />}
sx={{
background: 'linear-gradient(135deg, #ff9800 0%, #ffb74d 100%)',
color: 'white',
fontWeight: 600,
fontSize: '0.7rem',
px: 1.5,
py: 0.5,
borderRadius: 2,
boxShadow: '0 2px 8px rgba(255, 152, 0, 0.3)',
border: '1px solid rgba(255, 152, 0, 0.4)',
textTransform: 'none',
minWidth: 'auto',
'&:hover': {
background: 'linear-gradient(135deg, #ffb74d 0%, #ffcc02 100%)',
boxShadow: '0 4px 12px rgba(255, 152, 0, 0.4)',
transform: 'translateY(-1px)'
},
'&:active': {
transform: 'translateY(0)',
boxShadow: '0 2px 4px rgba(255, 152, 0, 0.3)'
},
'&:disabled': {
background: 'rgba(255, 152, 0, 0.3)',
color: 'rgba(255, 255, 255, 0.7)',
boxShadow: 'none',
transform: 'none'
},
'& .MuiButton-startIcon': {
marginRight: 0.5
}
}}
>
Review
</Button>
</Tooltip>
)}
{status === 'reviewed' && onResetReview && (
<Tooltip title="Reset Review">
<Button
variant="outlined"
size="small"
onClick={(e) => {
e.stopPropagation();
onResetReview();
}}
disabled={isReviewing}
startIcon={<UndoIcon />}
sx={{
color: ANALYSIS_CARD_STYLES.colors.warning,
borderColor: 'rgba(255, 152, 0, 0.5)',
fontWeight: 600,
fontSize: '0.7rem',
px: 1.5,
py: 0.5,
borderRadius: 2,
textTransform: 'none',
minWidth: 'auto',
background: 'rgba(255, 152, 0, 0.05)',
'&:hover': {
background: 'rgba(255, 152, 0, 0.1)',
borderColor: 'rgba(255, 152, 0, 0.7)',
transform: 'translateY(-1px)',
boxShadow: '0 2px 8px rgba(255, 152, 0, 0.2)'
},
'&:active': {
transform: 'translateY(0)'
},
'&:disabled': {
color: 'rgba(255, 152, 0, 0.4)',
borderColor: 'rgba(255, 152, 0, 0.2)',
background: 'rgba(255, 152, 0, 0.02)',
transform: 'none'
},
'& .MuiButton-startIcon': {
marginRight: 0.5
}
}}
>
Reset
</Button>
</Tooltip>
)}
</Box>
</Box>
</motion.div>
);
};
export default ReviewStatusIndicator;

View File

@@ -35,6 +35,8 @@ interface RiskAssessmentCardProps {
}
const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData }) => {
// Get style objects
const sectionStyles = getSectionStyles();
const accordionStyles = getAccordionStyles();
@@ -51,29 +53,27 @@ const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData })
if (!strategyData?.risk_assessment) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Risk Assessment"
subtitle="Risk analysis and mitigation"
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Risk assessment data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Risk Assessment"
subtitle="Strategic risk analysis"
icon={<WarningIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Risk assessment data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
);
}
@@ -395,17 +395,16 @@ const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData })
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Risk Assessment"
subtitle="Risk analysis and mitigation"
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
<ProgressiveCard
title="Risk Assessment"
subtitle="Risk analysis and mitigation"
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
componentId="risk_assessment"
/>
);
};

View File

@@ -40,15 +40,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
const accordionStyles = getAccordionStyles();
const listItemStyles = getListItemStyles();
console.log('🔍 StrategicInsightsCard - strategyData:', strategyData);
console.log('🔍 StrategicInsightsCard - strategic_insights:', strategyData?.strategic_insights);
console.log('🔍 StrategicInsightsCard - market_positioning:', strategyData?.strategic_insights?.market_positioning);
console.log('🔍 StrategicInsightsCard - swot_analysis:', strategyData?.strategic_insights?.market_positioning?.swot_analysis);
console.log('🔍 StrategicInsightsCard - strengths:', strategyData?.strategic_insights?.market_positioning?.swot_analysis?.strengths);
console.log('🔍 StrategicInsightsCard - opportunities:', strategyData?.strategic_insights?.market_positioning?.swot_analysis?.opportunities);
console.log('🔍 StrategicInsightsCard - content_opportunities:', strategyData?.strategic_insights?.content_opportunities);
console.log('🔍 StrategicInsightsCard - growth_potential:', strategyData?.strategic_insights?.growth_potential);
console.log('🔍 StrategicInsightsCard - swot_summary:', strategyData?.strategic_insights?.swot_summary);
if (!strategyData?.strategic_insights) {
return (
@@ -587,17 +579,16 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Strategic Insights"
subtitle="AI-powered market analysis"
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={2000}
/>
</Grid>
<ProgressiveCard
title="Strategic Insights"
subtitle="AI-powered market analysis"
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={2000}
componentId="strategic_insights"
/>
);
};

View File

@@ -42,9 +42,10 @@ import { getStrategyName, getStrategyGenerationDate } from '../utils/strategyTra
interface StrategyHeaderProps {
strategyData: StrategyData | null;
strategyConfirmed: boolean;
onStartReview?: () => void;
}
const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyConfirmed }) => {
const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyConfirmed, onStartReview }) => {
const [showNextStepText, setShowNextStepText] = useState(false);
if (!strategyData) return null;
@@ -577,13 +578,14 @@ const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyC
{/* Next Steps Button - Area B */}
<Box sx={{ mt: 1.5, display: 'flex', justifyContent: 'center' }}>
<Tooltip
title={strategyData.summary?.next_step || "Review strategy and generate content calendar"}
title="Start reviewing strategy components and create content calendar"
arrow
open={showNextStepText}
onClose={() => setShowNextStepText(false)}
>
<Button
variant="contained"
onClick={onStartReview}
onMouseEnter={() => setShowNextStepText(true)}
onMouseLeave={() => setShowNextStepText(false)}
sx={{
@@ -606,7 +608,7 @@ const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyC
}}
startIcon={<ArrowForwardIcon />}
>
Next Step
Next: Review Strategy and Create Content Calendar
</Button>
</Tooltip>
</Box>

View File

@@ -1,5 +1,5 @@
// Main Strategy Intelligence Tab
export { default as StrategyIntelligenceTab } from '../StrategyIntelligenceTab';
export { default as StrategyIntelligenceTab } from './StrategyIntelligenceTab';
// Components
export { default as StrategyHeader } from './components/StrategyHeader';

View File

@@ -165,6 +165,13 @@ export const transformPollingStrategyData = (strategyData: any): StrategyData =>
competitive_risks: riskAssessment.risk_categories?.competitive_risks || [],
technical_risks: riskAssessment.risk_categories?.technical_risks || [],
financial_risks: riskAssessment.risk_categories?.financial_risks || []
},
mitigation_strategies: riskAssessment.mitigation_strategies || [],
monitoring_framework: riskAssessment.monitoring_framework || {
key_indicators: [],
monitoring_frequency: "Weekly",
escalation_procedures: [],
review_schedule: "Monthly"
}
} : undefined,

View File

@@ -1,103 +0,0 @@
import React from 'react';
import { Box, CircularProgress, Alert, Typography, Grid } from '@mui/material';
import { useStrategyData } from './StrategyIntelligence/hooks/useStrategyData';
import { useStrategyActions } from './StrategyIntelligence/hooks/useStrategyActions';
import StrategyHeader from './StrategyIntelligence/components/StrategyHeader';
import StrategicInsightsCard from './StrategyIntelligence/components/StrategicInsightsCard';
import CompetitiveAnalysisCard from './StrategyIntelligence/components/CompetitiveAnalysisCard';
import PerformancePredictionsCard from './StrategyIntelligence/components/PerformancePredictionsCard';
import ImplementationRoadmapCard from './StrategyIntelligence/components/ImplementationRoadmapCard';
import RiskAssessmentCard from './StrategyIntelligence/components/RiskAssessmentCard';
import StrategyActions from './StrategyIntelligence/components/StrategyActions';
import ConfirmationDialog from './StrategyIntelligence/components/ConfirmationDialog';
const StrategyIntelligenceTab: React.FC = () => {
const { strategyData, loading, error, loadStrategyData } = useStrategyData();
const {
strategyConfirmed,
showConfirmDialog,
setShowConfirmDialog,
handleConfirmStrategy,
confirmStrategy,
handleGenerateContentCalendar
} = useStrategyActions();
const handleConfirmStrategyClick = () => {
handleConfirmStrategy();
};
const handleConfirmStrategyAction = async () => {
await confirmStrategy(strategyData);
};
const handleGenerateContentCalendarAction = async () => {
try {
await handleGenerateContentCalendar(strategyData);
} catch (error) {
console.error('Error generating content calendar:', error);
}
};
if (loading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 400 }}>
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Alert severity="error" sx={{ m: 2 }}>
{error}
</Alert>
);
}
if (!strategyData) {
return (
<Box sx={{ textAlign: 'center', p: 4 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
No Strategy Data Available
</Typography>
<Typography variant="body2" color="text.secondary">
Generate a comprehensive strategy first to view strategic intelligence.
</Typography>
</Box>
);
}
return (
<Box sx={{ p: 3 }}>
{/* Header Section */}
<StrategyHeader strategyData={strategyData} strategyConfirmed={strategyConfirmed} />
{/* Strategy Components Grid */}
<Grid container spacing={2}>
<StrategicInsightsCard strategyData={strategyData} />
<CompetitiveAnalysisCard strategyData={strategyData} />
<PerformancePredictionsCard strategyData={strategyData} />
<ImplementationRoadmapCard strategyData={strategyData} />
<RiskAssessmentCard strategyData={strategyData} />
</Grid>
{/* Action Buttons */}
<StrategyActions
strategyData={strategyData}
strategyConfirmed={strategyConfirmed}
onConfirmStrategy={handleConfirmStrategyClick}
onGenerateContentCalendar={handleGenerateContentCalendarAction}
onRefreshData={loadStrategyData}
/>
{/* Confirmation Dialog */}
<ConfirmationDialog
open={showConfirmDialog}
onClose={() => setShowConfirmDialog(false)}
onConfirm={handleConfirmStrategyAction}
/>
</Box>
);
};
export default StrategyIntelligenceTab;

View File

@@ -51,7 +51,7 @@ import {
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import StrategyIntelligenceTab from '../components/StrategyIntelligenceTab';
import StrategyIntelligenceTab from '../components/StrategyIntelligence/StrategyIntelligenceTab';
import StrategyOnboardingDialog from '../components/StrategyOnboardingDialog';
const ContentStrategyTab: React.FC = () => {
@@ -96,25 +96,70 @@ const ContentStrategyTab: React.FC = () => {
// Check strategy status when strategies are loaded
useEffect(() => {
if (strategies && strategies.length > 0 && !hasCheckedStrategy) {
console.log('🔄 useEffect triggered - strategies changed:', strategies);
console.log('🔄 Strategies type:', typeof strategies);
console.log('🔄 Is Array:', Array.isArray(strategies));
console.log('🔄 Strategies length:', strategies?.length);
console.log('🔄 Has checked strategy:', hasCheckedStrategy);
// Handle different response formats
let strategiesArray: any[] = [];
if (Array.isArray(strategies)) {
// Direct array
strategiesArray = strategies;
} else if (strategies && typeof strategies === 'object' && 'strategies' in strategies && Array.isArray((strategies as any).strategies)) {
// API response object with strategies array
strategiesArray = (strategies as any).strategies;
}
console.log('🔄 StrategiesArray length:', strategiesArray.length);
if (strategiesArray.length > 0) {
console.log('✅ Strategies found, checking status...');
checkStrategyStatus();
} else if ((!strategies || strategies.length === 0) && !hasCheckedStrategy) {
} else if (strategiesArray.length === 0 && hasCheckedStrategy) {
// Only set to 'none' if we've already checked and confirmed no strategies
console.log('❌ No strategies found, setting status to none...');
setStrategyStatus('none');
setHasCheckedStrategy(true);
setShowOnboarding(true);
}
}, [strategies, hasCheckedStrategy]);
// If strategiesArray.length === 0 and !hasCheckedStrategy, do nothing (wait for data to load)
}, [strategies, loadStrategies]);
const checkStrategyStatus = () => {
if (strategies && strategies.length > 0) {
console.log('🔍 Checking strategy status...');
console.log('🔍 Strategies from store:', strategies);
console.log('🔍 Strategies type:', typeof strategies);
console.log('🔍 Is Array:', Array.isArray(strategies));
console.log('🔍 Strategies length:', strategies?.length);
// Handle different response formats
let strategiesArray: any[] = [];
if (Array.isArray(strategies)) {
// Direct array
strategiesArray = strategies;
} else if (strategies && typeof strategies === 'object' && 'strategies' in strategies && Array.isArray((strategies as any).strategies)) {
// API response object with strategies array
strategiesArray = (strategies as any).strategies;
}
console.log('🔍 StrategiesArray length:', strategiesArray.length);
if (strategiesArray.length > 0) {
// Find the most recent strategy
const latestStrategy = strategies[0]; // Assuming strategies are sorted by date
const latestStrategy = strategiesArray[0]; // Assuming strategies are sorted by date
console.log('✅ Found strategies in database:', strategiesArray.length);
console.log('📊 Latest strategy:', latestStrategy);
// For now, we'll assume strategies are active if they exist
// In a real implementation, you would check a status field from the database
setStrategyStatus('active');
setShowOnboarding(false);
} else {
console.log('❌ No strategies found in database');
setStrategyStatus('none');
setShowOnboarding(true);
}
@@ -154,20 +199,16 @@ const ContentStrategyTab: React.FC = () => {
contentPlanningApi.handleSSEData(
eventSource,
(data) => {
console.log('Strategic Intelligence SSE Data:', data);
if (data.type === 'status') {
// Update loading message
console.log('Status:', data.message);
} else if (data.type === 'progress') {
// Update progress (could be used for progress bar)
console.log('Progress:', data.progress, '%');
} else if (data.type === 'result' && data.status === 'success') {
// Set the strategic intelligence data
setStrategicIntelligence(data.data);
setDataLoading(prev => ({ ...prev, strategicIntelligence: false }));
} else if (data.type === 'error') {
console.error('Strategic Intelligence Error:', data.message);
// Set fallback data on error
setStrategicIntelligence({
market_positioning: {
@@ -188,7 +229,6 @@ const ContentStrategyTab: React.FC = () => {
}
},
(error) => {
console.error('Strategic Intelligence SSE Error:', error);
// Set fallback data on error
setStrategicIntelligence({
market_positioning: {
@@ -356,13 +396,7 @@ const ContentStrategyTab: React.FC = () => {
</Alert>
)}
{strategyStatus === 'active' && (
<Alert severity="success" sx={{ mb: 3 }}>
<Typography variant="body1">
<strong>Strategy Active:</strong> Your content strategy is running and ALwrity is managing your content marketing automatically.
</Typography>
</Alert>
)}
{/* Strategic Intelligence */}
<Paper sx={{ width: '100%', mb: 3 }}>