ALwrity version 0.5.6
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 }}>
|
||||
|
||||
@@ -476,9 +476,24 @@ export const useContentPlanningStore = create<ContentPlanningStore>((set, get) =
|
||||
loadStrategies: async () => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
console.log('🔍 Loading strategies from API...');
|
||||
const strategies = await contentPlanningApi.getStrategiesSafe();
|
||||
set({ strategies, loading: false });
|
||||
console.log('🔍 API response for strategies:', strategies);
|
||||
console.log('🔍 Strategies type:', typeof strategies);
|
||||
console.log('🔍 Is Array:', Array.isArray(strategies));
|
||||
|
||||
if (Array.isArray(strategies)) {
|
||||
console.log('✅ Strategies loaded successfully (direct array):', strategies.length);
|
||||
set({ strategies, loading: false });
|
||||
} else if (strategies && strategies.strategies && Array.isArray(strategies.strategies)) {
|
||||
console.log('✅ Strategies found in response.strategies:', strategies.strategies.length);
|
||||
set({ strategies: strategies.strategies, loading: false });
|
||||
} else {
|
||||
console.log('❌ No strategies found in response');
|
||||
set({ strategies: [], loading: false });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ Error loading strategies:', error);
|
||||
set({ error: error.message || 'Failed to load strategies', loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
189
frontend/src/stores/strategyReviewStore.ts
Normal file
189
frontend/src/stores/strategyReviewStore.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
|
||||
export type ReviewStatus = 'not_reviewed' | 'in_review' | 'reviewed';
|
||||
|
||||
export interface StrategyComponent {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
status: ReviewStatus;
|
||||
reviewedAt?: Date;
|
||||
reviewedBy?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface ReviewState {
|
||||
// Review state
|
||||
components: StrategyComponent[];
|
||||
isReviewing: boolean;
|
||||
reviewProgress: number;
|
||||
|
||||
// Actions
|
||||
initializeComponents: (components: Omit<StrategyComponent, 'status' | 'reviewedAt' | 'reviewedBy' | 'notes'>[]) => void;
|
||||
startReview: (componentId: string) => void;
|
||||
completeReview: (componentId: string, notes?: string) => void;
|
||||
resetReview: (componentId: string) => void;
|
||||
resetAllReviews: () => void;
|
||||
updateReviewProgress: () => void;
|
||||
getReviewProgress: () => number;
|
||||
isAllReviewed: () => boolean;
|
||||
getUnreviewedComponents: () => StrategyComponent[];
|
||||
getReviewedComponents: () => StrategyComponent[];
|
||||
}
|
||||
|
||||
const STRATEGY_COMPONENTS = [
|
||||
{
|
||||
id: 'strategic_insights',
|
||||
title: 'Strategic Insights',
|
||||
subtitle: 'AI-powered market analysis'
|
||||
},
|
||||
{
|
||||
id: 'competitive_analysis',
|
||||
title: 'Competitive Analysis',
|
||||
subtitle: 'Market positioning insights'
|
||||
},
|
||||
{
|
||||
id: 'performance_predictions',
|
||||
title: 'Performance Predictions',
|
||||
subtitle: 'ROI and success metrics'
|
||||
},
|
||||
{
|
||||
id: 'implementation_roadmap',
|
||||
title: 'Implementation Roadmap',
|
||||
subtitle: 'Project timeline and phases'
|
||||
},
|
||||
{
|
||||
id: 'risk_assessment',
|
||||
title: 'Risk Assessment',
|
||||
subtitle: 'Risk analysis and mitigation'
|
||||
}
|
||||
];
|
||||
|
||||
export const useStrategyReviewStore = create<ReviewState>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
// Initial state
|
||||
components: [],
|
||||
isReviewing: false,
|
||||
reviewProgress: 0,
|
||||
|
||||
// Initialize components with default review status
|
||||
initializeComponents: (components) => {
|
||||
const initializedComponents = components.map(component => ({
|
||||
...component,
|
||||
status: 'not_reviewed' as ReviewStatus
|
||||
}));
|
||||
|
||||
set({ components: initializedComponents });
|
||||
get().updateReviewProgress();
|
||||
},
|
||||
|
||||
// Start reviewing a component
|
||||
startReview: (componentId: string) => {
|
||||
set(state => ({
|
||||
isReviewing: true,
|
||||
components: state.components.map(comp =>
|
||||
comp.id === componentId
|
||||
? { ...comp, status: 'in_review' as ReviewStatus }
|
||||
: comp
|
||||
)
|
||||
}));
|
||||
},
|
||||
|
||||
// Complete review for a component
|
||||
completeReview: (componentId: string, notes?: string) => {
|
||||
set(state => ({
|
||||
isReviewing: false,
|
||||
components: state.components.map(comp =>
|
||||
comp.id === componentId
|
||||
? {
|
||||
...comp,
|
||||
status: 'reviewed' as ReviewStatus,
|
||||
reviewedAt: new Date(),
|
||||
reviewedBy: 'current_user', // In real app, get from auth
|
||||
notes
|
||||
}
|
||||
: comp
|
||||
)
|
||||
}));
|
||||
|
||||
get().updateReviewProgress();
|
||||
},
|
||||
|
||||
// Reset review for a component
|
||||
resetReview: (componentId: string) => {
|
||||
set(state => ({
|
||||
components: state.components.map(comp =>
|
||||
comp.id === componentId
|
||||
? {
|
||||
...comp,
|
||||
status: 'not_reviewed' as ReviewStatus,
|
||||
reviewedAt: undefined,
|
||||
reviewedBy: undefined,
|
||||
notes: undefined
|
||||
}
|
||||
: comp
|
||||
)
|
||||
}));
|
||||
|
||||
get().updateReviewProgress();
|
||||
},
|
||||
|
||||
// Reset all reviews
|
||||
resetAllReviews: () => {
|
||||
set(state => ({
|
||||
components: state.components.map(comp => ({
|
||||
...comp,
|
||||
status: 'not_reviewed' as ReviewStatus,
|
||||
reviewedAt: undefined,
|
||||
reviewedBy: undefined,
|
||||
notes: undefined
|
||||
}))
|
||||
}));
|
||||
|
||||
get().updateReviewProgress();
|
||||
},
|
||||
|
||||
// Update review progress
|
||||
updateReviewProgress: () => {
|
||||
const { components } = get();
|
||||
const reviewedCount = components.filter(comp => comp.status === 'reviewed').length;
|
||||
const totalCount = components.length;
|
||||
const progress = totalCount > 0 ? (reviewedCount / totalCount) * 100 : 0;
|
||||
|
||||
set({ reviewProgress: progress });
|
||||
},
|
||||
|
||||
// Get review progress percentage
|
||||
getReviewProgress: () => {
|
||||
return get().reviewProgress;
|
||||
},
|
||||
|
||||
// Check if all components are reviewed
|
||||
isAllReviewed: () => {
|
||||
const { components } = get();
|
||||
return components.every(comp => comp.status === 'reviewed');
|
||||
},
|
||||
|
||||
// Get unreviewed components
|
||||
getUnreviewedComponents: () => {
|
||||
const { components } = get();
|
||||
return components.filter(comp => comp.status !== 'reviewed');
|
||||
},
|
||||
|
||||
// Get reviewed components
|
||||
getReviewedComponents: () => {
|
||||
const { components } = get();
|
||||
return components.filter(comp => comp.status === 'reviewed');
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: 'strategy-review-store',
|
||||
enabled: process.env.NODE_ENV === 'development'
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Initialize components when store is created
|
||||
useStrategyReviewStore.getState().initializeComponents(STRATEGY_COMPONENTS);
|
||||
Reference in New Issue
Block a user