Alwrity version 0.5.4
This commit is contained in:
@@ -1,232 +0,0 @@
|
||||
import re
|
||||
import os
|
||||
import PyPDF2
|
||||
import openai
|
||||
import streamlit as st
|
||||
import tempfile
|
||||
from loguru import logger
|
||||
|
||||
|
||||
from lib.ai_writers.ai_news_article_writer import ai_news_generation
|
||||
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
|
||||
from lib.ai_writers.ai_facebook_writer.facebook_ai_writer import facebook_main_menu
|
||||
from lib.ai_writers.linkedin_writer.linkedin_ai_writer import linkedin_main_menu
|
||||
from lib.ai_writers.twitter_writers.twitter_dashboard import run_dashboard
|
||||
from lib.ai_writers.insta_ai_writer import insta_writer
|
||||
from lib.ai_writers.youtube_writers.youtube_ai_writer import youtube_main_menu
|
||||
from lib.ai_writers.ai_essay_writer import ai_essay_generator
|
||||
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
||||
#from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
|
||||
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
|
||||
|
||||
def ai_agents_team():
|
||||
# Define options for AI Content Teams
|
||||
st.title("🐲 Your AI Agents Teams")
|
||||
st.markdown("""Alwrity offers AI agents team for content creators to easily modify them for their needs.
|
||||
Abstracting tech & plumbing, easily define role, goal, task. Use different AI agents framework.""")
|
||||
|
||||
options = [
|
||||
"AI Planning Team",
|
||||
"AI Content Creation Team"
|
||||
]
|
||||
|
||||
# Radio button for choosing an AI Content Team
|
||||
selected_team = st.radio("**Choose AI Agents Team:**", options)
|
||||
|
||||
if selected_team == "AI Planning Team":
|
||||
st.title("AI Agents for Content Ideation")
|
||||
plan_keywords = st.text_input(
|
||||
"Enter Keywords to get 2 months content calendar:",
|
||||
placeholder="Enter keywords to generate AI content calendar:",
|
||||
help="Enter at least two words for better results."
|
||||
)
|
||||
if st.button("Get calendar"):
|
||||
if plan_keywords and len(plan_keywords.split()) >= 2:
|
||||
with st.spinner("Get Content Plan..."):
|
||||
try:
|
||||
#plan_content = ai_agents_content_planner(plan_keywords)
|
||||
st.success(f"Coming soon: Content plan for: {plan_keywords}")
|
||||
#st.markdown(plan_content)
|
||||
except Exception as err:
|
||||
st.error(f"Failed to generate content plan: {err}")
|
||||
else:
|
||||
st.error("🚫 Single keywords are just too vague. Try again.")
|
||||
elif selected_team == "AI Content Creation Team":
|
||||
content_agents()
|
||||
|
||||
|
||||
|
||||
def content_agents():
|
||||
st.markdown("AI Agents Team for Content Writing")
|
||||
content_keywords = st.text_input(
|
||||
"Enter Main Domain Keywords of your business:",
|
||||
placeholder="Better keywords, Better content. Get keywords from Google search",
|
||||
help="These keywords define your main business sector, blogging niche, Industry, domain etc"
|
||||
)
|
||||
|
||||
if st.button("Start Writing"):
|
||||
if content_keywords and len(content_keywords.split()) >= 2:
|
||||
with st.spinner("Generating Content..."):
|
||||
try:
|
||||
#calendar_content = ai_agents_writers(content_keywords)
|
||||
st.success(f"🚫 Not implemented yet: {content_keywords}")
|
||||
#st.markdown(calendar_content)
|
||||
except Exception as err:
|
||||
st.error(f"🚫 Failed to generate content with AI Agents: {err}")
|
||||
else:
|
||||
st.error("🚫 Single keywords are just too vague. Try again.")
|
||||
|
||||
|
||||
|
||||
def essay_writer():
|
||||
st.title("AI Essay Writer 📝")
|
||||
st.write("Select your essay type, education level, and desired length, then let AI generate an essay for you. ✨")
|
||||
|
||||
# Define essay types and education levels
|
||||
essay_types = [
|
||||
"📖 Argumentative - Forming an opinion via research. Building an evidence-based argument.",
|
||||
"📚 Expository - Knowledge of a topic. Communicating information clearly.",
|
||||
"✒️ Narrative - Creative language use. Presenting a compelling narrative.",
|
||||
"🎨 Descriptive - Creative language use. Describing sensory details."
|
||||
]
|
||||
|
||||
education_levels = [
|
||||
"🏫 Primary School",
|
||||
"🏫 High School",
|
||||
"🎓 College",
|
||||
"🎓 Graduate School"
|
||||
]
|
||||
|
||||
# Define the options for number of pages
|
||||
num_pages_options = [
|
||||
"📄 Short Form (1-2 pages)",
|
||||
"📄📄 Medium Form (3-5 pages)",
|
||||
"📄📄📄 Long Form (6+ pages)"
|
||||
]
|
||||
|
||||
# Create columns for input fields
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
# Ask the user for the title of the essay
|
||||
essay_title = st.text_input("📝 Essay Title", placeholder="Enter the title of your essay", help="Provide a clear and concise title for your essay.")
|
||||
|
||||
# Ask the user for type of essay
|
||||
selected_essay_type = st.selectbox("📚 Type of Essay", options=essay_types, help="Choose the type of essay you want to write.")
|
||||
|
||||
with col2:
|
||||
# Ask the user for level of education
|
||||
selected_education_level = st.selectbox("🎓 Level of Education", options=education_levels, help="Choose your level of education.")
|
||||
|
||||
# Ask the user for number of pages
|
||||
selected_num_pages = st.selectbox("📄 Number of Pages", options=num_pages_options, help="Select the length of your essay.")
|
||||
|
||||
if st.button("🚀 Generate Essay"):
|
||||
if essay_title:
|
||||
st.success("Generating your essay... ✨")
|
||||
ai_essay_generator(essay_title, selected_essay_type, selected_education_level, selected_num_pages)
|
||||
else:
|
||||
st.error("Please enter a valid title for your essay. 🚫")
|
||||
|
||||
|
||||
def ai_news_writer():
|
||||
""" AI News Writer """
|
||||
st.markdown("<h1>📰 AI News Writer 🗞️ </h1>", unsafe_allow_html=True)
|
||||
|
||||
# Input for news keywords
|
||||
news_keywords = st.text_input(
|
||||
"**🔑 Enter Keywords from News Headlines:**",
|
||||
placeholder="Describe the News article in 3-5 words. Enter main keywords describing the News Event:",
|
||||
help="Enter at least two words for better results."
|
||||
)
|
||||
|
||||
if news_keywords and len(news_keywords.split()) < 2:
|
||||
st.error("🚫 News keywords should be at least two words long. Least, you can do..")
|
||||
|
||||
# Selectbox for country and language
|
||||
countries = [
|
||||
("es", "Spain"),
|
||||
("vn", "Vietnam"),
|
||||
("pk", "Pakistan"),
|
||||
("in", "India"),
|
||||
("de", "Germany"),
|
||||
("cn", "China")
|
||||
]
|
||||
|
||||
languages = [
|
||||
("en", "English"),
|
||||
("es", "Spanish"),
|
||||
("vi", "Vietnamese"),
|
||||
("ar", "Arabic"),
|
||||
("hi", "Hindi"),
|
||||
("de", "German"),
|
||||
("zh-cn", "Chinese")
|
||||
]
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
news_country = st.selectbox("**🌍 Select Origin Country of News Event:**",
|
||||
countries, format_func=lambda x: x[1], help="Which country did the NEWS originate from ?")
|
||||
with col2:
|
||||
news_language = st.selectbox("**🗣️ Select News Article Language to Search For:**",
|
||||
languages, format_func=lambda x: x[1], help="Language to output News Article in ?")
|
||||
|
||||
if st.button("📰 Generate News Report"):
|
||||
if news_keywords and len(news_keywords.split()) >= 2:
|
||||
with st.spinner("Generating News Report... ⏳"):
|
||||
try:
|
||||
news_report = ai_news_generation(news_keywords, news_country, news_language)
|
||||
st.success(f"Successfully generated news report on: {news_keywords} 🎉")
|
||||
st.markdown(news_report)
|
||||
except Exception as err:
|
||||
st.error(f"Failed to generate news report: {err} ❌")
|
||||
else:
|
||||
st.error("Please enter valid keywords for the news report. 🚫")
|
||||
|
||||
|
||||
def ai_finance_ta_writer():
|
||||
st.markdown("<div class='sub-header'>AI Financial Technical Analysis Writer</div>", unsafe_allow_html=True)
|
||||
|
||||
ticker_symbol = st.text_input(
|
||||
"Enter Ticker Symbol for TA:",
|
||||
placeholder="Enter a valid Ticker Symbol (Examples: IBM, BABA, HDFCBANK.NS, TATAMOTORS.NS etc)",
|
||||
help="Be sure of the ticker symbol. Double-check it! Examples: IBM, BABA, HDFCBANK.NS, TATAMOTORS.NS"
|
||||
)
|
||||
|
||||
if st.button("Generate TA Report"):
|
||||
if ticker_symbol:
|
||||
with st.spinner("Generating TA Report..."):
|
||||
try:
|
||||
# Get dashboard instance and generate technical analysis
|
||||
dashboard = get_dashboard()
|
||||
ta_report = dashboard.generate_technical_analysis(ticker_symbol)
|
||||
st.success(f"Successfully generated TA report for: {ticker_symbol}")
|
||||
st.markdown(ta_report)
|
||||
except Exception as err:
|
||||
st.error(f"🚫 Check ticker symbol: Failed to write Financial Technical Analysis. Error: {err}")
|
||||
else:
|
||||
st.error("🚫 Provide a valid Ticker Symbol. Don't waste my time.")
|
||||
|
||||
def ai_social_writer():
|
||||
# Define social media platforms as radio buttons
|
||||
social_media_options = [
|
||||
("facebook", "Facebook"),
|
||||
("linkedin", "LinkedIn"),
|
||||
("twitter", "Twitter"),
|
||||
("instagram", "Instagram"),
|
||||
("youtube", "YouTube")
|
||||
]
|
||||
|
||||
# Selectbox for choosing a platform
|
||||
selected_platform = st.radio("Choose a Social Media Platform:", social_media_options, format_func=lambda x: x[1])
|
||||
if "facebook" in selected_platform:
|
||||
facebook_main_menu()
|
||||
elif "linkedin" in selected_platform:
|
||||
linkedin_main_menu()
|
||||
elif "twitter" in selected_platform:
|
||||
run_dashboard()
|
||||
elif "instagram" in selected_platform:
|
||||
insta_writer()
|
||||
elif "youtube" in selected_platform:
|
||||
youtube_main_menu()
|
||||
@@ -1,310 +0,0 @@
|
||||
"""Test configuration settings page for ALwrity."""
|
||||
|
||||
import streamlit as st
|
||||
from loguru import logger
|
||||
import asyncio
|
||||
from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService
|
||||
from pages.style_utils import (
|
||||
get_test_config_styles,
|
||||
get_glass_container,
|
||||
get_info_section,
|
||||
get_example_box,
|
||||
get_analysis_section,
|
||||
get_style_guide_html
|
||||
)
|
||||
import sys
|
||||
from lib.personalization.style_analyzer import StyleAnalyzer
|
||||
|
||||
# Set page config - must be the first Streamlit command
|
||||
st.set_page_config(
|
||||
layout="wide",
|
||||
initial_sidebar_state="collapsed",
|
||||
menu_items={
|
||||
'Get Help': None,
|
||||
'Report a bug': None,
|
||||
'About': None
|
||||
}
|
||||
)
|
||||
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
import os
|
||||
from loguru import logger
|
||||
from lib.utils.read_main_config_params import get_personalization_settings
|
||||
from lib.web_crawlers.crawl4ai_web_crawler import analyze_style
|
||||
|
||||
# Configure logger
|
||||
logger.remove() # Remove default handler
|
||||
logger.add(
|
||||
"logs/test_config_settings.log",
|
||||
rotation="500 MB",
|
||||
retention="10 days",
|
||||
level="DEBUG",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
|
||||
backtrace=True,
|
||||
diagnose=True
|
||||
)
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level="INFO",
|
||||
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
|
||||
)
|
||||
|
||||
# Apply CSS styles
|
||||
st.markdown(get_test_config_styles(), unsafe_allow_html=True)
|
||||
|
||||
def load_website_url():
|
||||
"""Load website URL from config file."""
|
||||
try:
|
||||
logger.debug("Loading website URL from config file")
|
||||
config_path = Path(os.environ["ALWRITY_CONFIG"])
|
||||
config = yaml.safe_load(config_path.read_text())
|
||||
url = config.get('website', {}).get('url', '')
|
||||
logger.info(f"Loaded website URL: {url}")
|
||||
return url
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading website URL: {str(e)}", exc_info=True)
|
||||
return ''
|
||||
|
||||
def display_style_analysis(analysis_results: dict):
|
||||
"""Display the style analysis results in a structured format."""
|
||||
try:
|
||||
# Writing Style Section
|
||||
st.markdown("### 🎨 Writing Style Analysis")
|
||||
writing_style = analysis_results.get("writing_style", {})
|
||||
writing_style_content = f"""
|
||||
<ul>
|
||||
<li><strong>Tone:</strong> {writing_style.get("tone", "N/A")}</li>
|
||||
<li><strong>Voice:</strong> {writing_style.get("voice", "N/A")}</li>
|
||||
<li><strong>Complexity:</strong> {writing_style.get("complexity", "N/A")}</li>
|
||||
<li><strong>Engagement Level:</strong> {writing_style.get("engagement_level", "N/A")}</li>
|
||||
</ul>
|
||||
"""
|
||||
st.markdown(get_analysis_section("Writing Style", writing_style_content), unsafe_allow_html=True)
|
||||
|
||||
# Content Characteristics Section
|
||||
content_chars = analysis_results.get("content_characteristics", {})
|
||||
content_chars_content = f"""
|
||||
<ul>
|
||||
<li><strong>Sentence Structure:</strong> {content_chars.get("sentence_structure", "N/A")}</li>
|
||||
<li><strong>Vocabulary Level:</strong> {content_chars.get("vocabulary_level", "N/A")}</li>
|
||||
<li><strong>Paragraph Organization:</strong> {content_chars.get("paragraph_organization", "N/A")}</li>
|
||||
<li><strong>Content Flow:</strong> {content_chars.get("content_flow", "N/A")}</li>
|
||||
</ul>
|
||||
"""
|
||||
st.markdown(get_analysis_section("Content Characteristics", content_chars_content), unsafe_allow_html=True)
|
||||
|
||||
# Target Audience Section
|
||||
target_audience = analysis_results.get("target_audience", {})
|
||||
target_audience_content = f"""
|
||||
<ul>
|
||||
<li><strong>Demographics:</strong> {', '.join(target_audience.get("demographics", ["N/A"]))}</li>
|
||||
<li><strong>Expertise Level:</strong> {target_audience.get("expertise_level", "N/A")}</li>
|
||||
<li><strong>Industry Focus:</strong> {target_audience.get("industry_focus", "N/A")}</li>
|
||||
<li><strong>Geographic Focus:</strong> {target_audience.get("geographic_focus", "N/A")}</li>
|
||||
</ul>
|
||||
"""
|
||||
st.markdown(get_analysis_section("Target Audience", target_audience_content), unsafe_allow_html=True)
|
||||
|
||||
# Content Type Section
|
||||
content_type = analysis_results.get("content_type", {})
|
||||
content_type_content = f"""
|
||||
<ul>
|
||||
<li><strong>Primary Type:</strong> {content_type.get("primary_type", "N/A")}</li>
|
||||
<li><strong>Secondary Types:</strong> {', '.join(content_type.get("secondary_types", ["N/A"]))}</li>
|
||||
<li><strong>Purpose:</strong> {content_type.get("purpose", "N/A")}</li>
|
||||
<li><strong>Call to Action:</strong> {content_type.get("call_to_action", "N/A")}</li>
|
||||
</ul>
|
||||
"""
|
||||
st.markdown(get_analysis_section("Content Type", content_type_content), unsafe_allow_html=True)
|
||||
|
||||
# Recommended Settings Section
|
||||
recommended = analysis_results.get("recommended_settings", {})
|
||||
recommended_content = f"""
|
||||
<ul>
|
||||
<li><strong>Writing Tone:</strong> {recommended.get("writing_tone", "N/A")}</li>
|
||||
<li><strong>Target Audience:</strong> {recommended.get("target_audience", "N/A")}</li>
|
||||
<li><strong>Content Type:</strong> {recommended.get("content_type", "N/A")}</li>
|
||||
<li><strong>Creativity Level:</strong> {recommended.get("creativity_level", "N/A")}</li>
|
||||
<li><strong>Geographic Location:</strong> {recommended.get("geographic_location", "N/A")}</li>
|
||||
</ul>
|
||||
"""
|
||||
st.markdown(get_analysis_section("Recommended Settings", recommended_content), unsafe_allow_html=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error displaying style analysis: {str(e)}")
|
||||
st.error(f"Error displaying analysis results: {str(e)}")
|
||||
|
||||
def render_test_config_settings():
|
||||
"""Render the test configuration settings page."""
|
||||
try:
|
||||
logger.info("Starting to render test configuration settings")
|
||||
|
||||
# Add back button at the top
|
||||
col1, col2 = st.columns([1, 3])
|
||||
with col1:
|
||||
if st.button("← Back to Personalization Setup"):
|
||||
logger.info("User clicked back to personalization setup")
|
||||
# Set session state for navigation
|
||||
st.session_state.current_step = 4
|
||||
st.session_state.next_step = "personalization_setup"
|
||||
# Navigate back to the main page where personalization setup is rendered
|
||||
st.switch_page("alwrity.py")
|
||||
|
||||
# Title and description
|
||||
st.title("🎨 Find Your Style with ALwrity")
|
||||
st.markdown(get_glass_container(
|
||||
"<p>Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations.</p>"
|
||||
), unsafe_allow_html=True)
|
||||
|
||||
# Create two columns for the layout
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
# Website URL input
|
||||
st.markdown("### Website URL")
|
||||
url = st.text_input(
|
||||
"Enter your website URL",
|
||||
placeholder="https://example.com",
|
||||
help="Provide your website URL to analyze your content style. Leave empty if you want to provide written samples instead."
|
||||
)
|
||||
logger.debug(f"Website URL input value: {url}")
|
||||
|
||||
# Alternative: Written samples
|
||||
if not url:
|
||||
st.markdown("### Written Samples")
|
||||
st.markdown(get_info_section("""
|
||||
<p>No website URL? No problem! You can provide written samples of your content instead.</p>
|
||||
<p>Share your best articles, blog posts, or any content that represents your writing style.</p>
|
||||
"""), unsafe_allow_html=True)
|
||||
samples = st.text_area(
|
||||
"Paste your content samples here",
|
||||
help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style."
|
||||
)
|
||||
logger.debug(f"Sample text length: {len(samples) if samples else 0}")
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# ALwrity Style button
|
||||
st.markdown("<div style='height: 20px'></div>", unsafe_allow_html=True)
|
||||
if st.button("🎨 ALwrity Style", use_container_width=True):
|
||||
if url:
|
||||
with st.status("Starting style analysis...", expanded=True) as status:
|
||||
try:
|
||||
logger.info(f"Starting style analysis for URL: {url}")
|
||||
|
||||
# Step 1: Initialize crawler
|
||||
status.update(label="Step 1/4: Initializing web crawler...", state="running")
|
||||
crawler_service = AsyncWebCrawlerService()
|
||||
|
||||
# Step 2: Crawl website
|
||||
status.update(label="Step 2/4: Crawling website content...", state="running")
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
result = loop.run_until_complete(crawler_service.crawl_website(url))
|
||||
loop.close()
|
||||
|
||||
if result.get('success', False):
|
||||
content = result.get('content', {})
|
||||
|
||||
# Step 3: Initialize style analyzer
|
||||
status.update(label="Step 3/4: Analyzing content style...", state="running")
|
||||
style_analyzer = StyleAnalyzer()
|
||||
|
||||
# Step 4: Perform style analysis
|
||||
status.update(label="Step 4/4: Generating style recommendations...", state="running")
|
||||
style_analysis = style_analyzer.analyze_content_style(content)
|
||||
|
||||
if style_analysis.get('error'):
|
||||
status.update(label="Analysis failed", state="error")
|
||||
st.error(f"Style analysis failed: {style_analysis['error']}")
|
||||
else:
|
||||
status.update(label="Analysis complete!", state="complete")
|
||||
# Display style analysis results
|
||||
display_style_analysis(style_analysis)
|
||||
|
||||
# Display original content in tabs
|
||||
tab1, tab2, tab3 = st.tabs(["Content", "Metadata", "Links"])
|
||||
|
||||
with tab1:
|
||||
st.markdown("### Main Content")
|
||||
st.markdown(content.get('main_content', 'No content found'))
|
||||
|
||||
with tab2:
|
||||
st.markdown("### Metadata")
|
||||
st.markdown(f"""
|
||||
**Title:** {content.get('title', 'No title found')}
|
||||
|
||||
**Description:** {content.get('description', 'No description found')}
|
||||
|
||||
**Meta Tags:**
|
||||
{content.get('meta_tags', {})}
|
||||
""")
|
||||
|
||||
with tab3:
|
||||
st.markdown("### Links")
|
||||
for link in content.get('links', []):
|
||||
st.markdown(f"- [{link.get('text', '')}]({link.get('href', '')})")
|
||||
|
||||
else:
|
||||
status.update(label="Crawling failed", state="error")
|
||||
st.error(f"Failed to analyze website: {result.get('error', 'Unknown error')}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during style analysis: {str(e)}")
|
||||
st.error(f"Analysis failed: {str(e)}")
|
||||
elif samples:
|
||||
with st.spinner("Analyzing content samples..."):
|
||||
try:
|
||||
# TODO: Implement sample text analysis
|
||||
st.info("Sample text analysis coming soon!")
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing samples: {str(e)}")
|
||||
st.error(f"Analysis failed: {str(e)}")
|
||||
else:
|
||||
st.warning("Please provide either a website URL or content samples")
|
||||
|
||||
with col2:
|
||||
st.markdown("""
|
||||
### How ALwrity Discovers Your Style
|
||||
|
||||
**AI-Powered Style Analysis**
|
||||
|
||||
ALwrity AI analyzes your existing content to understand your unique writing style and preferences. This helps us generate content that matches your voice perfectly.
|
||||
|
||||
**Step 1: Content Analysis**
|
||||
|
||||
We'll analyze your website content or written samples to understand:
|
||||
|
||||
- Writing tone and voice
|
||||
- Vocabulary and language style
|
||||
- Content structure and formatting
|
||||
- Target audience and engagement style
|
||||
|
||||
**Step 2: Style Recommendations**
|
||||
|
||||
Based on the analysis, we'll provide:
|
||||
|
||||
- Personalized writing guidelines
|
||||
- Content structure templates
|
||||
- Tone and voice recommendations
|
||||
- Audience engagement strategies
|
||||
|
||||
**Step 3: Content Generation**
|
||||
|
||||
Finally, we'll use these insights to:
|
||||
|
||||
- Generate content that matches your style
|
||||
- Maintain consistency across all content
|
||||
- Optimize for your target audience
|
||||
- Ensure brand voice alignment
|
||||
""")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in render_test_config_settings: {str(e)}")
|
||||
st.error(f"An error occurred: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting test config settings page")
|
||||
render_test_config_settings()
|
||||
logger.info("Test config settings page rendered successfully")
|
||||
@@ -1,23 +0,0 @@
|
||||
import streamlit as st
|
||||
from streamlit_mic_recorder import speech_to_text
|
||||
|
||||
def record_voice(language="en"):
|
||||
# https://github.com/B4PT0R/streamlit-mic-recorder?tab=readme-ov-file#example
|
||||
state = st.session_state
|
||||
if "text_received" not in state:
|
||||
state.text_received = []
|
||||
|
||||
text = speech_to_text(
|
||||
start_prompt="🎙️Press & Speak🔊",
|
||||
stop_prompt="🔇Stop Recording🚨",
|
||||
language=language,
|
||||
use_container_width=True,
|
||||
just_once=False,
|
||||
)
|
||||
if text:
|
||||
state.text_received.append(text)
|
||||
result = ""
|
||||
for text in state.text_received:
|
||||
result += text
|
||||
state.text_received = []
|
||||
return result if result else None
|
||||
@@ -1,45 +0,0 @@
|
||||
"""Data models for website analysis results."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Optional
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class SEORecommendation:
|
||||
"""A single SEO recommendation."""
|
||||
priority: str # 'high', 'medium', 'low'
|
||||
category: str # 'content', 'technical', 'meta', etc.
|
||||
issue: str
|
||||
recommendation: str
|
||||
impact: str
|
||||
|
||||
@dataclass
|
||||
class MetaTagAnalysis:
|
||||
"""Analysis of meta tags."""
|
||||
title: Dict[str, str] # {'status': 'good', 'value': 'actual title', 'recommendation': 'suggestion'}
|
||||
description: Dict[str, str]
|
||||
keywords: Dict[str, str]
|
||||
has_robots: bool
|
||||
has_sitemap: bool
|
||||
|
||||
@dataclass
|
||||
class ContentAnalysis:
|
||||
"""Analysis of page content."""
|
||||
word_count: int
|
||||
headings_structure: Dict[str, int] # {'h1': 1, 'h2': 3, etc}
|
||||
keyword_density: Dict[str, float]
|
||||
readability_score: float
|
||||
content_quality_score: float
|
||||
|
||||
@dataclass
|
||||
class SEOAnalysisResult:
|
||||
"""Complete SEO analysis result."""
|
||||
url: str
|
||||
analyzed_at: datetime
|
||||
overall_score: float # 0-100
|
||||
meta_tags: MetaTagAnalysis
|
||||
content: ContentAnalysis
|
||||
recommendations: List[SEORecommendation]
|
||||
errors: List[str]
|
||||
warnings: List[str]
|
||||
success: bool
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Content Strategy Educational Content Module
|
||||
Provides educational content and messages for strategy generation process.
|
||||
"""
|
||||
|
||||
from .educational_content import EducationalContentManager
|
||||
|
||||
__all__ = ['EducationalContentManager']
|
||||
@@ -0,0 +1,319 @@
|
||||
"""
|
||||
Educational Content Manager
|
||||
Manages educational content and messages for strategy generation process.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class EducationalContentManager:
|
||||
"""Manages educational content for strategy generation steps."""
|
||||
|
||||
@staticmethod
|
||||
def get_initialization_content() -> Dict[str, Any]:
|
||||
"""Get educational content for initialization step."""
|
||||
return {
|
||||
"title": "🤖 AI-Powered Strategy Generation",
|
||||
"description": "Initializing AI analysis and preparing educational content...",
|
||||
"details": [
|
||||
"🔧 Setting up AI services",
|
||||
"📊 Loading user context",
|
||||
"🎯 Preparing strategy framework",
|
||||
"📚 Generating educational content"
|
||||
],
|
||||
"insight": "We're getting everything ready for your personalized AI strategy generation.",
|
||||
"estimated_time": "2-3 minutes total"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_step_content(step: int) -> Dict[str, Any]:
|
||||
"""Get educational content for a specific step."""
|
||||
step_content = {
|
||||
1: EducationalContentManager._get_user_context_content(),
|
||||
2: EducationalContentManager._get_foundation_content(),
|
||||
3: EducationalContentManager._get_strategic_insights_content(),
|
||||
4: EducationalContentManager._get_competitive_analysis_content(),
|
||||
5: EducationalContentManager._get_performance_predictions_content(),
|
||||
6: EducationalContentManager._get_implementation_roadmap_content(),
|
||||
7: EducationalContentManager._get_compilation_content(),
|
||||
8: EducationalContentManager._get_completion_content()
|
||||
}
|
||||
|
||||
return step_content.get(step, EducationalContentManager._get_default_content())
|
||||
|
||||
@staticmethod
|
||||
def get_step_completion_content(step: int, result_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Get educational content for step completion."""
|
||||
completion_content = {
|
||||
3: EducationalContentManager._get_strategic_insights_completion(result_data),
|
||||
4: EducationalContentManager._get_competitive_analysis_completion(result_data),
|
||||
5: EducationalContentManager._get_performance_predictions_completion(result_data),
|
||||
6: EducationalContentManager._get_implementation_roadmap_completion(result_data)
|
||||
}
|
||||
|
||||
return completion_content.get(step, EducationalContentManager._get_default_completion())
|
||||
|
||||
@staticmethod
|
||||
def _get_user_context_content() -> Dict[str, Any]:
|
||||
"""Get educational content for user context analysis."""
|
||||
return {
|
||||
"title": "🔍 Analyzing Your Data",
|
||||
"description": "We're gathering all your onboarding information to create a personalized strategy.",
|
||||
"details": [
|
||||
"📊 Website analysis data",
|
||||
"🎯 Research preferences",
|
||||
"🔑 API configurations",
|
||||
"📈 Historical performance metrics"
|
||||
],
|
||||
"insight": "Your data helps us understand your business context, target audience, and competitive landscape.",
|
||||
"ai_prompt_preview": "Analyzing user onboarding data to extract business context, audience insights, and competitive positioning..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_foundation_content() -> Dict[str, Any]:
|
||||
"""Get educational content for foundation building."""
|
||||
return {
|
||||
"title": "🏗️ Building Foundation",
|
||||
"description": "Creating the core strategy framework based on your business objectives.",
|
||||
"details": [
|
||||
"🎯 Business objectives mapping",
|
||||
"📊 Target metrics definition",
|
||||
"💰 Budget allocation strategy",
|
||||
"⏰ Timeline planning"
|
||||
],
|
||||
"insight": "A solid foundation ensures your content strategy aligns with business goals and resources.",
|
||||
"ai_prompt_preview": "Generating strategic foundation: business objectives, target metrics, budget allocation, and timeline planning..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_strategic_insights_content() -> Dict[str, Any]:
|
||||
"""Get educational content for strategic insights generation."""
|
||||
return {
|
||||
"title": "🧠 Strategic Intelligence Analysis",
|
||||
"description": "AI is analyzing your market position and identifying strategic opportunities.",
|
||||
"details": [
|
||||
"🎯 Market positioning analysis",
|
||||
"💡 Opportunity identification",
|
||||
"📈 Growth potential assessment",
|
||||
"🎪 Competitive advantage mapping"
|
||||
],
|
||||
"insight": "Strategic insights help you understand where you stand in the market and how to differentiate.",
|
||||
"ai_prompt_preview": "Analyzing market position, identifying strategic opportunities, assessing growth potential, and mapping competitive advantages...",
|
||||
"estimated_time": "15-20 seconds"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_competitive_analysis_content() -> Dict[str, Any]:
|
||||
"""Get educational content for competitive analysis."""
|
||||
return {
|
||||
"title": "🔍 Competitive Intelligence Analysis",
|
||||
"description": "AI is analyzing your competitors to identify gaps and opportunities.",
|
||||
"details": [
|
||||
"🏢 Competitor content strategies",
|
||||
"📊 Market gap analysis",
|
||||
"🎯 Differentiation opportunities",
|
||||
"📈 Industry trend analysis"
|
||||
],
|
||||
"insight": "Understanding your competitors helps you find unique angles and underserved market segments.",
|
||||
"ai_prompt_preview": "Analyzing competitor content strategies, identifying market gaps, finding differentiation opportunities, and assessing industry trends...",
|
||||
"estimated_time": "20-25 seconds"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_performance_predictions_content() -> Dict[str, Any]:
|
||||
"""Get educational content for performance predictions."""
|
||||
return {
|
||||
"title": "📊 Performance Forecasting",
|
||||
"description": "AI is predicting content performance and ROI based on industry data.",
|
||||
"details": [
|
||||
"📈 Traffic growth projections",
|
||||
"💰 ROI predictions",
|
||||
"🎯 Conversion rate estimates",
|
||||
"📊 Engagement metrics forecasting"
|
||||
],
|
||||
"insight": "Performance predictions help you set realistic expectations and optimize resource allocation.",
|
||||
"ai_prompt_preview": "Analyzing industry benchmarks, predicting traffic growth, estimating ROI, forecasting conversion rates, and projecting engagement metrics...",
|
||||
"estimated_time": "15-20 seconds"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_implementation_roadmap_content() -> Dict[str, Any]:
|
||||
"""Get educational content for implementation roadmap."""
|
||||
return {
|
||||
"title": "🗺️ Implementation Roadmap",
|
||||
"description": "AI is creating a detailed implementation plan for your content strategy.",
|
||||
"details": [
|
||||
"📋 Task breakdown and timeline",
|
||||
"👥 Resource allocation planning",
|
||||
"🎯 Milestone definition",
|
||||
"📊 Success metric tracking"
|
||||
],
|
||||
"insight": "A clear implementation roadmap ensures successful strategy execution and measurable results.",
|
||||
"ai_prompt_preview": "Creating implementation roadmap: task breakdown, resource allocation, milestone planning, and success metric definition...",
|
||||
"estimated_time": "15-20 seconds"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_risk_assessment_content() -> Dict[str, Any]:
|
||||
"""Get educational content for risk assessment."""
|
||||
return {
|
||||
"title": "⚠️ Risk Assessment",
|
||||
"description": "AI is identifying potential risks and mitigation strategies for your content strategy.",
|
||||
"details": [
|
||||
"🔍 Risk identification and analysis",
|
||||
"📊 Risk probability assessment",
|
||||
"🛡️ Mitigation strategy development",
|
||||
"📈 Risk monitoring framework"
|
||||
],
|
||||
"insight": "Proactive risk assessment helps you prepare for challenges and maintain strategy effectiveness.",
|
||||
"ai_prompt_preview": "Assessing risks: identifying potential challenges, analyzing probability and impact, developing mitigation strategies, and creating monitoring framework...",
|
||||
"estimated_time": "10-15 seconds"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_compilation_content() -> Dict[str, Any]:
|
||||
"""Get educational content for strategy compilation."""
|
||||
return {
|
||||
"title": "📋 Strategy Compilation",
|
||||
"description": "AI is compiling all components into a comprehensive content strategy.",
|
||||
"details": [
|
||||
"🔗 Component integration",
|
||||
"📊 Data synthesis",
|
||||
"📝 Strategy documentation",
|
||||
"✅ Quality validation"
|
||||
],
|
||||
"insight": "A comprehensive strategy integrates all components into a cohesive, actionable plan.",
|
||||
"ai_prompt_preview": "Compiling comprehensive strategy: integrating all components, synthesizing data, documenting strategy, and validating quality...",
|
||||
"estimated_time": "5-10 seconds"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_completion_content() -> Dict[str, Any]:
|
||||
"""Get educational content for strategy completion."""
|
||||
return {
|
||||
"title": "🎉 Strategy Generation Complete!",
|
||||
"description": "Your comprehensive AI-powered content strategy is ready for review!",
|
||||
"summary": {
|
||||
"total_components": 5,
|
||||
"successful_components": 5,
|
||||
"estimated_roi": "15-25%",
|
||||
"implementation_timeline": "12 months",
|
||||
"risk_level": "Medium"
|
||||
},
|
||||
"key_achievements": [
|
||||
"🧠 Strategic insights generated",
|
||||
"🔍 Competitive analysis completed",
|
||||
"📊 Performance predictions calculated",
|
||||
"🗺️ Implementation roadmap planned",
|
||||
"⚠️ Risk assessment conducted"
|
||||
],
|
||||
"next_steps": [
|
||||
"Review your comprehensive strategy in the Strategic Intelligence tab",
|
||||
"Customize specific components as needed",
|
||||
"Confirm the strategy to proceed",
|
||||
"Generate content calendar based on confirmed strategy"
|
||||
],
|
||||
"ai_insights": "Your strategy leverages advanced AI analysis of your business context, competitive landscape, and industry best practices to create a data-driven content approach.",
|
||||
"personalization_note": "This strategy is uniquely tailored to your business based on your onboarding data, ensuring relevance and effectiveness.",
|
||||
"content_calendar_note": "Content calendar will be generated separately after you review and confirm this strategy, ensuring it's based on your final approved strategy."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_default_content() -> Dict[str, Any]:
|
||||
"""Get default educational content."""
|
||||
return {
|
||||
"title": "🔄 Processing",
|
||||
"description": "AI is working on your strategy...",
|
||||
"details": [
|
||||
"⏳ Processing in progress",
|
||||
"📊 Analyzing data",
|
||||
"🎯 Generating insights",
|
||||
"📝 Compiling results"
|
||||
],
|
||||
"insight": "The AI is working hard to create your personalized strategy.",
|
||||
"estimated_time": "A few moments"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_strategic_insights_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Get completion content for strategic insights."""
|
||||
insights_count = len(result_data.get("insights", [])) if result_data else 0
|
||||
return {
|
||||
"title": "✅ Strategic Insights Complete",
|
||||
"description": "Successfully identified key strategic opportunities and market positioning.",
|
||||
"achievement": f"Generated {insights_count} strategic insights",
|
||||
"next_step": "Moving to competitive analysis..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_competitive_analysis_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Get completion content for competitive analysis."""
|
||||
competitors_count = len(result_data.get("competitors", [])) if result_data else 0
|
||||
return {
|
||||
"title": "✅ Competitive Analysis Complete",
|
||||
"description": "Successfully analyzed competitive landscape and identified market opportunities.",
|
||||
"achievement": f"Analyzed {competitors_count} competitors",
|
||||
"next_step": "Moving to performance predictions..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_performance_predictions_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Get completion content for performance predictions."""
|
||||
estimated_roi = result_data.get("estimated_roi", "15-25%") if result_data else "15-25%"
|
||||
return {
|
||||
"title": "✅ Performance Predictions Complete",
|
||||
"description": "Successfully predicted content performance and ROI.",
|
||||
"achievement": f"Predicted {estimated_roi} ROI",
|
||||
"next_step": "Moving to implementation roadmap..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_implementation_roadmap_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Get completion content for implementation roadmap."""
|
||||
timeline = result_data.get("total_duration", "12 months") if result_data else "12 months"
|
||||
return {
|
||||
"title": "✅ Implementation Roadmap Complete",
|
||||
"description": "Successfully created detailed implementation plan.",
|
||||
"achievement": f"Planned {timeline} implementation timeline",
|
||||
"next_step": "Moving to compilation..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_risk_assessment_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Get completion content for risk assessment."""
|
||||
risk_level = result_data.get("overall_risk_level", "Medium") if result_data else "Medium"
|
||||
return {
|
||||
"title": "✅ Risk Assessment Complete",
|
||||
"description": "Successfully identified risks and mitigation strategies.",
|
||||
"achievement": f"Assessed {risk_level} risk level",
|
||||
"next_step": "Finalizing comprehensive strategy..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_default_completion() -> Dict[str, Any]:
|
||||
"""Get default completion content."""
|
||||
return {
|
||||
"title": "✅ Step Complete",
|
||||
"description": "Successfully completed this step.",
|
||||
"achievement": "Step completed successfully",
|
||||
"next_step": "Moving to next step..."
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def update_completion_summary(completion_content: Dict[str, Any], strategy_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Update completion content with actual strategy data."""
|
||||
if "summary" in completion_content:
|
||||
content_calendar = strategy_data.get("content_calendar", {})
|
||||
performance_predictions = strategy_data.get("performance_predictions", {})
|
||||
implementation_roadmap = strategy_data.get("implementation_roadmap", {})
|
||||
risk_assessment = strategy_data.get("risk_assessment", {})
|
||||
|
||||
completion_content["summary"].update({
|
||||
"total_content_pieces": len(content_calendar.get("content_pieces", [])),
|
||||
"estimated_roi": performance_predictions.get("estimated_roi", "15-25%"),
|
||||
"implementation_timeline": implementation_roadmap.get("total_duration", "12 months"),
|
||||
"risk_level": risk_assessment.get("overall_risk_level", "Medium")
|
||||
})
|
||||
|
||||
return completion_content
|
||||
@@ -1122,4 +1122,4 @@ async def refresh_autofill(
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating fresh auto-fill payload: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "refresh_autofill")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "refresh_autofill")
|
||||
@@ -60,7 +60,7 @@ class AIStrategyGenerator:
|
||||
strategy_name: Optional custom strategy name
|
||||
|
||||
Returns:
|
||||
Comprehensive strategy with all components
|
||||
Comprehensive strategy with all components (EXCLUDING content calendar)
|
||||
|
||||
Raises:
|
||||
RuntimeError: If any AI component fails to generate
|
||||
@@ -77,19 +77,16 @@ class AIStrategyGenerator:
|
||||
# Step 3: Generate competitive analysis
|
||||
competitive_analysis = await self._generate_competitive_analysis(base_strategy, context)
|
||||
|
||||
# Step 4: Generate content calendar
|
||||
content_calendar = await self._generate_content_calendar(base_strategy, context)
|
||||
|
||||
# Step 5: Generate performance predictions
|
||||
# Step 4: Generate performance predictions
|
||||
performance_predictions = await self._generate_performance_predictions(base_strategy, context)
|
||||
|
||||
# Step 6: Generate implementation roadmap
|
||||
# Step 5: Generate implementation roadmap
|
||||
implementation_roadmap = await self._generate_implementation_roadmap(base_strategy, context)
|
||||
|
||||
# Step 7: Generate risk assessment
|
||||
# Step 6: Generate risk assessment
|
||||
risk_assessment = await self._generate_risk_assessment(base_strategy, context)
|
||||
|
||||
# Step 8: Compile comprehensive strategy
|
||||
# Step 7: Compile comprehensive strategy (NO CONTENT CALENDAR)
|
||||
comprehensive_strategy = {
|
||||
"strategy_metadata": {
|
||||
"generated_at": datetime.utcnow().isoformat(),
|
||||
@@ -99,21 +96,21 @@ class AIStrategyGenerator:
|
||||
"ai_model": "gemini-pro",
|
||||
"personalization_level": "high",
|
||||
"ai_generated": True,
|
||||
"comprehensive": True
|
||||
"comprehensive": True,
|
||||
"content_calendar_ready": False # Indicates calendar needs to be generated separately
|
||||
},
|
||||
"base_strategy": base_strategy,
|
||||
"strategic_insights": strategic_insights,
|
||||
"competitive_analysis": competitive_analysis,
|
||||
"content_calendar": content_calendar,
|
||||
"performance_predictions": performance_predictions,
|
||||
"implementation_roadmap": implementation_roadmap,
|
||||
"risk_assessment": risk_assessment,
|
||||
"summary": {
|
||||
"total_content_pieces": len(content_calendar.get("content_pieces", [])),
|
||||
"estimated_roi": performance_predictions.get("estimated_roi", "15-25%"),
|
||||
"implementation_timeline": implementation_roadmap.get("total_duration", "12 months"),
|
||||
"risk_level": risk_assessment.get("overall_risk_level", "Medium"),
|
||||
"success_probability": performance_predictions.get("success_probability", "85%")
|
||||
"success_probability": performance_predictions.get("success_probability", "85%"),
|
||||
"next_step": "Review strategy and generate content calendar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,8 +331,55 @@ class AIStrategyGenerator:
|
||||
}
|
||||
},
|
||||
"themes": {"type": "array", "items": {"type": "string"}},
|
||||
"schedule": {"type": "object"},
|
||||
"distribution_strategy": {"type": "object"}
|
||||
"schedule": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"publishing_frequency": {"type": "string"},
|
||||
"optimal_times": {"type": "array", "items": {"type": "string"}},
|
||||
"content_mix": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blog_posts": {"type": "string"},
|
||||
"social_media": {"type": "string"},
|
||||
"videos": {"type": "string"},
|
||||
"infographics": {"type": "string"},
|
||||
"newsletters": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"seasonal_adjustments": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"holiday_content": {"type": "array", "items": {"type": "string"}},
|
||||
"seasonal_themes": {"type": "array", "items": {"type": "string"}},
|
||||
"peak_periods": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"distribution_strategy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primary_platforms": {"type": "array", "items": {"type": "string"}},
|
||||
"cross_posting_strategy": {"type": "string"},
|
||||
"platform_specific_content": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"linkedin_content": {"type": "array", "items": {"type": "string"}},
|
||||
"twitter_content": {"type": "array", "items": {"type": "string"}},
|
||||
"instagram_content": {"type": "array", "items": {"type": "string"}},
|
||||
"facebook_content": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"engagement_timing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"best_times": {"type": "array", "items": {"type": "string"}},
|
||||
"frequency": {"type": "string"},
|
||||
"timezone_considerations": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,8 +527,33 @@ class AIStrategyGenerator:
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {"type": "object"},
|
||||
"resource_allocation": {"type": "object"},
|
||||
"timeline": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start_date": {"type": "string"},
|
||||
"end_date": {"type": "string"},
|
||||
"key_milestones": {"type": "array", "items": {"type": "string"}},
|
||||
"critical_path": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"resource_allocation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_requirements": {"type": "array", "items": {"type": "string"}},
|
||||
"budget_allocation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total_budget": {"type": "string"},
|
||||
"content_creation": {"type": "string"},
|
||||
"technology_tools": {"type": "string"},
|
||||
"marketing_promotion": {"type": "string"},
|
||||
"external_resources": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"technology_needs": {"type": "array", "items": {"type": "string"}},
|
||||
"external_resources": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"success_metrics": {"type": "array", "items": {"type": "string"}},
|
||||
"total_duration": {"type": "string"}
|
||||
}
|
||||
@@ -552,9 +621,25 @@ class AIStrategyGenerator:
|
||||
}
|
||||
},
|
||||
"overall_risk_level": {"type": "string"},
|
||||
"risk_categories": {"type": "object"},
|
||||
"risk_categories": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"technical_risks": {"type": "array", "items": {"type": "string"}},
|
||||
"market_risks": {"type": "array", "items": {"type": "string"}},
|
||||
"operational_risks": {"type": "array", "items": {"type": "string"}},
|
||||
"financial_risks": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"mitigation_strategies": {"type": "array", "items": {"type": "string"}},
|
||||
"monitoring_framework": {"type": "object"}
|
||||
"monitoring_framework": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key_indicators": {"type": "array", "items": {"type": "string"}},
|
||||
"monitoring_frequency": {"type": "string"},
|
||||
"escalation_procedures": {"type": "array", "items": {"type": "string"}},
|
||||
"review_schedule": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -506,7 +506,7 @@ class EnhancedStrategyService:
|
||||
|
||||
def _merge_strategy_with_onboarding(self, strategy_data: Dict[str, Any], field_transformations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Merge strategy data with onboarding data."""
|
||||
merged_data = strategy_data.copy()
|
||||
merged_data = strategy_data.copy()
|
||||
|
||||
for field, transformation in field_transformations.items():
|
||||
if field not in merged_data or merged_data[field] is None:
|
||||
|
||||
343
docs/sse_migration_strategy.md
Normal file
343
docs/sse_migration_strategy.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# SSE Migration Strategy & Implementation Plan
|
||||
|
||||
## 🚨 **Current Implementation Problems**
|
||||
|
||||
### **Backend Issues**
|
||||
- **Complex SSE Manager**: The `SSEAIServiceManager` with lambda functions is overly complex
|
||||
- **Async Generator Problems**: The `sse_yield` function using `__anext__()` is fragile
|
||||
- **Message Format Inconsistency**: Backend sends different message formats that frontend struggles to parse
|
||||
- **Tight Coupling**: AI service manager is tightly coupled to SSE implementation
|
||||
- **Error Propagation**: Errors in one component cascade to others
|
||||
- **Debugging Difficulty**: Complex async flows make debugging hard
|
||||
|
||||
### **Frontend Issues**
|
||||
- **EventSource Limitations**: No built-in reconnection, poor error handling
|
||||
- **Message Parsing Complexity**: Too many message types to handle
|
||||
- **Timeout Handling**: Frontend timeouts don't align with backend processing
|
||||
- **Connection State Management**: Poor handling of connection states
|
||||
- **Progress Tracking**: Inconsistent progress calculation and display
|
||||
|
||||
### **Architecture Problems**
|
||||
- **Tight Coupling**: Frontend and backend are tightly coupled to specific message formats
|
||||
- **No Reusability**: SSE implementation is specific to strategy generation
|
||||
- **Error Handling**: Inconsistent error handling across components
|
||||
- **Testing Difficulty**: Complex async flows make testing challenging
|
||||
|
||||
## 🎯 **Proposed Solution: Clean SSE with sse-starlette**
|
||||
|
||||
### **Phase 1: MVP Polling Solution (1-2 hours)**
|
||||
**Goal**: Get strategy generation working immediately with simple polling
|
||||
|
||||
**Implementation**:
|
||||
- Replace complex SSE with simple polling mechanism
|
||||
- Poll strategy status every 10 seconds
|
||||
- Show progress modal with educational content
|
||||
- Handle timeouts gracefully
|
||||
- Remove all SSE-related complexity
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Immediate working solution
|
||||
- ✅ Simple to implement and debug
|
||||
- ✅ Reliable and predictable
|
||||
- ✅ Easy to test
|
||||
|
||||
### **Phase 2: Proper SSE Implementation (1-2 days)**
|
||||
**Goal**: Implement clean, reusable SSE infrastructure
|
||||
|
||||
**Implementation**:
|
||||
- Use `sse-starlette` for backend SSE
|
||||
- Create reusable SSE client for frontend
|
||||
- Standardize message format
|
||||
- Add proper error handling and reconnection
|
||||
- Make SSE infrastructure reusable for other features
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Real-time updates
|
||||
- ✅ Better user experience
|
||||
- ✅ Reusable infrastructure
|
||||
- ✅ Proper error handling
|
||||
|
||||
## 🏗️ **Technical Architecture**
|
||||
|
||||
### **Backend: sse-starlette Implementation**
|
||||
|
||||
#### **Core SSE Module** (`backend/services/sse/`)
|
||||
```
|
||||
backend/services/sse/
|
||||
├── __init__.py
|
||||
├── sse_manager.py # Core SSE management
|
||||
├── message_formatter.py # Standardized message formatting
|
||||
├── connection_manager.py # Connection lifecycle management
|
||||
├── error_handler.py # SSE error handling
|
||||
└── types.py # SSE message types and schemas
|
||||
```
|
||||
|
||||
#### **SSE Manager Features**
|
||||
- **Connection Management**: Handle multiple SSE connections
|
||||
- **Message Broadcasting**: Send messages to specific clients
|
||||
- **Error Handling**: Graceful error handling and recovery
|
||||
- **Message Formatting**: Consistent message format across all features
|
||||
- **Connection Monitoring**: Track connection health and status
|
||||
|
||||
#### **Message Format Standardization**
|
||||
```python
|
||||
# Standard SSE message format
|
||||
{
|
||||
"event": "progress|complete|error|educational",
|
||||
"data": {
|
||||
"step": 1,
|
||||
"progress": 10,
|
||||
"message": "Processing...",
|
||||
"educational_content": {...},
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Frontend: Reusable SSE Client**
|
||||
|
||||
#### **Core SSE Module** (`frontend/src/services/sse/`)
|
||||
```
|
||||
frontend/src/services/sse/
|
||||
├── index.ts
|
||||
├── SSEConnection.ts # Core SSE connection management
|
||||
├── SSEEventManager.ts # Event handling and message parsing
|
||||
├── SSEReconnection.ts # Automatic reconnection logic
|
||||
├── SSEMessageTypes.ts # TypeScript types for messages
|
||||
└── SSEUtils.ts # Utility functions
|
||||
```
|
||||
|
||||
#### **SSE Client Features**
|
||||
- **Automatic Reconnection**: Handle connection drops gracefully
|
||||
- **Message Parsing**: Parse standardized message format
|
||||
- **Event Handling**: Handle different event types
|
||||
- **Error Recovery**: Recover from errors automatically
|
||||
- **Connection Monitoring**: Monitor connection health
|
||||
|
||||
#### **React Hook** (`frontend/src/hooks/useSSE.ts`)
|
||||
```typescript
|
||||
const useSSE = (url: string, options?: SSEOptions) => {
|
||||
// Returns: { data, error, isConnected, reconnect }
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 **Implementation Phases**
|
||||
|
||||
### **Phase 1: MVP Polling (Immediate - 1-2 hours)**
|
||||
|
||||
#### **Backend Changes**
|
||||
1. **Remove SSE complexity** from `ai_generation_endpoints.py`
|
||||
2. **Simplify AI generation** to return immediately after starting
|
||||
3. **Add status endpoint** to check generation progress
|
||||
4. **Remove SSEAIServiceManager** and related complexity
|
||||
|
||||
#### **Frontend Changes**
|
||||
1. **Replace SSE with polling** in `ContentStrategyBuilder.tsx`
|
||||
2. **Implement simple progress modal** with educational content
|
||||
3. **Add polling mechanism** (every 10 seconds)
|
||||
4. **Handle timeouts gracefully** (5-minute timeout)
|
||||
5. **Remove all SSE-related code**
|
||||
|
||||
#### **Files to Modify**
|
||||
- `backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py`
|
||||
- `frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx`
|
||||
- `frontend/src/services/contentPlanningApi.ts`
|
||||
|
||||
### **Phase 2: Clean SSE Infrastructure (1-2 days)**
|
||||
|
||||
#### **Backend Implementation**
|
||||
1. **Create SSE infrastructure** (`backend/services/sse/`)
|
||||
2. **Implement sse-starlette endpoints** for strategy generation
|
||||
3. **Standardize message format** across all SSE endpoints
|
||||
4. **Add connection management** and error handling
|
||||
5. **Create reusable SSE utilities**
|
||||
|
||||
#### **Frontend Implementation**
|
||||
1. **Create SSE client infrastructure** (`frontend/src/services/sse/`)
|
||||
2. **Implement React hook** for SSE connections
|
||||
3. **Add automatic reconnection** logic
|
||||
4. **Standardize message parsing** and event handling
|
||||
5. **Create reusable SSE components**
|
||||
|
||||
#### **New Files to Create**
|
||||
```
|
||||
Backend:
|
||||
- backend/services/sse/__init__.py
|
||||
- backend/services/sse/sse_manager.py
|
||||
- backend/services/sse/message_formatter.py
|
||||
- backend/services/sse/connection_manager.py
|
||||
- backend/services/sse/error_handler.py
|
||||
- backend/services/sse/types.py
|
||||
|
||||
Frontend:
|
||||
- frontend/src/services/sse/index.ts
|
||||
- frontend/src/services/sse/SSEConnection.ts
|
||||
- frontend/src/services/sse/SSEEventManager.ts
|
||||
- frontend/src/services/sse/SSEReconnection.ts
|
||||
- frontend/src/services/sse/SSEMessageTypes.ts
|
||||
- frontend/src/services/sse/SSEUtils.ts
|
||||
- frontend/src/hooks/useSSE.ts
|
||||
```
|
||||
|
||||
### **Phase 3: Migration & Testing (1 day)**
|
||||
|
||||
#### **Migration Steps**
|
||||
1. **Migrate strategy generation** to new SSE infrastructure
|
||||
2. **Test end-to-end functionality** with new SSE
|
||||
3. **Add comprehensive error handling** and recovery
|
||||
4. **Implement educational content** streaming
|
||||
5. **Add monitoring and logging** for SSE connections
|
||||
|
||||
#### **Testing Strategy**
|
||||
1. **Unit tests** for SSE infrastructure
|
||||
2. **Integration tests** for SSE endpoints
|
||||
3. **End-to-end tests** for strategy generation
|
||||
4. **Error scenario testing** (network drops, timeouts)
|
||||
5. **Performance testing** (multiple concurrent connections)
|
||||
|
||||
## 🔧 **Technical Specifications**
|
||||
|
||||
### **Backend SSE Manager Interface**
|
||||
```python
|
||||
class SSEManager:
|
||||
async def create_connection(self, client_id: str) -> SSEConnection
|
||||
async def send_message(self, client_id: str, message: SSEMessage)
|
||||
async def broadcast_message(self, message: SSEMessage, filter_func=None)
|
||||
async def close_connection(self, client_id: str)
|
||||
async def get_connection_status(self, client_id: str) -> ConnectionStatus
|
||||
```
|
||||
|
||||
### **Frontend SSE Client Interface**
|
||||
```typescript
|
||||
interface SSEConnection {
|
||||
connect(): Promise<void>
|
||||
disconnect(): void
|
||||
send(message: SSEMessage): void
|
||||
on(event: string, handler: EventHandler): void
|
||||
off(event: string, handler: EventHandler): void
|
||||
isConnected(): boolean
|
||||
reconnect(): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
### **Message Format Specification**
|
||||
```typescript
|
||||
interface SSEMessage {
|
||||
event: 'progress' | 'complete' | 'error' | 'educational' | 'status'
|
||||
data: {
|
||||
step?: number
|
||||
progress?: number
|
||||
message?: string
|
||||
educational_content?: EducationalContent
|
||||
error?: string
|
||||
timestamp: string
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 **Success Criteria**
|
||||
|
||||
### **Phase 1 Success Criteria**
|
||||
- ✅ Strategy generation works reliably
|
||||
- ✅ No more "Request timed out" errors
|
||||
- ✅ Users can see progress and educational content
|
||||
- ✅ Simple, debuggable implementation
|
||||
- ✅ Strategy creation completes successfully
|
||||
|
||||
### **Phase 2 Success Criteria**
|
||||
- ✅ Real-time progress updates via SSE
|
||||
- ✅ Automatic reconnection on network issues
|
||||
- ✅ Standardized message format across features
|
||||
- ✅ Reusable SSE infrastructure
|
||||
- ✅ Proper error handling and recovery
|
||||
- ✅ Educational content streaming
|
||||
|
||||
### **Phase 3 Success Criteria**
|
||||
- ✅ All features migrated to new SSE infrastructure
|
||||
- ✅ Comprehensive testing coverage
|
||||
- ✅ Performance meets requirements
|
||||
- ✅ Error scenarios handled gracefully
|
||||
- ✅ Monitoring and logging in place
|
||||
|
||||
## 🚀 **Migration Benefits**
|
||||
|
||||
### **Immediate Benefits (Phase 1)**
|
||||
- **Reliability**: No more timeout errors
|
||||
- **Simplicity**: Easy to debug and maintain
|
||||
- **User Experience**: Clear progress feedback
|
||||
- **Stability**: Predictable behavior
|
||||
|
||||
### **Long-term Benefits (Phase 2+)**
|
||||
- **Reusability**: SSE infrastructure for other features
|
||||
- **Real-time Updates**: Better user experience
|
||||
- **Scalability**: Handle multiple concurrent connections
|
||||
- **Maintainability**: Clean, modular architecture
|
||||
- **Extensibility**: Easy to add new SSE features
|
||||
|
||||
## 📝 **Implementation Notes**
|
||||
|
||||
### **Dependencies**
|
||||
- **Backend**: `sse-starlette` package
|
||||
- **Frontend**: No additional dependencies (uses native EventSource)
|
||||
|
||||
### **Configuration**
|
||||
- **SSE Timeout**: 5 minutes for long-running operations
|
||||
- **Reconnection**: Exponential backoff (1s, 2s, 4s, 8s, max 30s)
|
||||
- **Message Format**: JSON with standardized structure
|
||||
- **Error Handling**: Graceful degradation with fallback options
|
||||
|
||||
### **Monitoring & Logging**
|
||||
- **Connection Status**: Track active connections
|
||||
- **Message Flow**: Log message types and frequencies
|
||||
- **Error Tracking**: Monitor and alert on SSE errors
|
||||
- **Performance Metrics**: Track response times and throughput
|
||||
|
||||
### **Security Considerations**
|
||||
- **Authentication**: Validate client connections
|
||||
- **Rate Limiting**: Prevent abuse of SSE endpoints
|
||||
- **Message Validation**: Validate all incoming messages
|
||||
- **Connection Limits**: Limit concurrent connections per user
|
||||
|
||||
## 🔄 **Rollback Plan**
|
||||
|
||||
### **If Phase 1 Fails**
|
||||
- Revert to current SSE implementation
|
||||
- Keep polling as fallback option
|
||||
- Document issues for future reference
|
||||
|
||||
### **If Phase 2 Fails**
|
||||
- Keep Phase 1 polling implementation
|
||||
- Identify specific issues with sse-starlette
|
||||
- Consider alternative SSE libraries or WebSocket implementation
|
||||
|
||||
### **If Phase 3 Fails**
|
||||
- Rollback to Phase 2 implementation
|
||||
- Fix specific issues identified during testing
|
||||
- Re-run migration with fixes
|
||||
|
||||
## 📚 **References & Resources**
|
||||
|
||||
### **Documentation**
|
||||
- [sse-starlette Documentation](https://github.com/sysid/sse-starlette)
|
||||
- [Server-Sent Events MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
||||
- [EventSource API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
|
||||
|
||||
### **Best Practices**
|
||||
- [SSE Best Practices](https://html.spec.whatwg.org/multipage/server-sent-events.html)
|
||||
- [Real-time Web Applications](https://web.dev/real-time-web-applications/)
|
||||
- [Error Handling in SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Error_handling)
|
||||
|
||||
### **Examples & Templates**
|
||||
- [sse-starlette Examples](https://github.com/sysid/sse-starlette/tree/main/examples)
|
||||
- [React SSE Hook Examples](https://github.com/facebook/react/tree/main/packages/react-dom/src/events)
|
||||
- [FastAPI SSE Examples](https://fastapi.tiangolo.com/advanced/websockets/)
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**:
|
||||
1. Commit current code
|
||||
2. Refresh session
|
||||
3. Start Phase 1 implementation (MVP polling)
|
||||
4. Test strategy generation works
|
||||
5. Proceed to Phase 2 (clean SSE infrastructure)
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.c9966057.css",
|
||||
"main.js": "/static/js/main.2ee5cd94.js",
|
||||
"main.js": "/static/js/main.2819e23e.js",
|
||||
"index.html": "/index.html",
|
||||
"main.c9966057.css.map": "/static/css/main.c9966057.css.map",
|
||||
"main.2ee5cd94.js.map": "/static/js/main.2ee5cd94.js.map"
|
||||
"main.2819e23e.js.map": "/static/js/main.2819e23e.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.c9966057.css",
|
||||
"static/js/main.2ee5cd94.js"
|
||||
"static/js/main.2819e23e.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.2ee5cd94.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.2819e23e.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
@@ -50,7 +50,8 @@ import {
|
||||
Lightbulb as LightbulbIcon,
|
||||
Psychology as PsychologyIcon,
|
||||
Timeline as TimelineIcon,
|
||||
FiberManualRecord as FiberManualRecordIcon
|
||||
FiberManualRecord as FiberManualRecordIcon,
|
||||
Schedule as ScheduleIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useEnhancedStrategyStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/enhancedStrategyStore';
|
||||
@@ -63,6 +64,7 @@ import DataSourceTransparency from './DataSourceTransparency';
|
||||
import { useCategoryReview } from './ContentStrategyBuilder/hooks/useCategoryReview';
|
||||
import { useProgressTracking } from './ContentStrategyBuilder/hooks/useProgressTracking';
|
||||
import { useAutoPopulation } from './ContentStrategyBuilder/hooks/useAutoPopulation';
|
||||
import { useActionButtonsBusinessLogic } from './ContentStrategyBuilder/components/ActionButtons';
|
||||
|
||||
// Import extracted utilities
|
||||
import { getCategoryIcon, getCategoryColor, getCategoryName, getCategoryStatus } from './ContentStrategyBuilder/utils/categoryHelpers';
|
||||
@@ -72,7 +74,12 @@ import { getEducationalContent } from './ContentStrategyBuilder/utils/educationa
|
||||
import CategoryList from './ContentStrategyBuilder/components/CategoryList';
|
||||
import ProgressTracker from './ContentStrategyBuilder/components/ProgressTracker';
|
||||
import HeaderSection from './ContentStrategyBuilder/components/HeaderSection';
|
||||
import EducationalModal from './ContentStrategyBuilder/components/EducationalModal';
|
||||
import ActionButtons from './ContentStrategyBuilder/components/ActionButtons';
|
||||
import StrategyDisplay from './ContentStrategyBuilder/components/StrategyDisplay';
|
||||
import ErrorAlert from './ContentStrategyBuilder/components/ErrorAlert';
|
||||
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
||||
import CategoryDetailView from './ContentStrategyBuilder/components/CategoryDetailView';
|
||||
|
||||
const ContentStrategyBuilder: React.FC = () => {
|
||||
const {
|
||||
@@ -155,6 +162,25 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
completionStats
|
||||
});
|
||||
|
||||
// Use ActionButtons business logic hook
|
||||
const { handleCreateStrategy, handleSaveStrategy } = useActionButtonsBusinessLogic({
|
||||
formData,
|
||||
error,
|
||||
currentStrategy,
|
||||
setAIGenerating,
|
||||
setError,
|
||||
setCurrentStrategy,
|
||||
setSaving,
|
||||
setGenerationProgress,
|
||||
setEducationalContent,
|
||||
setShowEducationalModal,
|
||||
validateAllFields,
|
||||
getCompletionStats,
|
||||
generateAIRecommendations,
|
||||
createEnhancedStrategy,
|
||||
contentPlanningApi
|
||||
});
|
||||
|
||||
// Auto-populate from onboarding on first load
|
||||
useEffect(() => {
|
||||
if (!autoPopulateAttempted) {
|
||||
@@ -211,294 +237,6 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleCreateStrategy = async () => {
|
||||
try {
|
||||
setAIGenerating(true);
|
||||
setError(null);
|
||||
|
||||
console.log('Starting strategy creation...');
|
||||
console.log('Current formData:', formData);
|
||||
console.log('FormData ID:', formData.id);
|
||||
|
||||
// If we have a saved strategy, use its ID
|
||||
if (formData.id) {
|
||||
console.log('Using existing strategy ID:', formData.id);
|
||||
await generateAIRecommendations(formData.id);
|
||||
} else {
|
||||
console.log('No strategy ID found, creating new strategy...');
|
||||
// If no strategy is saved yet, save it first, then generate AI insights
|
||||
const isValid = validateAllFields();
|
||||
console.log('Form validation result:', isValid);
|
||||
|
||||
if (isValid) {
|
||||
const completionStats = getCompletionStats();
|
||||
const strategyData = {
|
||||
...formData,
|
||||
completion_percentage: completionStats.completion_percentage,
|
||||
user_id: 1, // This would come from auth context
|
||||
name: formData.name || 'Enhanced Content Strategy',
|
||||
industry: formData.industry || 'General'
|
||||
};
|
||||
|
||||
console.log('Attempting to create strategy with data:', strategyData);
|
||||
|
||||
// Use SSE streaming endpoint for strategy generation with educational content
|
||||
await generateStrategyWithSSE(strategyData);
|
||||
} else {
|
||||
setError('Please fill in all required fields before generating AI insights.');
|
||||
console.error('Form validation failed. Cannot generate AI insights.');
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(`Error generating AI recommendations: ${err.message || 'Unknown error'}`);
|
||||
console.error('Error in handleCreateStrategy:', err);
|
||||
} finally {
|
||||
setAIGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const generateStrategyWithSSE = async (strategyData: any) => {
|
||||
try {
|
||||
console.log('🚀 Starting SSE strategy generation...');
|
||||
|
||||
// Initialize progress and educational content
|
||||
setGenerationProgress(0);
|
||||
setEducationalContent({
|
||||
title: '🤖 AI-Powered Strategy Generation',
|
||||
description: 'Initializing AI analysis and preparing educational content...',
|
||||
details: [
|
||||
'🔧 Setting up AI services',
|
||||
'📊 Loading user context',
|
||||
'🎯 Preparing strategy framework',
|
||||
'📚 Generating educational content'
|
||||
],
|
||||
insight: 'We\'re getting everything ready for your personalized AI strategy generation.',
|
||||
estimated_time: '2-3 minutes total'
|
||||
});
|
||||
|
||||
// Show educational modal
|
||||
setShowEducationalModal(true);
|
||||
|
||||
// Create basic strategy first
|
||||
const newStrategy = await createEnhancedStrategy(strategyData);
|
||||
console.log('Basic strategy created:', newStrategy);
|
||||
|
||||
if (newStrategy && newStrategy.id) {
|
||||
console.log('Starting AI generation for strategy ID:', newStrategy.id);
|
||||
|
||||
// Set a timeout for the entire process (5 minutes)
|
||||
const processTimeout = setTimeout(async () => {
|
||||
console.error('⏰ Strategy generation timeout after 5 minutes');
|
||||
|
||||
// Try to check if the strategy was actually created
|
||||
try {
|
||||
const existingStrategy = await contentPlanningApi.getEnhancedStrategy(newStrategy.id.toString());
|
||||
if (existingStrategy) {
|
||||
console.log('✅ Strategy was created successfully despite SSE timeout');
|
||||
setCurrentStrategy(existingStrategy);
|
||||
setError('Strategy created successfully! The AI generation may still be running in the background. Check the Strategic Intelligence tab for detailed insights.');
|
||||
} else {
|
||||
setError('Strategy generation is taking longer than expected. The process may still be running in the background. Please check the Strategic Intelligence tab for results.');
|
||||
}
|
||||
} catch (checkError) {
|
||||
console.error('Error checking strategy status:', checkError);
|
||||
setError('Strategy generation is taking longer than expected. The process may still be running in the background. Please check the Strategic Intelligence tab for results.');
|
||||
}
|
||||
|
||||
setShowEducationalModal(false);
|
||||
}, 5 * 60 * 1000); // 5 minutes
|
||||
|
||||
// Add heartbeat monitoring
|
||||
let lastMessageTime = Date.now();
|
||||
const heartbeatInterval = setInterval(() => {
|
||||
const timeSinceLastMessage = Date.now() - lastMessageTime;
|
||||
if (timeSinceLastMessage > 30000) { // 30 seconds without message
|
||||
console.warn('⚠️ No SSE messages received for 30 seconds');
|
||||
setEducationalContent({
|
||||
title: '🤖 AI-Powered Strategy Generation',
|
||||
description: 'AI analysis is still running in the background. This may take a few more minutes.',
|
||||
details: [
|
||||
'⏳ Processing complex AI analysis',
|
||||
'📊 Analyzing market data',
|
||||
'🎯 Generating strategic insights',
|
||||
'📈 Calculating performance predictions'
|
||||
],
|
||||
insight: 'The AI is working on comprehensive analysis. This is normal for complex strategies.',
|
||||
estimated_time: 'Additional 1-2 minutes'
|
||||
});
|
||||
}
|
||||
}, 10000); // Check every 10 seconds
|
||||
|
||||
// Use SSE endpoint for AI generation with educational content
|
||||
const eventSource = await contentPlanningApi.streamStrategyGeneration(Number(newStrategy.id));
|
||||
|
||||
console.log('🔌 SSE EventSource created:', eventSource);
|
||||
console.log('🔌 SSE readyState:', eventSource.readyState);
|
||||
|
||||
// Handle SSE data with proper parsing
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
console.log('📨 Raw SSE message:', event.data);
|
||||
|
||||
// Update last message time for heartbeat
|
||||
lastMessageTime = Date.now();
|
||||
|
||||
// Parse the SSE data
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('📨 Parsed SSE data:', data);
|
||||
console.log('📨 Message type analysis:', {
|
||||
hasStep: data.step !== undefined,
|
||||
hasProgress: data.progress !== undefined,
|
||||
hasEducationalContent: !!data.educational_content,
|
||||
hasError: !!data.error,
|
||||
hasSuccess: !!data.success,
|
||||
hasType: !!data.type,
|
||||
step: data.step,
|
||||
progress: data.progress,
|
||||
message: data.message
|
||||
});
|
||||
|
||||
// Handle different types of messages
|
||||
if (data.error) {
|
||||
console.error('❌ SSE Error:', data.error);
|
||||
clearTimeout(processTimeout);
|
||||
clearInterval(heartbeatInterval);
|
||||
setError(`AI generation failed: ${data.error}`);
|
||||
setShowEducationalModal(false);
|
||||
eventSource.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle step and progress updates (backend sends these)
|
||||
if (data.step !== undefined) {
|
||||
console.log('🔢 Updating step:', data.step);
|
||||
// Calculate progress from step (each step is 10%)
|
||||
const stepProgress = Math.min(data.step * 10, 100);
|
||||
console.log('📊 Calculated progress from step:', stepProgress);
|
||||
setGenerationProgress(stepProgress);
|
||||
}
|
||||
|
||||
// Handle explicit progress updates
|
||||
if (data.progress !== undefined) {
|
||||
console.log('📊 Updating progress:', data.progress);
|
||||
setGenerationProgress(data.progress);
|
||||
}
|
||||
|
||||
// Handle educational content updates
|
||||
if (data.educational_content) {
|
||||
console.log('📚 Updating educational content:', data.educational_content);
|
||||
setEducationalContent(data.educational_content);
|
||||
}
|
||||
|
||||
// Handle completion
|
||||
if (data.step === 10 && data.success) {
|
||||
console.log('✅ Strategy generation completed successfully!');
|
||||
clearTimeout(processTimeout);
|
||||
clearInterval(heartbeatInterval);
|
||||
setCurrentStrategy(data.strategy);
|
||||
setShowEducationalModal(false);
|
||||
setError('Strategy created successfully! Check the Strategic Intelligence tab for detailed insights.');
|
||||
eventSource.close();
|
||||
}
|
||||
|
||||
// Handle educational content from AI service manager
|
||||
if (data.type === 'educational_content' && data.educational_content) {
|
||||
console.log('📚 AI Service educational content:', data.educational_content);
|
||||
setEducationalContent(data.educational_content);
|
||||
}
|
||||
|
||||
// Handle success messages for individual steps
|
||||
if (data.success && data.message) {
|
||||
console.log('✅ Step completed:', data.message);
|
||||
// Progress is already updated above, just log the success
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
console.error('❌ Error parsing SSE message:', parseError);
|
||||
console.error('Raw message:', event.data);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle SSE errors
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('❌ SSE connection error:', error);
|
||||
console.error(' ReadyState:', eventSource.readyState);
|
||||
|
||||
// Check connection state
|
||||
switch (eventSource.readyState) {
|
||||
case EventSource.CONNECTING:
|
||||
console.log('🔄 SSE connection is connecting...');
|
||||
break;
|
||||
case EventSource.OPEN:
|
||||
console.log('✅ SSE connection is open');
|
||||
break;
|
||||
case EventSource.CLOSED:
|
||||
console.log('🔌 SSE connection is closed');
|
||||
clearTimeout(processTimeout);
|
||||
clearInterval(heartbeatInterval);
|
||||
setError('Connection lost during AI generation. The process may still be running in the background. Please check the Strategic Intelligence tab for results.');
|
||||
setShowEducationalModal(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle SSE connection open
|
||||
eventSource.onopen = () => {
|
||||
console.log('✅ SSE connection opened successfully');
|
||||
console.log(' ReadyState:', eventSource.readyState);
|
||||
console.log(' URL:', eventSource.url);
|
||||
|
||||
// Update educational content to show connection is established
|
||||
setEducationalContent({
|
||||
title: '🔌 Connection Established',
|
||||
description: 'Successfully connected to AI generation service. Starting analysis...',
|
||||
details: [
|
||||
'✅ SSE connection active',
|
||||
'🤖 AI service ready',
|
||||
'📊 Data processing initialized',
|
||||
'🎯 Strategy generation starting'
|
||||
],
|
||||
insight: 'The connection is now established and AI analysis is beginning.',
|
||||
estimated_time: '2-3 minutes total'
|
||||
});
|
||||
};
|
||||
|
||||
} else {
|
||||
setError('Failed to create strategy or get strategy ID for AI generation.');
|
||||
console.error('Failed to create strategy or get strategy ID for AI generation.');
|
||||
setShowEducationalModal(false);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error in SSE strategy generation:', error);
|
||||
setError(`Error in strategy generation: ${error.message || 'Unknown error'}`);
|
||||
setShowEducationalModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveStrategy = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
|
||||
const completionStats = getCompletionStats();
|
||||
const strategyData = {
|
||||
...formData,
|
||||
completion_percentage: completionStats.completion_percentage,
|
||||
user_id: 1,
|
||||
name: formData.name || 'Enhanced Content Strategy',
|
||||
industry: formData.industry || 'General'
|
||||
};
|
||||
|
||||
const newStrategy = await createEnhancedStrategy(strategyData);
|
||||
setCurrentStrategy(newStrategy);
|
||||
setError('Strategy saved successfully!');
|
||||
} catch (err: any) {
|
||||
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReviewCategory = (categoryId: string) => {
|
||||
setActiveCategory(activeCategory === categoryId ? null : categoryId);
|
||||
};
|
||||
@@ -519,75 +257,19 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
<HeaderSection autoPopulatedFields={autoPopulatedFields} />
|
||||
|
||||
{/* Error Alert */}
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{ mb: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
|
||||
action={
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button size="small" variant="outlined" onClick={() => autoPopulateFromOnboarding(true)} startIcon={<RefreshIcon />}>Retry</Button>
|
||||
<Button size="small" variant="contained" color="primary" onClick={() => setShowDataSourceTransparency(true)} startIcon={<InfoIcon />}>Why?</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="subtitle2">Real data required</Typography>
|
||||
<Typography variant="body2">{error || 'We could not auto-populate because required onboarding/analysis data is missing. Connect sources or complete onboarding, then retry.'}</Typography>
|
||||
</Box>
|
||||
</Alert>
|
||||
)}
|
||||
<ErrorAlert
|
||||
error={error}
|
||||
onRetry={() => autoPopulateFromOnboarding(true)}
|
||||
onShowDataSourceTransparency={() => setShowDataSourceTransparency(true)}
|
||||
/>
|
||||
|
||||
{/* Success Alert */}
|
||||
{!error && currentStrategy && (
|
||||
<Alert severity="success" sx={{ mb: 3 }}>
|
||||
Strategy "{currentStrategy.name}" created successfully! Check the Strategic Intelligence tab for detailed insights.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Strategy Display */}
|
||||
{currentStrategy && (
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Created Strategy: {currentStrategy.name}
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
Industry: {currentStrategy.industry}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
Completion: {currentStrategy.completion_percentage}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
Created: {new Date(currentStrategy.created_at).toLocaleDateString()}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
ID: {currentStrategy.id}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => window.location.href = '/content-planning?tab=strategic-intelligence'}
|
||||
startIcon={<AssessmentIcon />}
|
||||
>
|
||||
View Strategic Intelligence
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{categoryCompletionMessage && (
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{ mb: 3, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
{categoryCompletionMessage}
|
||||
</Alert>
|
||||
)}
|
||||
{/* Strategy Display and Success Alerts */}
|
||||
<StrategyDisplay
|
||||
currentStrategy={currentStrategy}
|
||||
error={error}
|
||||
categoryCompletionMessage={categoryCompletionMessage}
|
||||
onViewStrategicIntelligence={() => window.location.href = '/content-planning?tab=strategic-intelligence'}
|
||||
/>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{/* Category Overview Panel */}
|
||||
@@ -781,212 +463,41 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
{/* Main Content Area */}
|
||||
<Grid item xs={12} md={8}>
|
||||
<Paper sx={{ p: 3, minHeight: '600px', background: 'linear-gradient(180deg, #faf7ff, #f1f0ff)' }}>
|
||||
{activeCategory ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{/* Category Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||
{getCategoryIcon(activeCategory)}
|
||||
<Typography variant="h5" sx={{ ml: 1 }}>
|
||||
{activeCategory.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={`${Math.round(completionStats.category_completion[activeCategory])}% Complete`}
|
||||
color={getCategoryColor(activeCategory) as any}
|
||||
sx={{ ml: 'auto' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Educational Info Dialog */}
|
||||
<Dialog
|
||||
open={!!showEducationalInfo}
|
||||
onClose={() => setShowEducationalInfo(null)}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<SchoolIcon />
|
||||
{showEducationalInfo && getEducationalContent(showEducationalInfo).title}
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body1" paragraph>
|
||||
{showEducationalInfo && getEducationalContent(showEducationalInfo).description}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Key Points:
|
||||
</Typography>
|
||||
<List>
|
||||
{showEducationalInfo && getEducationalContent(showEducationalInfo).points.map((point, index) => (
|
||||
<ListItem key={index} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon>
|
||||
<LightbulbIcon color="primary" fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={point} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Pro Tips:
|
||||
</Typography>
|
||||
<List>
|
||||
{showEducationalInfo && getEducationalContent(showEducationalInfo).tips.map((tip, index) => (
|
||||
<ListItem key={index} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon>
|
||||
<PsychologyIcon color="secondary" fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={tip} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setShowEducationalInfo(null)}>
|
||||
Got it!
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Category Fields */}
|
||||
<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';
|
||||
|
||||
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={autoPopulatedFields[field.id] ? 0.8 : undefined}
|
||||
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
|
||||
personalizationData={personalizationData[field.id]}
|
||||
onChange={(value: any) => updateFormField(field.id, value)}
|
||||
onValidate={() => validateFormField(field.id)}
|
||||
onShowTooltip={() => setShowTooltip(field.id)}
|
||||
onViewDataSource={() => setShowDataSourceTransparency(true)}
|
||||
accentColorKey={getCategoryColor(activeCategory) as any}
|
||||
isCompact={isCompactField}
|
||||
/>
|
||||
</motion.div>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* Category Actions */}
|
||||
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
|
||||
{(() => {
|
||||
const isReviewed = reviewedCategories.has(activeCategory);
|
||||
console.log('🔍 Category review status:', {
|
||||
activeCategory,
|
||||
isReviewed,
|
||||
reviewedCategories: Array.from(reviewedCategories)
|
||||
});
|
||||
return !isReviewed ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
console.log('🔘 Button clicked! activeCategory:', activeCategory);
|
||||
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
|
||||
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
|
||||
handleConfirmCategoryReviewWrapper();
|
||||
}}
|
||||
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
|
||||
disabled={isMarkingReviewed}
|
||||
>
|
||||
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
|
||||
</Button>
|
||||
) : (
|
||||
<Chip
|
||||
label="Category Reviewed"
|
||||
color="success"
|
||||
icon={<CheckCircleIcon />}
|
||||
sx={{ px: 2, py: 1 }}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setActiveCategory(null)}
|
||||
>
|
||||
Back to Overview
|
||||
</Button>
|
||||
</Box>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Box sx={{ textAlign: 'center', py: 8 }}>
|
||||
<TimelineIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Select a Category to Review
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Click on any category from the left panel to review and complete the fields.
|
||||
</Typography>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
<CategoryDetailView
|
||||
activeCategory={activeCategory}
|
||||
formData={formData}
|
||||
formErrors={formErrors}
|
||||
autoPopulatedFields={autoPopulatedFields}
|
||||
dataSources={dataSources}
|
||||
personalizationData={personalizationData}
|
||||
completionStats={completionStats}
|
||||
reviewedCategories={reviewedCategories}
|
||||
isMarkingReviewed={isMarkingReviewed}
|
||||
showEducationalInfo={showEducationalInfo}
|
||||
STRATEGIC_INPUT_FIELDS={STRATEGIC_INPUT_FIELDS}
|
||||
onUpdateFormField={updateFormField}
|
||||
onValidateFormField={validateFormField}
|
||||
onShowTooltip={setShowTooltip}
|
||||
onViewDataSource={() => setShowDataSourceTransparency(true)}
|
||||
onConfirmCategoryReview={handleConfirmCategoryReviewWrapper}
|
||||
onSetActiveCategory={setActiveCategory}
|
||||
onSetShowEducationalInfo={setShowEducationalInfo}
|
||||
getCategoryIcon={getCategoryIcon}
|
||||
getCategoryColor={getCategoryColor}
|
||||
getEducationalContent={getEducationalContent}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Box sx={{ mt: 3, display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
|
||||
<MuiTooltip
|
||||
title={reviewProgressPercentage < 20 ? `Complete at least 20% of the form (currently ${Math.round(reviewProgressPercentage)}%)` : 'Create a comprehensive content strategy with AI insights'}
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AutoAwesomeIcon />}
|
||||
onClick={handleCreateStrategy}
|
||||
disabled={aiGenerating || reviewProgressPercentage < 20}
|
||||
>
|
||||
{aiGenerating ? 'Creating...' : 'Create Strategy'}
|
||||
</Button>
|
||||
</span>
|
||||
</MuiTooltip>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<SaveIcon />}
|
||||
onClick={handleSaveStrategy}
|
||||
disabled={saving || reviewProgressPercentage < 30}
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save Strategy'}
|
||||
</Button>
|
||||
</Box>
|
||||
<ActionButtons
|
||||
aiGenerating={aiGenerating}
|
||||
saving={saving}
|
||||
reviewProgressPercentage={reviewProgressPercentage}
|
||||
onCreateStrategy={handleCreateStrategy}
|
||||
onSaveStrategy={handleSaveStrategy}
|
||||
/>
|
||||
|
||||
{/* AI Recommendations Modal */}
|
||||
<Dialog
|
||||
@@ -1009,179 +520,13 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Educational Modal for Strategy Generation */}
|
||||
<Dialog
|
||||
{/* Enhanced Educational Modal for Strategy Generation */}
|
||||
<EducationalModal
|
||||
open={showEducationalModal}
|
||||
onClose={() => setShowEducationalModal(false)}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<SchoolIcon color="primary" />
|
||||
{educationalContent?.title || 'AI Strategy Generation'}
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{educationalContent ? (
|
||||
<Box>
|
||||
{/* Progress Bar */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={1}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Progress: {generationProgress}%
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Step {Math.ceil(generationProgress / 10)} of 10
|
||||
</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={generationProgress}
|
||||
sx={{ height: 8, borderRadius: 4 }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Educational Content */}
|
||||
<Typography variant="h6" gutterBottom color="primary">
|
||||
{educationalContent.title || 'AI Strategy Generation'}
|
||||
</Typography>
|
||||
|
||||
{educationalContent.description && (
|
||||
<Typography variant="body1" paragraph>
|
||||
{educationalContent.description}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{educationalContent.details && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
What's happening:
|
||||
</Typography>
|
||||
<List dense>
|
||||
{educationalContent.details.map((detail: string, index: number) => (
|
||||
<ListItem key={index} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 32 }}>
|
||||
<FiberManualRecordIcon sx={{ fontSize: 8 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={detail} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.insight && (
|
||||
<Box sx={{ mb: 2, p: 2, bgcolor: 'grey.50', borderRadius: 1 }}>
|
||||
<Typography variant="subtitle2" color="primary" gutterBottom>
|
||||
💡 Insight:
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{educationalContent.insight}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.ai_prompt_preview && (
|
||||
<Box sx={{ mb: 2, p: 2, bgcolor: 'blue.50', borderRadius: 1 }}>
|
||||
<Typography variant="subtitle2" color="primary" gutterBottom>
|
||||
🤖 AI Prompt Preview:
|
||||
</Typography>
|
||||
<Typography variant="body2" fontFamily="monospace" fontSize="0.875rem">
|
||||
{educationalContent.ai_prompt_preview}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.estimated_time && (
|
||||
<Box sx={{ mb: 2, p: 2, bgcolor: 'orange.50', borderRadius: 1 }}>
|
||||
<Typography variant="subtitle2" color="warning.main" gutterBottom>
|
||||
⏱️ Estimated Time:
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{educationalContent.estimated_time}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.achievement && (
|
||||
<Box sx={{ mb: 2, p: 2, bgcolor: 'green.50', borderRadius: 1 }}>
|
||||
<Typography variant="subtitle2" color="success.main" gutterBottom>
|
||||
✅ Achievement:
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{educationalContent.achievement}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.next_step && (
|
||||
<Box sx={{ mb: 2, p: 2, bgcolor: 'purple.50', borderRadius: 1 }}>
|
||||
<Typography variant="subtitle2" color="secondary.main" gutterBottom>
|
||||
🔄 Next Step:
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{educationalContent.next_step}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Summary for completion */}
|
||||
{educationalContent.summary && (
|
||||
<Box sx={{ mt: 3, p: 2, bgcolor: 'primary.50', borderRadius: 1 }}>
|
||||
<Typography variant="h6" gutterBottom color="primary">
|
||||
🎉 Strategy Generation Summary
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2">
|
||||
<strong>Components:</strong> {educationalContent.summary.successful_components}/{educationalContent.summary.total_components}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2">
|
||||
<strong>Content Pieces:</strong> {educationalContent.summary.total_content_pieces}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2">
|
||||
<strong>Estimated ROI:</strong> {educationalContent.summary.estimated_roi}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2">
|
||||
<strong>Timeline:</strong> {educationalContent.summary.implementation_timeline}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
/* Loading state when educational content is not yet available */
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<CircularProgress sx={{ mb: 2 }} />
|
||||
<Typography variant="h6" gutterBottom>
|
||||
🤖 AI-Powered Strategy Generation
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" paragraph>
|
||||
Initializing AI analysis and preparing educational content...
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
This may take a few moments as we set up the AI services.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setShowEducationalModal(false)}
|
||||
disabled={generationProgress < 100}
|
||||
>
|
||||
{generationProgress < 100 ? 'Please wait...' : 'Close'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
educationalContent={educationalContent}
|
||||
generationProgress={generationProgress}
|
||||
/>
|
||||
|
||||
{/* Data Source Transparency Modal */}
|
||||
<Dialog
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Tooltip as MuiTooltip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
Save as SaveIcon
|
||||
} from '@mui/icons-material';
|
||||
import { ActionButtonsProps, ActionButtonsBusinessLogicProps } from '../types/contentStrategy.types';
|
||||
|
||||
// Business Logic Hook
|
||||
export const useActionButtonsBusinessLogic = ({
|
||||
formData,
|
||||
error,
|
||||
currentStrategy,
|
||||
setAIGenerating,
|
||||
setError,
|
||||
setCurrentStrategy,
|
||||
setSaving,
|
||||
setGenerationProgress,
|
||||
setEducationalContent,
|
||||
setShowEducationalModal,
|
||||
validateAllFields,
|
||||
getCompletionStats,
|
||||
generateAIRecommendations,
|
||||
createEnhancedStrategy,
|
||||
contentPlanningApi
|
||||
}: ActionButtonsBusinessLogicProps) => {
|
||||
|
||||
const handleCreateStrategy = async () => {
|
||||
try {
|
||||
setAIGenerating(true);
|
||||
setError(null);
|
||||
|
||||
console.log('Starting strategy creation...');
|
||||
console.log('Current formData:', formData);
|
||||
console.log('FormData ID:', formData.id);
|
||||
|
||||
// If we have a saved strategy, use its ID
|
||||
if (formData.id) {
|
||||
console.log('Using existing strategy ID:', formData.id);
|
||||
await generateAIRecommendations(formData.id);
|
||||
} else {
|
||||
console.log('No strategy ID found, creating new strategy...');
|
||||
// If no strategy is saved yet, save it first, then generate AI insights
|
||||
const isValid = validateAllFields();
|
||||
console.log('Form validation result:', isValid);
|
||||
|
||||
if (isValid) {
|
||||
const completionStats = getCompletionStats();
|
||||
const strategyData = {
|
||||
...formData,
|
||||
completion_percentage: completionStats.completion_percentage,
|
||||
user_id: 1, // This would come from auth context
|
||||
name: formData.name || 'Enhanced Content Strategy',
|
||||
industry: formData.industry || 'General'
|
||||
};
|
||||
|
||||
console.log('Attempting to create strategy with data:', strategyData);
|
||||
|
||||
// Use SSE streaming endpoint for strategy generation with educational content
|
||||
await generateStrategyWithPolling(strategyData);
|
||||
} else {
|
||||
setError('Please fill in all required fields before generating AI insights.');
|
||||
console.error('Form validation failed. Cannot generate AI insights.');
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(`Error generating AI recommendations: ${err.message || 'Unknown error'}`);
|
||||
console.error('Error in handleCreateStrategy:', err);
|
||||
} finally {
|
||||
setAIGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const generateStrategyWithPolling = async (strategyData: any) => {
|
||||
try {
|
||||
console.log('🚀 Starting polling-based strategy generation...');
|
||||
|
||||
// Initialize progress and educational content
|
||||
setGenerationProgress(0);
|
||||
setEducationalContent({
|
||||
title: '🤖 AI-Powered Strategy Generation',
|
||||
description: 'Initializing AI analysis and preparing educational content...',
|
||||
details: [
|
||||
'🔧 Setting up AI services',
|
||||
'📊 Loading user context',
|
||||
'🎯 Preparing strategy framework',
|
||||
'📚 Generating educational content'
|
||||
],
|
||||
insight: 'We\'re getting everything ready for your personalized AI strategy generation.',
|
||||
estimated_time: '2-3 minutes total'
|
||||
});
|
||||
|
||||
// Show educational modal
|
||||
setShowEducationalModal(true);
|
||||
|
||||
// Start polling-based strategy generation directly (no basic strategy creation)
|
||||
const generationResult = await contentPlanningApi.startStrategyGenerationPolling(1, 'Enhanced Content Strategy');
|
||||
console.log('Strategy generation started:', generationResult);
|
||||
|
||||
if (generationResult && generationResult.task_id) {
|
||||
const taskId = generationResult.task_id;
|
||||
console.log('Task ID received:', taskId);
|
||||
|
||||
// Start polling for status updates
|
||||
contentPlanningApi.pollStrategyGeneration(
|
||||
taskId,
|
||||
// onProgress callback
|
||||
(status: any) => {
|
||||
console.log('📊 Progress update:', status);
|
||||
|
||||
// Update progress
|
||||
if (status.progress !== undefined) {
|
||||
setGenerationProgress(status.progress);
|
||||
}
|
||||
|
||||
// Update educational content
|
||||
if (status.educational_content) {
|
||||
console.log('📚 Updating educational content:', status.educational_content);
|
||||
setEducationalContent(status.educational_content);
|
||||
}
|
||||
|
||||
// Update message
|
||||
if (status.message) {
|
||||
console.log('📝 Status message:', status.message);
|
||||
}
|
||||
},
|
||||
// onComplete callback
|
||||
(strategy: any) => {
|
||||
console.log('✅ Strategy generation completed successfully!');
|
||||
setCurrentStrategy(strategy);
|
||||
setShowEducationalModal(false);
|
||||
setError('Strategy created successfully! Check the Strategic Intelligence tab for detailed insights.');
|
||||
},
|
||||
// onError callback
|
||||
(error: string) => {
|
||||
console.error('❌ Strategy generation failed:', error);
|
||||
setError(`Strategy generation failed: ${error}`);
|
||||
setShowEducationalModal(false);
|
||||
},
|
||||
5000, // 5 second polling interval for faster updates
|
||||
72 // 6 minutes max (72 * 5 seconds)
|
||||
);
|
||||
|
||||
} else {
|
||||
setError('Failed to start strategy generation. No task ID received.');
|
||||
setShowEducationalModal(false);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error in polling-based strategy generation:', error);
|
||||
setError(`Error in strategy generation: ${error.message || 'Unknown error'}`);
|
||||
setShowEducationalModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveStrategy = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
|
||||
const completionStats = getCompletionStats();
|
||||
const strategyData = {
|
||||
...formData,
|
||||
completion_percentage: completionStats.completion_percentage,
|
||||
user_id: 1,
|
||||
name: formData.name || 'Enhanced Content Strategy',
|
||||
industry: formData.industry || 'General'
|
||||
};
|
||||
|
||||
const newStrategy = await createEnhancedStrategy(strategyData);
|
||||
setCurrentStrategy(newStrategy);
|
||||
setError('Strategy saved successfully!');
|
||||
} catch (err: any) {
|
||||
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleCreateStrategy,
|
||||
handleSaveStrategy
|
||||
};
|
||||
};
|
||||
|
||||
// UI Component
|
||||
const ActionButtons: React.FC<ActionButtonsProps> = ({
|
||||
aiGenerating,
|
||||
saving,
|
||||
reviewProgressPercentage,
|
||||
onCreateStrategy,
|
||||
onSaveStrategy
|
||||
}) => {
|
||||
return (
|
||||
<Box sx={{ mt: 3, display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
|
||||
<MuiTooltip
|
||||
title={reviewProgressPercentage < 20 ? `Complete at least 20% of the form (currently ${Math.round(reviewProgressPercentage)}%)` : 'Create a comprehensive content strategy with AI insights'}
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AutoAwesomeIcon />}
|
||||
onClick={onCreateStrategy}
|
||||
disabled={aiGenerating || reviewProgressPercentage < 20}
|
||||
>
|
||||
{aiGenerating ? 'Creating...' : 'Create Strategy'}
|
||||
</Button>
|
||||
</span>
|
||||
</MuiTooltip>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<SaveIcon />}
|
||||
onClick={onSaveStrategy}
|
||||
disabled={saving || reviewProgressPercentage < 30}
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save Strategy'}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionButtons;
|
||||
@@ -0,0 +1,257 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Chip,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Grid,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import {
|
||||
School as SchoolIcon,
|
||||
Lightbulb as LightbulbIcon,
|
||||
Psychology as PsychologyIcon,
|
||||
Timeline as TimelineIcon,
|
||||
CheckCircle as CheckCircleIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import StrategicInputField from '../StrategicInputField';
|
||||
import { CategoryDetailViewProps, EducationalInfoDialogProps } from '../types/contentStrategy.types';
|
||||
|
||||
const EducationalInfoDialog: React.FC<EducationalInfoDialogProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
categoryId,
|
||||
getEducationalContent
|
||||
}) => {
|
||||
if (!categoryId) return null;
|
||||
|
||||
const educationalContent = getEducationalContent(categoryId);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<SchoolIcon />
|
||||
{educationalContent?.title}
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body1" paragraph>
|
||||
{educationalContent?.description}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Key Points:
|
||||
</Typography>
|
||||
<List>
|
||||
{educationalContent?.points?.map((point: string, index: number) => (
|
||||
<ListItem key={index} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon>
|
||||
<LightbulbIcon color="primary" fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={point} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Pro Tips:
|
||||
</Typography>
|
||||
<List>
|
||||
{educationalContent?.tips?.map((tip: string, index: number) => (
|
||||
<ListItem key={index} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon>
|
||||
<PsychologyIcon color="secondary" fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={tip} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
Got it!
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
|
||||
activeCategory,
|
||||
formData,
|
||||
formErrors,
|
||||
autoPopulatedFields,
|
||||
dataSources,
|
||||
personalizationData,
|
||||
completionStats,
|
||||
reviewedCategories,
|
||||
isMarkingReviewed,
|
||||
showEducationalInfo,
|
||||
STRATEGIC_INPUT_FIELDS,
|
||||
onUpdateFormField,
|
||||
onValidateFormField,
|
||||
onShowTooltip,
|
||||
onViewDataSource,
|
||||
onConfirmCategoryReview,
|
||||
onSetActiveCategory,
|
||||
onSetShowEducationalInfo,
|
||||
getCategoryIcon,
|
||||
getCategoryColor,
|
||||
getEducationalContent
|
||||
}) => {
|
||||
if (!activeCategory) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Box sx={{ textAlign: 'center', py: 8 }}>
|
||||
<TimelineIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Select a Category to Review
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Click on any category from the left panel to review and complete the fields.
|
||||
</Typography>
|
||||
</Box>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{/* Category Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||
{getCategoryIcon(activeCategory)}
|
||||
<Typography variant="h5" sx={{ ml: 1 }}>
|
||||
{activeCategory.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={`${Math.round(completionStats.category_completion[activeCategory])}% Complete`}
|
||||
color={getCategoryColor(activeCategory) as any}
|
||||
sx={{ ml: 'auto' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Educational Info Dialog */}
|
||||
<EducationalInfoDialog
|
||||
open={!!showEducationalInfo}
|
||||
onClose={() => onSetShowEducationalInfo(null)}
|
||||
categoryId={showEducationalInfo}
|
||||
getEducationalContent={getEducationalContent}
|
||||
/>
|
||||
|
||||
{/* Category Fields */}
|
||||
<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';
|
||||
|
||||
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={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}
|
||||
accentColorKey={getCategoryColor(activeCategory) as any}
|
||||
isCompact={isCompactField}
|
||||
/>
|
||||
</motion.div>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* Category Actions */}
|
||||
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
|
||||
{(() => {
|
||||
const isReviewed = reviewedCategories.has(activeCategory);
|
||||
console.log('🔍 Category review status:', {
|
||||
activeCategory,
|
||||
isReviewed,
|
||||
reviewedCategories: Array.from(reviewedCategories)
|
||||
});
|
||||
return !isReviewed ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
console.log('🔘 Button clicked! activeCategory:', activeCategory);
|
||||
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
|
||||
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
|
||||
onConfirmCategoryReview();
|
||||
}}
|
||||
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
|
||||
disabled={isMarkingReviewed}
|
||||
>
|
||||
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
|
||||
</Button>
|
||||
) : (
|
||||
<Chip
|
||||
label="Category Reviewed"
|
||||
color="success"
|
||||
icon={<CheckCircleIcon />}
|
||||
sx={{ px: 2, py: 1 }}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onSetActiveCategory(null)}
|
||||
>
|
||||
Back to Overview
|
||||
</Button>
|
||||
</Box>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryDetailView;
|
||||
@@ -0,0 +1,554 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import {
|
||||
School as SchoolIcon,
|
||||
Lightbulb as LightbulbIcon,
|
||||
Timeline as TimelineIcon,
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
TrendingUp as TrendingUpIcon,
|
||||
FiberManualRecord as FiberManualRecordIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import { EducationalModalProps } from '../types/contentStrategy.types';
|
||||
|
||||
const EducationalModal: React.FC<EducationalModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
educationalContent,
|
||||
generationProgress
|
||||
}) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: 4,
|
||||
background: 'linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%)',
|
||||
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15)',
|
||||
border: '1px solid rgba(102, 126, 234, 0.1)',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
color: 'white',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)',
|
||||
pointerEvents: 'none'
|
||||
}
|
||||
}}>
|
||||
<Box display="flex" alignItems="center" gap={2} sx={{ position: 'relative', zIndex: 1 }}>
|
||||
<Box sx={{
|
||||
p: 1,
|
||||
borderRadius: 2,
|
||||
background: 'rgba(255, 255, 255, 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||||
}}>
|
||||
<SchoolIcon sx={{ color: 'white', fontSize: 24 }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 0.5 }}>
|
||||
{educationalContent?.title || 'AI Strategy Generation'}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, fontWeight: 500 }}>
|
||||
Creating your comprehensive content strategy
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ p: 4 }}>
|
||||
{educationalContent ? (
|
||||
<Box>
|
||||
{/* Enhanced Progress Section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, color: '#667eea', mb: 0.5 }}>
|
||||
Progress: {generationProgress}%
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
|
||||
Step {Math.ceil(generationProgress / 10)} of 8 • {Math.ceil((100 - generationProgress) / 10)} steps remaining
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ textAlign: 'right' }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: '#667eea' }}>
|
||||
{generationProgress}%
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Complete
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Enhanced Progress Bar with Glitch Effect */}
|
||||
<Box sx={{ position: 'relative', mb: 1 }}>
|
||||
<Box sx={{
|
||||
height: 12,
|
||||
borderRadius: 6,
|
||||
background: 'linear-gradient(90deg, #f0f0f0 0%, #e0e0e0 100%)',
|
||||
overflow: 'hidden',
|
||||
boxShadow: 'inset 0 2px 4px rgba(0, 0, 0, 0.1)',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${generationProgress}%` }}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
ease: "easeOut",
|
||||
delay: 0.2
|
||||
}}
|
||||
style={{
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%)',
|
||||
borderRadius: 6,
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{/* Glitch Effect Overlay */}
|
||||
<motion.div
|
||||
animate={{
|
||||
x: [0, -2, 2, 0],
|
||||
opacity: [0.8, 1, 0.8]
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.1,
|
||||
repeat: Infinity,
|
||||
repeatType: "reverse"
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.3) 50%, transparent 100%)',
|
||||
borderRadius: 6
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Shimmer Effect */}
|
||||
<motion.div
|
||||
animate={{
|
||||
x: ['-100%', '100%']
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "linear"
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.4) 50%, transparent 100%)',
|
||||
borderRadius: 6
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Progress Markers */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
mt: 1,
|
||||
px: 1
|
||||
}}>
|
||||
{[0, 25, 50, 75, 100].map((marker) => (
|
||||
<Box key={marker} sx={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
background: generationProgress >= marker ? '#667eea' : '#e0e0e0',
|
||||
boxShadow: generationProgress >= marker ? '0 2px 8px rgba(102, 126, 234, 0.3)' : 'none',
|
||||
transition: 'all 0.3s ease'
|
||||
}} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Enhanced Educational Content */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
>
|
||||
<Grid container spacing={3}>
|
||||
{/* Main Content */}
|
||||
<Grid item xs={12} lg={8}>
|
||||
{educationalContent.description && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom sx={{
|
||||
fontWeight: 600,
|
||||
color: '#667eea',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}>
|
||||
<LightbulbIcon sx={{ fontSize: 20 }} />
|
||||
What's Happening
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{
|
||||
lineHeight: 1.7,
|
||||
color: 'text.primary',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{educationalContent.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.details && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom sx={{
|
||||
fontWeight: 600,
|
||||
color: '#667eea',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}>
|
||||
<TimelineIcon sx={{ fontSize: 20 }} />
|
||||
Current Activities
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
background: 'rgba(102, 126, 234, 0.05)',
|
||||
borderRadius: 3,
|
||||
p: 2,
|
||||
border: '1px solid rgba(102, 126, 234, 0.1)'
|
||||
}}>
|
||||
<List dense sx={{ py: 0 }}>
|
||||
{educationalContent.details.map((detail: string, index: number) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.1 * index }}
|
||||
>
|
||||
<ListItem sx={{ py: 1, px: 0 }}>
|
||||
<ListItemIcon sx={{ minWidth: 36 }}>
|
||||
<Box sx={{
|
||||
p: 0.5,
|
||||
borderRadius: 1,
|
||||
background: 'rgba(102, 126, 234, 0.1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<FiberManualRecordIcon sx={{
|
||||
fontSize: 8,
|
||||
color: '#667eea'
|
||||
}} />
|
||||
</Box>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={detail}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
sx: {
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.5,
|
||||
color: 'text.primary'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</motion.div>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.ai_prompt_preview && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom sx={{
|
||||
fontWeight: 600,
|
||||
color: '#667eea',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}>
|
||||
<AutoAwesomeIcon sx={{ fontSize: 20 }} />
|
||||
AI Processing
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
borderRadius: 3,
|
||||
p: 2.5,
|
||||
color: 'white',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)',
|
||||
pointerEvents: 'none'
|
||||
}} />
|
||||
<Typography variant="body2" sx={{
|
||||
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: 1.6,
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}}>
|
||||
{educationalContent.ai_prompt_preview}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* Sidebar with Insights and Info */}
|
||||
<Grid item xs={12} lg={4}>
|
||||
<Box sx={{
|
||||
background: 'rgba(102, 126, 234, 0.02)',
|
||||
borderRadius: 3,
|
||||
p: 3,
|
||||
border: '1px solid rgba(102, 126, 234, 0.1)',
|
||||
height: 'fit-content'
|
||||
}}>
|
||||
<Typography variant="h6" gutterBottom sx={{
|
||||
fontWeight: 600,
|
||||
color: '#667eea',
|
||||
mb: 2
|
||||
}}>
|
||||
Insights & Information
|
||||
</Typography>
|
||||
|
||||
{educationalContent.insight && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{
|
||||
fontWeight: 600,
|
||||
color: '#4caf50',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
mb: 1
|
||||
}}>
|
||||
<LightbulbIcon sx={{ fontSize: 16 }} />
|
||||
Key Insight
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
lineHeight: 1.6,
|
||||
color: 'text.secondary',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{educationalContent.insight}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.estimated_time && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{
|
||||
fontWeight: 600,
|
||||
color: '#ff9800',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
mb: 1
|
||||
}}>
|
||||
<ScheduleIcon sx={{ fontSize: 16 }} />
|
||||
Time Estimate
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
lineHeight: 1.6,
|
||||
color: 'text.secondary',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{educationalContent.estimated_time}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.achievement && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{
|
||||
fontWeight: 600,
|
||||
color: '#4caf50',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
mb: 1
|
||||
}}>
|
||||
<CheckCircleIcon sx={{ fontSize: 16 }} />
|
||||
Achievement
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
lineHeight: 1.6,
|
||||
color: 'text.secondary',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{educationalContent.achievement}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{educationalContent.next_step && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{
|
||||
fontWeight: 600,
|
||||
color: '#9c27b0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
mb: 1
|
||||
}}>
|
||||
<TrendingUpIcon sx={{ fontSize: 16 }} />
|
||||
Next Step
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
lineHeight: 1.6,
|
||||
color: 'text.secondary',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{educationalContent.next_step}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Summary for completion */}
|
||||
{educationalContent.summary && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
>
|
||||
<Box sx={{
|
||||
mt: 4,
|
||||
p: 3,
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
borderRadius: 3,
|
||||
color: 'white',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)',
|
||||
pointerEvents: 'none'
|
||||
}} />
|
||||
<Typography variant="h6" gutterBottom sx={{
|
||||
fontWeight: 700,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}>
|
||||
<AutoAwesomeIcon />
|
||||
Strategy Generation Summary
|
||||
</Typography>
|
||||
<Grid container spacing={2} sx={{ position: 'relative', zIndex: 1 }}>
|
||||
{Object.entries(educationalContent.summary).map(([key, value]) => (
|
||||
<Grid item xs={6} md={3} key={key}>
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>
|
||||
{value as string}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.9, fontWeight: 500 }}>
|
||||
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<CircularProgress size={60} sx={{ color: '#667eea', mb: 2 }} />
|
||||
<Typography variant="h6" sx={{ fontWeight: 600, color: '#667eea' }}>
|
||||
Initializing Strategy Generation
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
Preparing AI models and analyzing your data...
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{
|
||||
p: 3,
|
||||
pt: 0,
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
fontWeight: 600,
|
||||
borderColor: 'rgba(102, 126, 234, 0.3)',
|
||||
color: '#667eea',
|
||||
'&:hover': {
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.05)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default EducationalModal;
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Alert,
|
||||
Button,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Refresh as RefreshIcon,
|
||||
Info as InfoIcon
|
||||
} from '@mui/icons-material';
|
||||
import { ErrorAlertProps } from '../types/contentStrategy.types';
|
||||
|
||||
const ErrorAlert: React.FC<ErrorAlertProps> = ({
|
||||
error,
|
||||
onRetry,
|
||||
onShowDataSourceTransparency
|
||||
}) => {
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{ mb: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
|
||||
action={
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={onRetry}
|
||||
startIcon={<RefreshIcon />}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onShowDataSourceTransparency}
|
||||
startIcon={<InfoIcon />}
|
||||
>
|
||||
Why?
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="subtitle2">Real data required</Typography>
|
||||
<Typography variant="body2">
|
||||
{error || 'We could not auto-populate because required onboarding/analysis data is missing. Connect sources or complete onboarding, then retry.'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorAlert;
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
Button,
|
||||
Alert,
|
||||
Grid
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Assessment as AssessmentIcon
|
||||
} from '@mui/icons-material';
|
||||
import { StrategyDisplayProps } from '../types/contentStrategy.types';
|
||||
|
||||
const StrategyDisplay: React.FC<StrategyDisplayProps> = ({
|
||||
currentStrategy,
|
||||
error,
|
||||
categoryCompletionMessage,
|
||||
onViewStrategicIntelligence
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* Success Alert */}
|
||||
{!error && currentStrategy && (
|
||||
<Alert severity="success" sx={{ mb: 3 }}>
|
||||
Strategy "{currentStrategy.name}" created successfully! Check the Strategic Intelligence tab for detailed insights.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Strategy Display */}
|
||||
{currentStrategy && (
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Created Strategy: {currentStrategy.name}
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
Industry: {currentStrategy.industry}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
Completion: {currentStrategy.completion_percentage}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
Created: {new Date(currentStrategy.created_at).toLocaleDateString()}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
ID: {currentStrategy.id}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={onViewStrategicIntelligence}
|
||||
startIcon={<AssessmentIcon />}
|
||||
>
|
||||
View Strategic Intelligence
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* Category Completion Message */}
|
||||
{categoryCompletionMessage && (
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{ mb: 3, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
{categoryCompletionMessage}
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StrategyDisplay;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Main Content Strategy Builder
|
||||
export { default as ContentStrategyBuilder } from '../ContentStrategyBuilder';
|
||||
|
||||
// Components
|
||||
export { default as HeaderSection } from './components/HeaderSection';
|
||||
export { default as CategoryList } from './components/CategoryList';
|
||||
export { default as ProgressTracker } from './components/ProgressTracker';
|
||||
export { default as EducationalModal } from './components/EducationalModal';
|
||||
export { default as CategoryDetailView } from './components/CategoryDetailView';
|
||||
export { default as ActionButtons } from './components/ActionButtons';
|
||||
export { default as StrategyDisplay } from './components/StrategyDisplay';
|
||||
export { default as ErrorAlert } from './components/ErrorAlert';
|
||||
|
||||
// Hooks
|
||||
export { useActionButtonsBusinessLogic } from './components/ActionButtons';
|
||||
|
||||
// Types
|
||||
export * from './types/contentStrategy.types';
|
||||
@@ -0,0 +1,124 @@
|
||||
// Educational Content Types
|
||||
export interface EducationalContent {
|
||||
title?: string;
|
||||
description?: string;
|
||||
details?: string[];
|
||||
insight?: string;
|
||||
estimated_time?: string;
|
||||
achievement?: string;
|
||||
next_step?: string;
|
||||
ai_prompt_preview?: string;
|
||||
summary?: Record<string, string>;
|
||||
}
|
||||
|
||||
// Educational Modal Props
|
||||
export interface EducationalModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
educationalContent: EducationalContent | null;
|
||||
generationProgress: number;
|
||||
}
|
||||
|
||||
// Category Detail View Types
|
||||
export interface CategoryDetailViewProps {
|
||||
activeCategory: string | null;
|
||||
formData: Record<string, any>;
|
||||
formErrors: Record<string, any>;
|
||||
autoPopulatedFields: Record<string, any>;
|
||||
dataSources: Record<string, any>;
|
||||
personalizationData: Record<string, any>;
|
||||
completionStats: any;
|
||||
reviewedCategories: Set<string>;
|
||||
isMarkingReviewed: boolean;
|
||||
showEducationalInfo: string | null;
|
||||
STRATEGIC_INPUT_FIELDS: any[];
|
||||
onUpdateFormField: (fieldId: string, value: any) => void;
|
||||
onValidateFormField: (fieldId: string) => boolean;
|
||||
onShowTooltip: (fieldId: string) => void;
|
||||
onViewDataSource: () => void;
|
||||
onConfirmCategoryReview: () => void;
|
||||
onSetActiveCategory: (category: string | null) => void;
|
||||
onSetShowEducationalInfo: (categoryId: string | null) => void;
|
||||
getCategoryIcon: (category: string) => React.ReactNode;
|
||||
getCategoryColor: (category: string) => string;
|
||||
getEducationalContent: (categoryId: string) => any;
|
||||
}
|
||||
|
||||
// Educational Info Dialog Types
|
||||
export interface EducationalInfoDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
categoryId: string | null;
|
||||
getEducationalContent: (categoryId: string) => any;
|
||||
}
|
||||
|
||||
// Action Buttons Types
|
||||
export interface ActionButtonsProps {
|
||||
aiGenerating: boolean;
|
||||
saving: boolean;
|
||||
reviewProgressPercentage: number;
|
||||
onCreateStrategy: () => void;
|
||||
onSaveStrategy: () => void;
|
||||
}
|
||||
|
||||
// Action Buttons Business Logic Types
|
||||
export interface ActionButtonsBusinessLogicProps {
|
||||
formData: Record<string, any>;
|
||||
error: string | null;
|
||||
currentStrategy: any;
|
||||
setAIGenerating: (generating: boolean) => void;
|
||||
setError: (error: string | null) => void;
|
||||
setCurrentStrategy: (strategy: any) => void;
|
||||
setSaving: (saving: boolean) => void;
|
||||
setGenerationProgress: (progress: number) => void;
|
||||
setEducationalContent: (content: any) => void;
|
||||
setShowEducationalModal: (show: boolean) => void;
|
||||
validateAllFields: () => boolean;
|
||||
getCompletionStats: () => any;
|
||||
generateAIRecommendations: (strategyId: string) => Promise<void>;
|
||||
createEnhancedStrategy: (strategyData: any) => Promise<any>;
|
||||
contentPlanningApi: any;
|
||||
}
|
||||
|
||||
// Strategy Generation Types
|
||||
export interface StrategyGenerationStatus {
|
||||
progress?: number;
|
||||
message?: string;
|
||||
educational_content?: EducationalContent;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface StrategyGenerationCallbacks {
|
||||
onProgress: (status: StrategyGenerationStatus) => void;
|
||||
onComplete: (strategy: any) => void;
|
||||
onError: (error: string) => void;
|
||||
}
|
||||
|
||||
// Category Navigation Types
|
||||
export interface CategoryNavigationProps {
|
||||
activeCategory: string | null;
|
||||
onCategorySelect: (category: string | null) => void;
|
||||
completionStats: any;
|
||||
reviewedCategories: Set<string>;
|
||||
}
|
||||
|
||||
// Strategy Display Types
|
||||
export interface StrategyDisplayProps {
|
||||
currentStrategy: any;
|
||||
error: string | null;
|
||||
categoryCompletionMessage: string | null;
|
||||
onViewStrategicIntelligence: () => void;
|
||||
}
|
||||
|
||||
// Error Alert Types
|
||||
export interface ErrorAlertProps {
|
||||
error: string | null;
|
||||
onRetry: () => void;
|
||||
onShowDataSourceTransparency: () => void;
|
||||
}
|
||||
|
||||
// Success Alert Types
|
||||
export interface SuccessAlertProps {
|
||||
currentStrategy: any;
|
||||
categoryCompletionMessage: string | null;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Alert, Typography, Grid } 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';
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,435 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Popover
|
||||
} from '@mui/material';
|
||||
import {
|
||||
TrendingUp as TrendingUpIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
Business as BusinessIcon,
|
||||
Star as StarIcon,
|
||||
Warning as WarningIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Cancel as CancelIcon,
|
||||
Lightbulb as LightbulbIcon
|
||||
} from '@mui/icons-material';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import {
|
||||
ANALYSIS_CARD_STYLES,
|
||||
getSectionStyles,
|
||||
getAccordionStyles,
|
||||
getEnhancedChipStyles,
|
||||
getListItemStyles
|
||||
} from '../styles';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
|
||||
interface CompetitiveAnalysisCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
}
|
||||
|
||||
const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strategyData }) => {
|
||||
const [chipModal, setChipModal] = useState<{
|
||||
open: boolean;
|
||||
anchorEl: HTMLElement | null;
|
||||
content: string;
|
||||
type: 'strength' | 'weakness';
|
||||
}>({
|
||||
open: false,
|
||||
anchorEl: null,
|
||||
content: '',
|
||||
type: 'strength'
|
||||
});
|
||||
|
||||
// Get style objects
|
||||
const sectionStyles = getSectionStyles();
|
||||
const accordionStyles = getAccordionStyles();
|
||||
const listItemStyles = getListItemStyles();
|
||||
|
||||
// Helper function to extract company name from description
|
||||
const extractCompanyName = (description: string): string => {
|
||||
if (description.includes('Jasper')) return 'Jasper AI';
|
||||
if (description.includes('Copy.ai')) return 'Copy.ai';
|
||||
if (description.includes('Writesonic')) return 'Writesonic';
|
||||
if (description.includes('Grammarly')) return 'Grammarly';
|
||||
if (description.includes('Surfer')) return 'Surfer SEO';
|
||||
if (description.includes('Clearscope')) return 'Clearscope';
|
||||
if (description.includes('Ahrefs')) return 'Ahrefs';
|
||||
if (description.includes('SEMrush')) return 'SEMrush';
|
||||
return description.split(' ').slice(0, 2).join(' ');
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// Summary content - always visible
|
||||
const summaryContent = (
|
||||
<Box>
|
||||
{/* Competitive Overview */}
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.warning} 0%, ${ANALYSIS_CARD_STYLES.colors.error} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600,
|
||||
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.warning}30`
|
||||
}}>
|
||||
{strategyData.competitive_analysis.competitors?.length || 0}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Key Competitors
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
{strategyData.competitive_analysis.competitors?.length || 0} competitors analyzed
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label={`${strategyData.competitive_analysis.market_gaps?.length || 0} Gaps`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`${strategyData.competitive_analysis.opportunities?.length || 0} Opportunities`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Competitors Preview */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
|
||||
Top Competitors
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{strategyData.competitive_analysis.competitors?.slice(0, 3).map((competitor: any, index: number) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={extractCompanyName(competitor.name || competitor.description || 'Unknown')}
|
||||
size="small"
|
||||
icon={<BusinessIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
))}
|
||||
{(strategyData.competitive_analysis.competitors?.length || 0) > 3 && (
|
||||
<Chip
|
||||
label={`+${(strategyData.competitive_analysis.competitors?.length || 0) - 3} more`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
// Detailed content - shown on expansion
|
||||
const detailedContent = (
|
||||
<Box>
|
||||
{/* Competitors Analysis */}
|
||||
{strategyData.competitive_analysis.competitors && strategyData.competitive_analysis.competitors.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Competitor Analysis ({strategyData.competitive_analysis.competitors.length})
|
||||
</Typography>
|
||||
|
||||
{strategyData.competitive_analysis.competitors.map((competitor: any, index: number) => (
|
||||
<Accordion key={index} defaultExpanded={false} sx={accordionStyles.accordion}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
|
||||
sx={accordionStyles.accordionSummary}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Box sx={{ mr: 1.5 }}>
|
||||
<BusinessIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontSize: 20 }} />
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
|
||||
{extractCompanyName(competitor.name || competitor.description || 'Unknown Competitor')}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
|
||||
{competitor.description || 'Competitor analysis'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 0 }}>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
{/* Content Strategy */}
|
||||
{competitor.content_strategy && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 1 }}>
|
||||
Content Strategy
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{competitor.content_strategy}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Strengths */}
|
||||
{competitor.strengths && competitor.strengths.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
|
||||
Strengths
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{competitor.strengths.map((strength: string, strengthIndex: number) => (
|
||||
<Chip
|
||||
key={strengthIndex}
|
||||
label={strength}
|
||||
size="small"
|
||||
icon={<CheckCircleIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
onClick={(e) => setChipModal({
|
||||
open: true,
|
||||
anchorEl: e.currentTarget,
|
||||
content: strength,
|
||||
type: 'strength'
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Weaknesses */}
|
||||
{competitor.weaknesses && competitor.weaknesses.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontWeight: 600, mb: 1 }}>
|
||||
Weaknesses
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{competitor.weaknesses.map((weakness: string, weaknessIndex: number) => (
|
||||
<Chip
|
||||
key={weaknessIndex}
|
||||
label={weakness}
|
||||
size="small"
|
||||
icon={<CancelIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip}
|
||||
onClick={(e) => setChipModal({
|
||||
open: true,
|
||||
anchorEl: e.currentTarget,
|
||||
content: weakness,
|
||||
type: 'weakness'
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Market Gaps */}
|
||||
{strategyData.competitive_analysis.market_gaps && strategyData.competitive_analysis.market_gaps.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Market Gaps ({strategyData.competitive_analysis.market_gaps.length})
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{strategyData.competitive_analysis.market_gaps.map((gap: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.info,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={gap}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Opportunities */}
|
||||
{strategyData.competitive_analysis.opportunities && strategyData.competitive_analysis.opportunities.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Opportunities ({strategyData.competitive_analysis.opportunities.length})
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{strategyData.competitive_analysis.opportunities.map((opportunity: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.success,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Strategic Recommendations */}
|
||||
{strategyData.competitive_analysis.recommendations && strategyData.competitive_analysis.recommendations.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Strategic Recommendations ({strategyData.competitive_analysis.recommendations.length})
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{strategyData.competitive_analysis.recommendations.map((recommendation: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.accent,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={recommendation}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid item xs={12} lg={6}>
|
||||
<ProgressiveCard
|
||||
title="Competitive Analysis"
|
||||
subtitle="Market positioning insights"
|
||||
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
|
||||
summary={summaryContent}
|
||||
details={detailedContent}
|
||||
trigger="hover"
|
||||
autoCollapseDelay={3000}
|
||||
/>
|
||||
|
||||
{/* Chip Modal for detailed view */}
|
||||
<Popover
|
||||
open={chipModal.open}
|
||||
anchorEl={chipModal.anchorEl}
|
||||
onClose={() => setChipModal({ ...chipModal, open: false })}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
background: ANALYSIS_CARD_STYLES.colors.background.dark,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)',
|
||||
p: 2,
|
||||
maxWidth: 300
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
{chipModal.type === 'strength' ? (
|
||||
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16, mr: 1 }} />
|
||||
) : (
|
||||
<CancelIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontSize: 16, mr: 1 }} />
|
||||
)}
|
||||
<Typography variant="body2" sx={{
|
||||
color: chipModal.type === 'strength' ? ANALYSIS_CARD_STYLES.colors.success : ANALYSIS_CARD_STYLES.colors.warning,
|
||||
fontWeight: 600
|
||||
}}>
|
||||
{chipModal.type === 'strength' ? 'Strength' : 'Weakness'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{chipModal.content}
|
||||
</Typography>
|
||||
</Popover>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompetitiveAnalysisCard;
|
||||
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Alert,
|
||||
Box
|
||||
} from '@mui/material';
|
||||
import {
|
||||
CheckCircle as CheckCircleIcon
|
||||
} from '@mui/icons-material';
|
||||
import { ConfirmationDialogProps } from '../types/strategy.types';
|
||||
|
||||
const ConfirmationDialog: React.FC<ConfirmationDialogProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
onConfirm
|
||||
}) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 16px 48px rgba(0, 0, 0, 0.2)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
p: 1,
|
||||
borderRadius: 2,
|
||||
background: 'linear-gradient(135deg, #4caf50 0%, #8bc34a 100%)',
|
||||
mr: 1.5,
|
||||
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)'
|
||||
}}>
|
||||
<CheckCircleIcon sx={{ color: 'white', fontSize: 20 }} />
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
Confirm Strategy
|
||||
</Typography>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body1" sx={{ mb: 2, fontWeight: 500 }}>
|
||||
Are you sure you want to confirm this strategy? Once confirmed, you'll be able to generate a content calendar based on this strategy.
|
||||
</Typography>
|
||||
<Alert severity="info" sx={{
|
||||
mb: 2,
|
||||
borderRadius: 2,
|
||||
border: '1px solid rgba(33, 150, 243, 0.3)'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||
<strong>Next Steps:</strong> After confirmation, you can generate a comprehensive content calendar that follows this strategy.
|
||||
</Typography>
|
||||
</Alert>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
px: 3,
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
variant="contained"
|
||||
color="success"
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
px: 3,
|
||||
fontWeight: 600,
|
||||
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)',
|
||||
'&:hover': {
|
||||
boxShadow: '0 6px 16px rgba(76, 175, 80, 0.4)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Confirm Strategy
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmationDialog;
|
||||
@@ -0,0 +1,453 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Timeline as TimelineIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
Group as GroupIcon,
|
||||
AttachMoney as MoneyIcon,
|
||||
CheckCircle as CheckCircleIcon
|
||||
} from '@mui/icons-material';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import {
|
||||
ANALYSIS_CARD_STYLES,
|
||||
getSectionStyles,
|
||||
getAccordionStyles,
|
||||
getEnhancedChipStyles,
|
||||
getListItemStyles
|
||||
} from '../styles';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
|
||||
interface ImplementationRoadmapCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
}
|
||||
|
||||
const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ strategyData }) => {
|
||||
// Get style objects
|
||||
const sectionStyles = getSectionStyles();
|
||||
const accordionStyles = getAccordionStyles();
|
||||
const listItemStyles = getListItemStyles();
|
||||
|
||||
// Helper function to format budget allocation
|
||||
const formatBudgetAllocation = (budgetAllocation: any): string => {
|
||||
if (typeof budgetAllocation === 'string') return budgetAllocation;
|
||||
if (typeof budgetAllocation === 'object' && budgetAllocation !== null) {
|
||||
return Object.entries(budgetAllocation)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join(', ');
|
||||
}
|
||||
return 'Budget allocation not specified';
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// Summary content - always visible
|
||||
const summaryContent = (
|
||||
<Box>
|
||||
{/* Project Overview */}
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.info} 0%, ${ANALYSIS_CARD_STYLES.colors.primary} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600,
|
||||
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.info}30`
|
||||
}}>
|
||||
{strategyData.implementation_roadmap.phases?.length || 0}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Project Phases
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
{strategyData.implementation_roadmap.total_duration || '6 months'} implementation
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label={`${strategyData.implementation_roadmap.phases?.reduce((total: number, phase: any) => total + (phase.tasks?.length || 0), 0) || 0} Tasks`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`${strategyData.implementation_roadmap.phases?.reduce((total: number, phase: any) => total + (phase.milestones?.length || 0), 0) || 0} Milestones`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Phases Preview */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
|
||||
Implementation Phases
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{strategyData.implementation_roadmap.phases?.slice(0, 4).map((phase: any, index: number) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={`Phase ${index + 1}`}
|
||||
size="small"
|
||||
icon={<TimelineIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
))}
|
||||
{(strategyData.implementation_roadmap.phases?.length || 0) > 4 && (
|
||||
<Chip
|
||||
label={`+${(strategyData.implementation_roadmap.phases?.length || 0) - 4} more`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
// Detailed content - shown on expansion
|
||||
const detailedContent = (
|
||||
<Box>
|
||||
{/* Project Timeline */}
|
||||
{strategyData.implementation_roadmap.timeline && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Project Timeline
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
Duration: {strategyData.implementation_roadmap.total_duration || '6 months'}
|
||||
</Typography>
|
||||
</Box>
|
||||
{strategyData.implementation_roadmap.timeline.start_date && (
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary, mb: 1 }}>
|
||||
Start Date: {strategyData.implementation_roadmap.timeline.start_date}
|
||||
</Typography>
|
||||
)}
|
||||
{strategyData.implementation_roadmap.timeline.end_date && (
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary, mb: 1 }}>
|
||||
End Date: {strategyData.implementation_roadmap.timeline.end_date}
|
||||
</Typography>
|
||||
)}
|
||||
{strategyData.implementation_roadmap.timeline.key_milestones && strategyData.implementation_roadmap.timeline.key_milestones.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600, mb: 1 }}>
|
||||
Key Milestones:
|
||||
</Typography>
|
||||
<List dense>
|
||||
{strategyData.implementation_roadmap.timeline.key_milestones.map((milestone: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.success,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={milestone}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Implementation Phases */}
|
||||
{strategyData.implementation_roadmap.phases && strategyData.implementation_roadmap.phases.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Implementation Phases ({strategyData.implementation_roadmap.phases.length})
|
||||
</Typography>
|
||||
|
||||
{strategyData.implementation_roadmap.phases.map((phase: any, index: number) => (
|
||||
<Accordion key={index} defaultExpanded={false} sx={accordionStyles.accordion}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
|
||||
sx={accordionStyles.accordionSummary}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Box sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1rem',
|
||||
fontWeight: 600
|
||||
}}>
|
||||
{index + 1}
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
|
||||
{phase.name || `Phase ${index + 1}`}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
|
||||
{phase.duration || '2 months'} • {phase.tasks?.length || 0} tasks • {phase.milestones?.length || 0} milestones
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 0 }}>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
{/* Phase Description */}
|
||||
{phase.description && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{phase.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Tasks */}
|
||||
{phase.tasks && phase.tasks.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 1 }}>
|
||||
Tasks ({phase.tasks.length})
|
||||
</Typography>
|
||||
<List dense>
|
||||
{phase.tasks.map((task: string, taskIndex: number) => (
|
||||
<ListItem key={taskIndex} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.primary,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={task}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Milestones */}
|
||||
{phase.milestones && phase.milestones.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
|
||||
Milestones ({phase.milestones.length})
|
||||
</Typography>
|
||||
<List dense>
|
||||
{phase.milestones.map((milestone: string, milestoneIndex: number) => (
|
||||
<ListItem key={milestoneIndex} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={milestone}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Resources */}
|
||||
{phase.resources && phase.resources.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontWeight: 600, mb: 1 }}>
|
||||
Resources ({phase.resources.length})
|
||||
</Typography>
|
||||
<List dense>
|
||||
{phase.resources.map((resource: string, resourceIndex: number) => (
|
||||
<ListItem key={resourceIndex} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.warning,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={resource}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Resource Allocation */}
|
||||
{strategyData.implementation_roadmap.resource_allocation && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Resource Allocation
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
{/* Team Members */}
|
||||
{strategyData.implementation_roadmap.resource_allocation.team_members && strategyData.implementation_roadmap.resource_allocation.team_members.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 1 }}>
|
||||
Team Members ({strategyData.implementation_roadmap.resource_allocation.team_members.length})
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{strategyData.implementation_roadmap.resource_allocation.team_members.map((member: string, index: number) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={member}
|
||||
size="small"
|
||||
icon={<GroupIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Budget Allocation */}
|
||||
{strategyData.implementation_roadmap.resource_allocation.budget_allocation && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
|
||||
Budget Allocation
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{formatBudgetAllocation(strategyData.implementation_roadmap.resource_allocation.budget_allocation)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Success Metrics */}
|
||||
{strategyData.implementation_roadmap.success_metrics && strategyData.implementation_roadmap.success_metrics.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Success Metrics ({strategyData.implementation_roadmap.success_metrics.length})
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{strategyData.implementation_roadmap.success_metrics.map((metric: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.success,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={metric}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImplementationRoadmapCard;
|
||||
@@ -0,0 +1,539 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ShowChart as ShowChartIcon,
|
||||
TrendingUp as TrendingUpIcon,
|
||||
Timeline as TimelineIcon,
|
||||
Assessment as AssessmentIcon
|
||||
} from '@mui/icons-material';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import {
|
||||
ANALYSIS_CARD_STYLES,
|
||||
getSectionStyles,
|
||||
getEnhancedChipStyles,
|
||||
getListItemStyles
|
||||
} from '../styles';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
|
||||
interface PerformancePredictionsCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
}
|
||||
|
||||
const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({ strategyData }) => {
|
||||
// Get style objects
|
||||
const sectionStyles = getSectionStyles();
|
||||
const listItemStyles = getListItemStyles();
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// Summary content - always visible
|
||||
const summaryContent = (
|
||||
<Box>
|
||||
{/* ROI Summary */}
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
alignItems: { xs: 'flex-start', sm: 'center' },
|
||||
justifyContent: 'space-between',
|
||||
mb: 2,
|
||||
gap: 2,
|
||||
width: '100%'
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.accent} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600,
|
||||
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.success}30`,
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{strategyData.performance_predictions.roi_predictions?.estimated_roi || '25%'}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
minWidth: 0,
|
||||
flex: 1,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Typography variant="h6" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.primary,
|
||||
fontWeight: 600,
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1.2,
|
||||
mb: 0.5,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
ROI Predictions
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.7rem',
|
||||
lineHeight: 1.2,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Expected return on investment
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: { xs: 'flex-start', sm: 'flex-end' },
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<Chip
|
||||
label={`${(strategyData.performance_predictions as any)?.success_probability || '85%'} Success`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`${(strategyData.performance_predictions as any)?.implementation_timeline || '6 months'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* ROI Description - Full width container */}
|
||||
<Box sx={{ mt: 2, width: '100%' }}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.5,
|
||||
wordBreak: 'break-word',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
ROI of {strategyData.performance_predictions.roi_predictions?.estimated_roi || '300-350%'} is achievable leveraging the strong cost-per-lead to lifetime-value ratio.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Key Metrics Preview */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.primary,
|
||||
mb: 1.5,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.85rem'
|
||||
}}>
|
||||
Key Metrics Preview
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
justifyContent: 'flex-start'
|
||||
}}>
|
||||
<Chip
|
||||
label="Traffic Growth"
|
||||
size="small"
|
||||
icon={<TrendingUpIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="Engagement"
|
||||
size="small"
|
||||
icon={<AssessmentIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="Conversion"
|
||||
size="small"
|
||||
icon={<ShowChartIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.accent).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
// Detailed content - shown on expansion
|
||||
const detailedContent = (
|
||||
<Box>
|
||||
{/* ROI Summary */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
ROI Summary
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.85rem',
|
||||
lineHeight: 1.5,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Estimated ROI: {strategyData.performance_predictions.roi_predictions?.estimated_roi || '25%'}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.85rem',
|
||||
lineHeight: 1.5,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Success Probability: {(strategyData.performance_predictions as any)?.success_probability || '85%'}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.85rem',
|
||||
lineHeight: 1.5,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Implementation Timeline: {(strategyData.performance_predictions as any)?.implementation_timeline || '6 months'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Key Metrics */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Key Metrics
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', sm: 'repeat(auto-fit, minmax(200px, 1fr))' },
|
||||
gap: 2
|
||||
}}>
|
||||
{/* Traffic Predictions */}
|
||||
{strategyData.performance_predictions.traffic_predictions && (
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.success}`,
|
||||
borderRadius: 2,
|
||||
background: `rgba(76, 175, 80, 0.1)`,
|
||||
minHeight: 80,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.success,
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.2,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Traffic Growth
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.7rem',
|
||||
lineHeight: 1.3,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{strategyData.performance_predictions.traffic_predictions.growth_rate || '150% increase'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Engagement Predictions */}
|
||||
{strategyData.performance_predictions.engagement_predictions && (
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.secondary}`,
|
||||
borderRadius: 2,
|
||||
background: `rgba(118, 75, 162, 0.1)`,
|
||||
minHeight: 80,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.secondary,
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.2,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Engagement Rate
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.7rem',
|
||||
lineHeight: 1.3,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{strategyData.performance_predictions.engagement_predictions.engagement_rate || '45% improvement'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Conversion Predictions */}
|
||||
{strategyData.performance_predictions.conversion_predictions && (
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.accent}`,
|
||||
borderRadius: 2,
|
||||
background: `rgba(240, 147, 251, 0.1)`,
|
||||
minHeight: 80,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.accent,
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.2,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
Conversion Rate
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.7rem',
|
||||
lineHeight: 1.3,
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{strategyData.performance_predictions.conversion_predictions.conversion_rate || '3.2% to 5.8%'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 2, opacity: 0.2, borderColor: ANALYSIS_CARD_STYLES.colors.border.secondary }} />
|
||||
|
||||
{/* Detailed Predictions */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Detailed Predictions
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{/* Traffic Predictions */}
|
||||
{strategyData.performance_predictions.traffic_predictions && (
|
||||
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.success,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Traffic Growth"
|
||||
secondary={`${strategyData.performance_predictions.traffic_predictions.growth_rate || '150% increase'} in organic visitors`}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 0.5 }
|
||||
}}
|
||||
secondaryTypographyProps={{
|
||||
variant: 'caption',
|
||||
fontSize: '0.75rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
{/* Engagement Predictions */}
|
||||
{strategyData.performance_predictions.engagement_predictions && (
|
||||
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.secondary,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Engagement Rate"
|
||||
secondary={`${strategyData.performance_predictions.engagement_predictions.engagement_rate || '45% improvement'} in user interaction`}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.secondary, fontWeight: 600, mb: 0.5 }
|
||||
}}
|
||||
secondaryTypographyProps={{
|
||||
variant: 'caption',
|
||||
fontSize: '0.75rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
{/* Conversion Predictions */}
|
||||
{strategyData.performance_predictions.conversion_predictions && (
|
||||
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.accent,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Conversion Rate"
|
||||
secondary={`${strategyData.performance_predictions.conversion_predictions.conversion_rate || '3.2% to 5.8%'} improvement`}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.accent, fontWeight: 600, mb: 0.5 }
|
||||
}}
|
||||
secondaryTypographyProps={{
|
||||
variant: 'caption',
|
||||
fontSize: '0.75rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
{/* ROI Predictions */}
|
||||
{strategyData.performance_predictions.roi_predictions && (
|
||||
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.success,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Revenue Impact"
|
||||
secondary={`${(strategyData.performance_predictions.roi_predictions as any)?.revenue_impact || '$50K'} additional monthly revenue`}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 0.5 }
|
||||
}}
|
||||
secondaryTypographyProps={{
|
||||
variant: 'caption',
|
||||
fontSize: '0.75rem',
|
||||
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Timeline Projections */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Timeline Projections
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.5
|
||||
}}>
|
||||
• Month 1-2: Initial setup and foundation building
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.5
|
||||
}}>
|
||||
• Month 3-4: Content creation and optimization
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.5
|
||||
}}>
|
||||
• Month 5-6: Scaling and performance optimization
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.5
|
||||
}}>
|
||||
• Ongoing: Continuous monitoring and improvement
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default PerformancePredictionsCard;
|
||||
@@ -0,0 +1,259 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Fade,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
ExpandLess as ExpandLessIcon,
|
||||
KeyboardArrowDown as ArrowDownIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
ANALYSIS_CARD_STYLES,
|
||||
getAnalysisCardStyles,
|
||||
getEnhancedChipStyles
|
||||
} from '../styles';
|
||||
|
||||
interface ProgressiveCardProps {
|
||||
summary: React.ReactNode;
|
||||
details: React.ReactNode;
|
||||
trigger?: 'hover' | 'click';
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
icon?: React.ReactNode;
|
||||
autoCollapseDelay?: number; // milliseconds
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
|
||||
summary,
|
||||
details,
|
||||
trigger = 'click',
|
||||
title,
|
||||
subtitle,
|
||||
icon,
|
||||
autoCollapseDelay = 3000, // 3 seconds default
|
||||
className
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const cardStyles = getAnalysisCardStyles();
|
||||
|
||||
// Handle hover interactions
|
||||
const handleMouseEnter = () => {
|
||||
if (trigger === 'hover') {
|
||||
// Clear any existing timeout
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
hoverTimeoutRef.current = null;
|
||||
}
|
||||
setIsExpanded(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (trigger === 'hover') {
|
||||
// Set timeout to auto-collapse
|
||||
hoverTimeoutRef.current = setTimeout(() => {
|
||||
setIsExpanded(false);
|
||||
hoverTimeoutRef.current = null;
|
||||
}, autoCollapseDelay);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle click interactions
|
||||
const handleToggle = () => {
|
||||
if (trigger === 'click') {
|
||||
setIsExpanded(!isExpanded);
|
||||
}
|
||||
};
|
||||
|
||||
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 }}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressiveCard;
|
||||
@@ -0,0 +1,413 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Box,
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Lightbulb as LightbulbIcon,
|
||||
TrendingUp as TrendingUpIcon,
|
||||
Security as SecurityIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
Assessment as AssessmentIcon
|
||||
} from '@mui/icons-material';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
import { ANALYSIS_CARD_STYLES, getEnhancedChipStyles } from '../styles';
|
||||
|
||||
const ProgressiveDemo: React.FC = () => {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography variant="h4" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.primary,
|
||||
mb: 3,
|
||||
textAlign: 'center',
|
||||
fontWeight: 600
|
||||
}}>
|
||||
Progressive Disclosure Demo
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
mb: 4,
|
||||
textAlign: 'center',
|
||||
maxWidth: 800,
|
||||
mx: 'auto'
|
||||
}}>
|
||||
Experience the new progressive disclosure system. Cards show a summary initially,
|
||||
with detailed content revealed through user interaction.
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{/* Click Trigger Example */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<ProgressiveCard
|
||||
title="Strategic Insights"
|
||||
subtitle="Click to expand"
|
||||
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
|
||||
summary={
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
mb: 2
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.info} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600
|
||||
}}>
|
||||
85%
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Market Analysis
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
Strong positioning identified
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label="High Growth"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="6 months"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
<Chip label="Market" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip} />
|
||||
<Chip label="Consumer" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip} />
|
||||
<Chip label="Business" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.accent).chip} />
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
details={
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Detailed Strategic Insights
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
background: ANALYSIS_CARD_STYLES.colors.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Market positioning analysis reveals strong competitive advantages
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Consumer behavior patterns indicate high engagement potential
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Business model optimization opportunities identified
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
|
||||
• Revenue growth projections show 25% increase potential
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
trigger="click"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Hover Trigger Example */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<ProgressiveCard
|
||||
title="Performance Predictions"
|
||||
subtitle="Hover to expand"
|
||||
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
|
||||
summary={
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
mb: 2
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.warning} 0%, ${ANALYSIS_CARD_STYLES.colors.error} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600
|
||||
}}>
|
||||
92%
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
ROI Predictions
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
High success probability
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label="25% ROI"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="6 months"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
<Chip label="Traffic" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip} />
|
||||
<Chip label="Engagement" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip} />
|
||||
<Chip label="Conversion" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.accent).chip} />
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
details={
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Detailed Performance Metrics
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
background: ANALYSIS_CARD_STYLES.colors.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Traffic growth: 150% increase in organic visitors
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Engagement rate: 45% improvement in user interaction
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Conversion rate: 3.2% to 5.8% improvement
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
|
||||
• Revenue impact: $50K additional monthly revenue
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
trigger="hover"
|
||||
autoCollapseDelay={5000}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Risk Assessment Example */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<ProgressiveCard
|
||||
title="Risk Assessment"
|
||||
subtitle="Click to expand"
|
||||
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
|
||||
summary={
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
mb: 2
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.error} 0%, ${ANALYSIS_CARD_STYLES.colors.warning} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600
|
||||
}}>
|
||||
Med
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Risk Level
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
Medium risk identified
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label="7 Risks"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="Mitigated"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
<Chip label="Technical" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip} />
|
||||
<Chip label="Market" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip} />
|
||||
<Chip label="Operational" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip} />
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
details={
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Risk Categories & Mitigation
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
background: ANALYSIS_CARD_STYLES.colors.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Technical Risks: Infrastructure scalability challenges
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Market Risks: Competitive pressure and market saturation
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Operational Risks: Resource allocation and team scaling
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
|
||||
• Mitigation: Comprehensive monitoring and contingency plans
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
trigger="click"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Implementation Roadmap Example */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<ProgressiveCard
|
||||
title="Implementation Roadmap"
|
||||
subtitle="Hover to expand"
|
||||
icon={<ScheduleIcon sx={{ color: 'white', fontSize: 20 }} />}
|
||||
summary={
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
mb: 2
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.info} 0%, ${ANALYSIS_CARD_STYLES.colors.primary} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600
|
||||
}}>
|
||||
6M
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Timeline
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
6-month implementation
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label="4 Phases"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="5 Team"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
<Chip label="Phase 1" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip} />
|
||||
<Chip label="Phase 2" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip} />
|
||||
<Chip label="Phase 3" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip} />
|
||||
<Chip label="Phase 4" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip} />
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
details={
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Implementation Phases
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
background: ANALYSIS_CARD_STYLES.colors.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Phase 1 (Months 1-2): Foundation and setup
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Phase 2 (Months 3-4): Core implementation
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
|
||||
• Phase 3 (Months 5-6): Optimization and scaling
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
|
||||
• Phase 4 (Ongoing): Monitoring and maintenance
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
trigger="hover"
|
||||
autoCollapseDelay={4000}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressiveDemo;
|
||||
@@ -0,0 +1,404 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Security as SecurityIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
Warning as WarningIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Error as ErrorIcon,
|
||||
Info as InfoIcon
|
||||
} from '@mui/icons-material';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import {
|
||||
ANALYSIS_CARD_STYLES,
|
||||
getSectionStyles,
|
||||
getAccordionStyles,
|
||||
getEnhancedChipStyles,
|
||||
getListItemStyles
|
||||
} from '../styles';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
|
||||
interface RiskAssessmentCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
}
|
||||
|
||||
const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData }) => {
|
||||
// Get style objects
|
||||
const sectionStyles = getSectionStyles();
|
||||
const accordionStyles = getAccordionStyles();
|
||||
const listItemStyles = getListItemStyles();
|
||||
|
||||
// Helper function to get risk level color
|
||||
const getRiskLevelColor = (level: string) => {
|
||||
const lowerLevel = level.toLowerCase();
|
||||
if (lowerLevel.includes('high')) return ANALYSIS_CARD_STYLES.colors.error;
|
||||
if (lowerLevel.includes('medium')) return ANALYSIS_CARD_STYLES.colors.warning;
|
||||
if (lowerLevel.includes('low')) return ANALYSIS_CARD_STYLES.colors.success;
|
||||
return ANALYSIS_CARD_STYLES.colors.info;
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// Summary content - always visible
|
||||
const summaryContent = (
|
||||
<Box>
|
||||
{/* Risk Overview */}
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${getRiskLevelColor(strategyData.risk_assessment.overall_risk_level || 'Medium')} 0%, ${ANALYSIS_CARD_STYLES.colors.warning} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600,
|
||||
boxShadow: `0 4px 12px ${getRiskLevelColor(strategyData.risk_assessment.overall_risk_level || 'Medium')}30`
|
||||
}}>
|
||||
{strategyData.risk_assessment.risks?.length || 0}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Risk Level
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
{strategyData.risk_assessment.overall_risk_level || 'Medium'} overall risk
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label={`${strategyData.risk_assessment.risks?.length || 0} Risks`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`${strategyData.risk_assessment.mitigation_strategies?.length || 0} Mitigations`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Risk Categories Preview */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
|
||||
Risk Categories
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{strategyData.risk_assessment.risk_categories && Object.keys(strategyData.risk_assessment.risk_categories).slice(0, 4).map((category: string) => (
|
||||
<Chip
|
||||
key={category}
|
||||
label={category.replace(/_/g, ' ')}
|
||||
size="small"
|
||||
icon={<WarningIcon />}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip}
|
||||
/>
|
||||
))}
|
||||
{strategyData.risk_assessment.risk_categories && Object.keys(strategyData.risk_assessment.risk_categories).length > 4 && (
|
||||
<Chip
|
||||
label={`+${Object.keys(strategyData.risk_assessment.risk_categories).length - 4} more`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
// Detailed content - shown on expansion
|
||||
const detailedContent = (
|
||||
<Box>
|
||||
{/* Overall Risk Assessment */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Overall Risk Assessment
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Chip
|
||||
label={strategyData.risk_assessment.overall_risk_level || 'Medium'}
|
||||
size="medium"
|
||||
icon={<SecurityIcon />}
|
||||
sx={getEnhancedChipStyles(getRiskLevelColor(strategyData.risk_assessment.overall_risk_level || 'Medium')).chip}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
{strategyData.risk_assessment.risks?.length || 0} identified risks with {strategyData.risk_assessment.mitigation_strategies?.length || 0} mitigation strategies
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Individual Risks */}
|
||||
{strategyData.risk_assessment.risks && strategyData.risk_assessment.risks.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Individual Risks ({strategyData.risk_assessment.risks.length})
|
||||
</Typography>
|
||||
|
||||
{strategyData.risk_assessment.risks.map((risk: any, index: number) => (
|
||||
<Accordion key={index} defaultExpanded={false} sx={accordionStyles.accordion}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
|
||||
sx={accordionStyles.accordionSummary}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Box sx={{ mr: 1.5 }}>
|
||||
<ErrorIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.error, fontSize: 20 }} />
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
|
||||
{typeof risk === 'string' ? risk : risk.risk || 'Risk'}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
|
||||
{typeof risk === 'object' && risk.probability ? `${risk.probability} probability, ${risk.impact} impact` : 'Risk assessment'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 0 }}>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
{typeof risk === 'object' && (
|
||||
<>
|
||||
{/* Risk Details */}
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem', mb: 1 }}>
|
||||
{risk.risk}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 1 }}>
|
||||
<Chip
|
||||
label={`Probability: ${risk.probability || 'Unknown'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
risk.probability === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
|
||||
risk.probability === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
|
||||
ANALYSIS_CARD_STYLES.colors.success
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`Impact: ${risk.impact || 'Unknown'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
risk.impact === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
|
||||
risk.impact === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
|
||||
ANALYSIS_CARD_STYLES.colors.success
|
||||
).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Mitigation Strategy */}
|
||||
{risk.mitigation && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
|
||||
Mitigation Strategy
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{risk.mitigation}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Contingency Plan */}
|
||||
{risk.contingency && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontWeight: 600, mb: 1 }}>
|
||||
Contingency Plan
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{risk.contingency}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{typeof risk === 'string' && (
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{risk}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Mitigation Strategies */}
|
||||
{strategyData.risk_assessment.mitigation_strategies && strategyData.risk_assessment.mitigation_strategies.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Mitigation Strategies ({strategyData.risk_assessment.mitigation_strategies.length})
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{strategyData.risk_assessment.mitigation_strategies.map((strategy: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={strategy}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Risk Categories */}
|
||||
{strategyData.risk_assessment.risk_categories && Object.keys(strategyData.risk_assessment.risk_categories).length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Risk Categories
|
||||
</Typography>
|
||||
|
||||
{Object.entries(strategyData.risk_assessment.risk_categories).map(([category, risks]: [string, any]) => (
|
||||
<Accordion key={category} defaultExpanded={false} sx={accordionStyles.accordion}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
|
||||
sx={accordionStyles.accordionSummary}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Box sx={{ mr: 1.5 }}>
|
||||
<WarningIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontSize: 20 }} />
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
|
||||
{category.replace(/_/g, ' ')}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
|
||||
{Array.isArray(risks) ? `${risks.length} risks` : 'Risk category'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 0 }}>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
{Array.isArray(risks) ? (
|
||||
<List dense>
|
||||
{risks.map((risk: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.warning,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={risk}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{typeof risks === 'string' ? risks : 'Risk category details'}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Monitoring Framework */}
|
||||
{strategyData.risk_assessment.monitoring_framework && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Monitoring Framework
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
{Object.entries(strategyData.risk_assessment.monitoring_framework).map(([key, value]: [string, any]) => (
|
||||
<Box key={key} sx={{ mb: 1 }}>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 0.5 }}>
|
||||
{key.replace(/_/g, ' ')}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
|
||||
{typeof value === 'string' ? value : JSON.stringify(value)}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default RiskAssessmentCard;
|
||||
@@ -0,0 +1,330 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Lightbulb as LightbulbIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
TrendingUp as TrendingUpIcon,
|
||||
Psychology as PsychologyIcon,
|
||||
Business as BusinessIcon,
|
||||
Analytics as AnalyticsIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import {
|
||||
ANALYSIS_CARD_STYLES,
|
||||
getSectionStyles,
|
||||
getAccordionStyles,
|
||||
getEnhancedChipStyles,
|
||||
getListItemStyles,
|
||||
getAnimationStyles
|
||||
} from '../styles';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
|
||||
interface StrategicInsightsCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
}
|
||||
|
||||
const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyData }) => {
|
||||
// Get style objects
|
||||
const sectionStyles = getSectionStyles();
|
||||
const accordionStyles = getAccordionStyles();
|
||||
const listItemStyles = getListItemStyles();
|
||||
const animationStyles = getAnimationStyles();
|
||||
|
||||
console.log('🔍 StrategicInsightsCard - strategyData:', strategyData);
|
||||
console.log('🔍 StrategicInsightsCard - strategic_insights:', strategyData?.strategic_insights);
|
||||
|
||||
if (!strategyData?.strategic_insights) {
|
||||
return (
|
||||
<Grid item xs={12} lg={6}>
|
||||
<ProgressiveCard
|
||||
title="Strategic Insights"
|
||||
subtitle="AI-powered market analysis"
|
||||
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
|
||||
summary={
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
Strategic insights 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="click"
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to get insight icon
|
||||
const getInsightIcon = (type: string) => {
|
||||
switch (type?.toLowerCase()) {
|
||||
case 'market': return <TrendingUpIcon />;
|
||||
case 'consumer': return <PsychologyIcon />;
|
||||
case 'business': return <BusinessIcon />;
|
||||
case 'analytics': return <AnalyticsIcon />;
|
||||
default: return <LightbulbIcon />;
|
||||
}
|
||||
};
|
||||
|
||||
// Summary content - always visible
|
||||
const summaryContent = (
|
||||
<Box>
|
||||
{/* Market Analysis Summary */}
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.info} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mr: 2,
|
||||
color: 'white',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 600,
|
||||
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.success}30`
|
||||
}}>
|
||||
85%
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
|
||||
Market Analysis
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
|
||||
Strong market positioning identified
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Chip
|
||||
label="High Growth"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
|
||||
/>
|
||||
<Chip
|
||||
label="6 months"
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Key Insights Preview */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
|
||||
Key Insights Preview
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{strategyData.strategic_insights.insights?.slice(0, 3).map((insight: any, index: number) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={insight.type}
|
||||
size="small"
|
||||
icon={getInsightIcon(insight.type)}
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
|
||||
/>
|
||||
))}
|
||||
{(strategyData.strategic_insights.insights?.length || 0) > 3 && (
|
||||
<Chip
|
||||
label={`+${(strategyData.strategic_insights.insights?.length || 0) - 3} more`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
// Detailed content - shown on expansion
|
||||
const detailedContent = (
|
||||
<Box>
|
||||
{/* Strategic Insights by Type */}
|
||||
{strategyData.strategic_insights.insights && strategyData.strategic_insights.insights.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Detailed Strategic Insights ({strategyData.strategic_insights.insights.length})
|
||||
</Typography>
|
||||
|
||||
{/* Group insights by type */}
|
||||
{Object.entries(
|
||||
strategyData.strategic_insights.insights.reduce((acc: any, insight: any) => {
|
||||
const type = insight.type || 'General';
|
||||
if (!acc[type]) acc[type] = [];
|
||||
acc[type].push(insight);
|
||||
return acc;
|
||||
}, {})
|
||||
).map(([type, insights]: [string, any]) => (
|
||||
<Accordion key={type} defaultExpanded={false} sx={accordionStyles.accordion}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
|
||||
sx={accordionStyles.accordionSummary}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ mr: 1.5 }}>
|
||||
{getInsightIcon(type)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
|
||||
{type} Insights ({insights.length})
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
|
||||
{insights.length} strategic {insights.length === 1 ? 'insight' : 'insights'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 0 }}>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{insights.map((insight: any, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.primary,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={insight.insight}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
secondary={
|
||||
<Box sx={{ mt: 0.5 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 0.5 }}>
|
||||
<Chip
|
||||
label={`P: ${insight.priority || 'Medium'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
insight.priority === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
|
||||
insight.priority === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
|
||||
ANALYSIS_CARD_STYLES.colors.success
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`I: ${insight.estimated_impact || 'Medium'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
insight.estimated_impact === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
|
||||
insight.estimated_impact === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
|
||||
ANALYSIS_CARD_STYLES.colors.success
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`C: ${insight.confidence_level || 'Medium'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
insight.confidence_level === 'High' ? ANALYSIS_CARD_STYLES.colors.success :
|
||||
insight.confidence_level === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
|
||||
ANALYSIS_CARD_STYLES.colors.error
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`T: ${insight.implementation_time || '3 months'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
</Box>
|
||||
{insight.reasoning && (
|
||||
<Typography variant="caption" sx={{
|
||||
display: 'block',
|
||||
fontSize: '0.75rem',
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontStyle: 'italic'
|
||||
}}>
|
||||
<strong>Reasoning:</strong> {insight.reasoning}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Content Opportunities */}
|
||||
{strategyData.strategic_insights.content_opportunities && strategyData.strategic_insights.content_opportunities.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
|
||||
Content Opportunities ({strategyData.strategic_insights.content_opportunities.length})
|
||||
</Typography>
|
||||
<Box sx={sectionStyles.sectionContainer}>
|
||||
<List dense>
|
||||
{strategyData.strategic_insights.content_opportunities.map((opportunity: string, index: number) => (
|
||||
<ListItem key={index} sx={listItemStyles.listItem}>
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<Box sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_CARD_STYLES.colors.success,
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default StrategicInsightsCard;
|
||||
@@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Alert
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Check as CheckIcon,
|
||||
CalendarToday as CalendarIcon,
|
||||
Refresh as RefreshIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
|
||||
interface StrategyActionsProps {
|
||||
strategyData: StrategyData | null;
|
||||
strategyConfirmed: boolean;
|
||||
onConfirmStrategy: () => void;
|
||||
onGenerateContentCalendar: () => void;
|
||||
onRefreshData: () => void;
|
||||
}
|
||||
|
||||
const StrategyActions: React.FC<StrategyActionsProps> = ({
|
||||
strategyData,
|
||||
strategyConfirmed,
|
||||
onConfirmStrategy,
|
||||
onGenerateContentCalendar,
|
||||
onRefreshData
|
||||
}) => {
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
{/* Strategy Confirmation Status */}
|
||||
{strategyConfirmed && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{
|
||||
mb: 3,
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.2)',
|
||||
border: '1px solid rgba(76, 175, 80, 0.3)'
|
||||
}}
|
||||
action={
|
||||
<Button
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={onGenerateContentCalendar}
|
||||
startIcon={<CalendarIcon />}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
Generate Content Calendar
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
Strategy confirmed! You can now generate a content calendar based on this strategy.
|
||||
</Alert>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
|
||||
{!strategyConfirmed ? (
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={onConfirmStrategy}
|
||||
startIcon={<CheckIcon />}
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
borderRadius: 3,
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
fontWeight: 600,
|
||||
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)',
|
||||
boxShadow: '0 12px 40px rgba(102, 126, 234, 0.4)',
|
||||
transform: 'translateY(-2px)'
|
||||
},
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
}}
|
||||
>
|
||||
Confirm Strategy
|
||||
</Button>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={onGenerateContentCalendar}
|
||||
startIcon={<CalendarIcon />}
|
||||
color="success"
|
||||
sx={{
|
||||
borderRadius: 3,
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
fontWeight: 600,
|
||||
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
|
||||
'&:hover': {
|
||||
boxShadow: '0 12px 40px rgba(76, 175, 80, 0.4)',
|
||||
transform: 'translateY(-2px)'
|
||||
},
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
}}
|
||||
>
|
||||
Generate Content Calendar
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="large"
|
||||
onClick={onRefreshData}
|
||||
startIcon={<RefreshIcon />}
|
||||
sx={{
|
||||
borderRadius: 3,
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
fontWeight: 600,
|
||||
borderColor: 'rgba(102, 126, 234, 0.3)',
|
||||
color: '#667eea',
|
||||
'&:hover': {
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.05)',
|
||||
transform: 'translateY(-2px)'
|
||||
},
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
}}
|
||||
>
|
||||
Refresh Data
|
||||
</Button>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StrategyActions;
|
||||
@@ -0,0 +1,619 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
Grid,
|
||||
Typography,
|
||||
Chip,
|
||||
Box,
|
||||
Tooltip,
|
||||
LinearProgress,
|
||||
Badge,
|
||||
Card,
|
||||
CardContent,
|
||||
CircularProgress,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Psychology as PsychologyIcon,
|
||||
TrendingUp as TrendingUpIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Warning as WarningIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
CalendarToday as CalendarTodayIcon,
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
Timeline as TimelineIcon,
|
||||
Info as InfoIcon,
|
||||
Analytics as AnalyticsIcon,
|
||||
Assessment as AssessmentIcon,
|
||||
Business as BusinessIcon,
|
||||
ShowChart as ShowChartIcon,
|
||||
Security as SecurityIcon,
|
||||
ArrowForward as ArrowForwardIcon,
|
||||
Star as StarIcon,
|
||||
DataUsage as DataUsageIcon,
|
||||
Input as InputIcon,
|
||||
Storage as StorageIcon,
|
||||
Person as PersonIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import { getStrategyName, getStrategyGenerationDate } from '../utils/strategyTransformers';
|
||||
|
||||
interface StrategyHeaderProps {
|
||||
strategyData: StrategyData | null;
|
||||
strategyConfirmed: boolean;
|
||||
}
|
||||
|
||||
const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyConfirmed }) => {
|
||||
const [showNextStepText, setShowNextStepText] = useState(false);
|
||||
|
||||
if (!strategyData) return null;
|
||||
|
||||
// Helper function to get percentage value from string
|
||||
const getPercentageValue = (value: string | undefined) => {
|
||||
if (!value) return 0;
|
||||
const match = value.match(/(\d+)/);
|
||||
return match ? parseInt(match[1]) : 0;
|
||||
};
|
||||
|
||||
// Helper function to get risk color
|
||||
const getRiskColor = (riskLevel: string | undefined) => {
|
||||
switch (riskLevel?.toLowerCase()) {
|
||||
case 'low': return '#4caf50';
|
||||
case 'medium': return '#ff9800';
|
||||
case 'high': return '#f44336';
|
||||
case 'high-medium': return '#ff5722';
|
||||
default: return '#ff9800';
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to extract company name from strategy name
|
||||
const getCompanyName = (strategyName: string) => {
|
||||
// Extract company name from strategy name (e.g., "Enhanced Content Strategy" -> "ALwrity")
|
||||
// For now, we'll use a default company name
|
||||
return "ALwrity";
|
||||
};
|
||||
|
||||
// Helper function to get timeline percentage
|
||||
const getTimelinePercentage = (timeline: string | undefined) => {
|
||||
if (!timeline) return 0;
|
||||
const match = timeline.match(/(\d+)/);
|
||||
return match ? Math.min(parseInt(match[1]) * 10, 100) : 0; // Convert months to percentage
|
||||
};
|
||||
|
||||
const roiValue = getPercentageValue(strategyData.summary?.estimated_roi);
|
||||
const successValue = getPercentageValue(strategyData.summary?.success_probability);
|
||||
const timelineValue = getTimelinePercentage(strategyData.summary?.implementation_timeline);
|
||||
const riskColor = getRiskColor(strategyData.summary?.risk_level);
|
||||
const companyName = getCompanyName(getStrategyName(strategyData));
|
||||
|
||||
// Analysis components data
|
||||
const analysisComponents = [
|
||||
{ name: 'Strategic Insights', icon: <AnalyticsIcon />, color: '#667eea' },
|
||||
{ name: 'Competitive Analysis', icon: <BusinessIcon />, color: '#4caf50' },
|
||||
{ name: 'Performance Predictions', icon: <ShowChartIcon />, color: '#2196f3' },
|
||||
{ name: 'Implementation Roadmap', icon: <TimelineIcon />, color: '#ff9800' },
|
||||
{ name: 'Risk Assessment', icon: <SecurityIcon />, color: '#f44336' }
|
||||
];
|
||||
|
||||
// Mock data sources and user inputs (replace with actual data from strategyData)
|
||||
const dataSources = [
|
||||
{ name: 'Industry Analysis', type: 'Market Research', icon: <DataUsageIcon /> },
|
||||
{ name: 'Competitor Data', type: 'External Sources', icon: <StorageIcon /> },
|
||||
{ name: 'User Preferences', type: 'Input Survey', icon: <PersonIcon /> },
|
||||
{ name: 'Content Performance', type: 'Analytics', icon: <InputIcon /> }
|
||||
];
|
||||
|
||||
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%)' }
|
||||
},
|
||||
'@keyframes gradient': {
|
||||
'0%': { backgroundPosition: '0% 50%' },
|
||||
'50%': { backgroundPosition: '100% 50%' },
|
||||
'100%': { backgroundPosition: '0% 50%' }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* 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.5 }}>
|
||||
{/* Header Section - More Compact */}
|
||||
<Grid container spacing={2} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 0.5 }}>
|
||||
<motion.div
|
||||
animate={{ rotate: [0, 10, -10, 0] }}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
||||
>
|
||||
<PsychologyIcon sx={{ fontSize: 28, color: '#667eea', mr: 1.5 }} />
|
||||
</motion.div>
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{
|
||||
fontWeight: 800,
|
||||
background: 'linear-gradient(45deg, #667eea, #764ba2, #f093fb)',
|
||||
backgroundSize: '200% 200%',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
animation: 'gradient 3s ease infinite',
|
||||
mb: 0.25
|
||||
}}>
|
||||
{companyName} Content Strategy
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
opacity: 0.8,
|
||||
color: '#e0e0e0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
fontSize: '0.75rem'
|
||||
}}>
|
||||
<ScheduleIcon sx={{ fontSize: 12 }} />
|
||||
Generated on {getStrategyGenerationDate(strategyData)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Strategy Metadata Chips - More Compact */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75, mb: 1 }}>
|
||||
<Tooltip title="AI-Generated Strategy using advanced machine learning" arrow>
|
||||
<Badge badgeContent={<CheckCircleIcon sx={{ fontSize: 10, color: '#4caf50' }} />} color="default">
|
||||
<Chip
|
||||
icon={<AutoAwesomeIcon />}
|
||||
label="AI Generated"
|
||||
size="small"
|
||||
sx={{
|
||||
background: 'rgba(102, 126, 234, 0.2)',
|
||||
color: '#667eea',
|
||||
border: '1px solid rgba(102, 126, 234, 0.3)',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 24
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
<Tooltip title="Comprehensive analysis covering all strategic aspects" arrow>
|
||||
<Badge badgeContent={<CheckCircleIcon sx={{ fontSize: 10, color: '#4caf50' }} />} color="default">
|
||||
<Chip
|
||||
icon={<PsychologyIcon />}
|
||||
label="Comprehensive"
|
||||
size="small"
|
||||
sx={{
|
||||
background: 'rgba(76, 175, 80, 0.2)',
|
||||
color: '#4caf50',
|
||||
border: '1px solid rgba(76, 175, 80, 0.3)',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 24
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
<Tooltip title="Content calendar generation status" arrow>
|
||||
<Badge badgeContent={<WarningIcon sx={{ fontSize: 10, color: '#ff9800' }} />} color="default">
|
||||
<Chip
|
||||
icon={<CalendarTodayIcon />}
|
||||
label={strategyData.strategy_metadata?.content_calendar_ready ? "Calendar Ready" : "Calendar Pending"}
|
||||
size="small"
|
||||
color={strategyData.strategy_metadata?.content_calendar_ready ? "success" : "warning"}
|
||||
sx={{
|
||||
background: strategyData.strategy_metadata?.content_calendar_ready
|
||||
? 'rgba(76, 175, 80, 0.2)'
|
||||
: 'rgba(255, 152, 0, 0.2)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 24
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Key Metrics Section with 4 Circular Progress Charts */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||
{/* ROI Circular Progress */}
|
||||
<Tooltip title="Estimated Return on Investment for content marketing efforts" arrow>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={roiValue}
|
||||
size={50}
|
||||
thickness={3}
|
||||
sx={{
|
||||
color: '#4caf50',
|
||||
'& .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: '#4caf50', fontWeight: 700, fontSize: '0.6rem' }}>
|
||||
{roiValue}%
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ color: '#4caf50', fontWeight: 600, fontSize: '0.65rem' }}>
|
||||
ROI
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
{/* Success Probability Circular Progress */}
|
||||
<Tooltip title="Probability of achieving the defined content strategy goals" arrow>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={successValue}
|
||||
size={50}
|
||||
thickness={3}
|
||||
sx={{
|
||||
color: '#2196f3',
|
||||
'& .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: '#2196f3', fontWeight: 700, fontSize: '0.6rem' }}>
|
||||
{successValue}%
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ color: '#2196f3', fontWeight: 600, fontSize: '0.65rem' }}>
|
||||
Success
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
{/* Risk Level Circular Progress */}
|
||||
<Tooltip title="Overall risk assessment for the content strategy implementation" arrow>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={strategyData.summary?.risk_level === 'Low' ? 25 :
|
||||
strategyData.summary?.risk_level === 'Medium' ? 50 :
|
||||
strategyData.summary?.risk_level === 'High' ? 75 : 60}
|
||||
size={50}
|
||||
thickness={3}
|
||||
sx={{
|
||||
color: riskColor,
|
||||
'& .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: riskColor, fontWeight: 700, fontSize: '0.6rem' }}>
|
||||
{strategyData.summary?.risk_level === 'Low' ? 'L' :
|
||||
strategyData.summary?.risk_level === 'Medium' ? 'M' :
|
||||
strategyData.summary?.risk_level === 'High' ? 'H' : 'HM'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ color: riskColor, fontWeight: 600, fontSize: '0.65rem' }}>
|
||||
Risk
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
{/* Timeline Circular Progress */}
|
||||
<Tooltip title="Implementation timeline for the content strategy" arrow>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={timelineValue}
|
||||
size={50}
|
||||
thickness={3}
|
||||
sx={{
|
||||
color: '#667eea',
|
||||
'& .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: '#667eea', fontWeight: 700, fontSize: '0.6rem' }}>
|
||||
{strategyData.summary?.implementation_timeline?.match(/\d+/)?.[0] || '6'}m
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ color: '#667eea', fontWeight: 600, fontSize: '0.65rem' }}>
|
||||
Timeline
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Strategy Status Section - Expanded to show data sources and analysis components */}
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 1.5, color: '#667eea', fontSize: '0.9rem' }}>
|
||||
Strategy Status & Data Sources
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{/* Status Info */}
|
||||
<Grid item xs={12} md={4}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<TrendingUpIcon sx={{ color: '#4caf50', fontSize: 16 }} />
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem' }}>
|
||||
Status
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'white', fontWeight: 600, fontSize: '0.75rem' }}>
|
||||
{strategyConfirmed ? 'Confirmed' : 'Pending Review'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<InfoIcon sx={{ color: '#2196f3', fontSize: 16 }} />
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem' }}>
|
||||
User ID
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'white', fontWeight: 600, fontSize: '0.75rem' }}>
|
||||
{strategyData.strategy_metadata?.user_id}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Data Sources */}
|
||||
<Grid item xs={12} md={8}>
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem', mb: 1, display: 'block' }}>
|
||||
Data Sources & User Inputs
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
|
||||
{dataSources.map((source, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
icon={source.icon}
|
||||
label={`${source.name} (${source.type})`}
|
||||
size="small"
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.3) 0%, rgba(102, 126, 234, 0.2) 100%)',
|
||||
color: '#667eea',
|
||||
border: '1px solid rgba(102, 126, 234, 0.4)',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 24,
|
||||
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.2)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.4) 0%, rgba(102, 126, 234, 0.3) 100%)',
|
||||
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Analysis Components Section - Moved inside the card */}
|
||||
<Box sx={{ mt: 2, pt: 2, borderTop: '1px solid rgba(255, 255, 255, 0.1)' }}>
|
||||
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem', mb: 1, display: 'block' }}>
|
||||
Analysis Components
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
|
||||
{analysisComponents.map((component, index) => (
|
||||
<Tooltip key={index} title={`${component.name} analysis completed`} arrow>
|
||||
<Badge badgeContent={<CheckCircleIcon sx={{ fontSize: 10, color: '#4caf50' }} />} color="default">
|
||||
<Chip
|
||||
icon={component.icon}
|
||||
label={component.name}
|
||||
size="small"
|
||||
sx={{
|
||||
background: `linear-gradient(135deg, ${component.color}30 0%, ${component.color}20 100%)`,
|
||||
color: component.color,
|
||||
border: `1px solid ${component.color}50`,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 24,
|
||||
boxShadow: `0 2px 8px ${component.color}20`,
|
||||
'&:hover': {
|
||||
background: `linear-gradient(135deg, ${component.color}40 0%, ${component.color}30 100%)`,
|
||||
boxShadow: `0 4px 12px ${component.color}30`,
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
))}
|
||||
<Chip
|
||||
icon={<StarIcon />}
|
||||
label="Ready for Review"
|
||||
size="small"
|
||||
color="success"
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, rgba(76, 175, 80, 0.3) 0%, rgba(76, 175, 80, 0.2) 100%)',
|
||||
color: '#4caf50',
|
||||
border: '1px solid rgba(76, 175, 80, 0.4)',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 24,
|
||||
boxShadow: '0 2px 8px rgba(76, 175, 80, 0.2)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, rgba(76, 175, 80, 0.4) 0%, rgba(76, 175, 80, 0.3) 100%)',
|
||||
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* 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"}
|
||||
arrow
|
||||
open={showNextStepText}
|
||||
onClose={() => setShowNextStepText(false)}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
onMouseEnter={() => setShowNextStepText(true)}
|
||||
onMouseLeave={() => setShowNextStepText(false)}
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #4caf50 0%, #66bb6a 50%, #81c784 100%)',
|
||||
color: 'white',
|
||||
fontWeight: 700,
|
||||
fontSize: '0.8rem',
|
||||
px: 3,
|
||||
py: 1,
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 4px 15px rgba(76, 175, 80, 0.4)',
|
||||
border: '2px solid rgba(76, 175, 80, 0.3)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #66bb6a 0%, #81c784 50%, #a5d6a7 100%)',
|
||||
boxShadow: '0 6px 20px rgba(76, 175, 80, 0.6)',
|
||||
transform: 'translateY(-2px)'
|
||||
},
|
||||
transition: 'all 0.3s ease',
|
||||
textTransform: 'none'
|
||||
}}
|
||||
startIcon={<ArrowForwardIcon />}
|
||||
>
|
||||
Next Step
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StrategyHeader;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Paper } from '@mui/material';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
|
||||
interface TestComponentProps {
|
||||
strategyData: StrategyData | null;
|
||||
}
|
||||
|
||||
const TestComponent: React.FC<TestComponentProps> = ({ strategyData }) => {
|
||||
if (!strategyData) return null;
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2, mb: 2, background: 'rgba(76, 175, 80, 0.1)' }}>
|
||||
<Typography variant="h6" color="success.main" gutterBottom>
|
||||
✅ Modular Structure Test
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Strategy Name: {strategyData.strategy_metadata?.strategy_name || strategyData.metadata?.strategy_name}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
ROI: {strategyData.summary?.estimated_roi}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Success Probability: {strategyData.summary?.success_probability}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="success.main">
|
||||
✅ Modular structure is working correctly!
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestComponent;
|
||||
@@ -0,0 +1,84 @@
|
||||
import { useState } from 'react';
|
||||
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
|
||||
export const useStrategyActions = () => {
|
||||
const [strategyConfirmed, setStrategyConfirmed] = useState(false);
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
|
||||
const handleConfirmStrategy = () => {
|
||||
setShowConfirmDialog(true);
|
||||
};
|
||||
|
||||
const confirmStrategy = async (strategyData: StrategyData | null) => {
|
||||
try {
|
||||
setStrategyConfirmed(true);
|
||||
setShowConfirmDialog(false);
|
||||
|
||||
// Save confirmation status to backend
|
||||
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id;
|
||||
if (userId) {
|
||||
try {
|
||||
// Update the strategy with confirmation status
|
||||
await contentPlanningApi.updateEnhancedStrategy(
|
||||
userId.toString(),
|
||||
{ confirmed: true, confirmed_at: new Date().toISOString() }
|
||||
);
|
||||
console.log('Strategy confirmation saved to backend');
|
||||
} catch (updateError) {
|
||||
console.warn('Could not save confirmation to backend:', updateError);
|
||||
// Don't fail the confirmation if backend update fails
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Strategy confirmed! Ready to generate content calendar.');
|
||||
} catch (error) {
|
||||
console.error('Error confirming strategy:', error);
|
||||
setStrategyConfirmed(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateContentCalendar = async (strategyData: StrategyData | null) => {
|
||||
try {
|
||||
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id;
|
||||
if (!userId) {
|
||||
console.error('No strategy data available for calendar generation');
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate content calendar based on confirmed strategy
|
||||
const calendarRequest = {
|
||||
user_id: userId,
|
||||
strategy_id: userId, // Using user_id as strategy_id for now
|
||||
calendar_type: 'comprehensive',
|
||||
industry: strategyData.base_strategy?.industry || 'technology',
|
||||
business_size: 'medium', // TODO: Get from strategy data
|
||||
force_refresh: false
|
||||
};
|
||||
|
||||
console.log('Generating content calendar with request:', calendarRequest);
|
||||
|
||||
// Call the calendar generation API
|
||||
const calendarResponse = await contentPlanningApi.generateCalendar(calendarRequest);
|
||||
|
||||
console.log('Content calendar generated successfully:', calendarResponse);
|
||||
|
||||
// TODO: Navigate to calendar tab or show success message
|
||||
// You could also store the calendar data in a global state
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating content calendar:', error);
|
||||
// Show error message to user
|
||||
throw new Error('Failed to generate content calendar. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
strategyConfirmed,
|
||||
showConfirmDialog,
|
||||
setShowConfirmDialog,
|
||||
handleConfirmStrategy,
|
||||
confirmStrategy,
|
||||
handleGenerateContentCalendar
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
import {
|
||||
getUserId,
|
||||
transformPollingStrategyData,
|
||||
transformFullStructureData,
|
||||
transformSwotToComprehensiveStructure,
|
||||
hasFullStructure
|
||||
} from '../utils/strategyTransformers';
|
||||
|
||||
export const useStrategyData = () => {
|
||||
const [strategyData, setStrategyData] = useState<StrategyData | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadStrategyData = async (forceRefresh = false) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const userId = getUserId();
|
||||
|
||||
// First, try to get the latest generated strategy from the polling system
|
||||
try {
|
||||
const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategy(userId);
|
||||
|
||||
if (latestStrategyResponse?.strategy) {
|
||||
console.log('✅ Found latest generated strategy from polling system:', latestStrategyResponse.strategy);
|
||||
|
||||
const transformedStrategy = transformPollingStrategyData(latestStrategyResponse.strategy);
|
||||
|
||||
console.log('🔄 Transformed strategy data:', transformedStrategy);
|
||||
setStrategyData(transformedStrategy);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (pollingError) {
|
||||
console.log('No latest strategy found in polling system, checking database...', pollingError);
|
||||
}
|
||||
|
||||
// If no strategy found in polling system, try to get from database
|
||||
try {
|
||||
const strategiesResponse = await contentPlanningApi.getEnhancedStrategies(userId);
|
||||
|
||||
// Handle the enhanced strategies response structure
|
||||
const strategies = strategiesResponse?.data?.strategies || strategiesResponse?.strategies || [];
|
||||
|
||||
if (strategies && strategies.length > 0) {
|
||||
// Get the most recent strategy (assuming it's sorted by creation date)
|
||||
const latestStrategy = strategies[0];
|
||||
|
||||
// Check if this strategy has comprehensive AI-generated data
|
||||
if (latestStrategy.comprehensive_ai_analysis) {
|
||||
console.log('✅ Found comprehensive strategy in database:', latestStrategy);
|
||||
console.log('📊 Comprehensive AI analysis structure:', latestStrategy.comprehensive_ai_analysis);
|
||||
console.log('🔍 Available fields:', Object.keys(latestStrategy.comprehensive_ai_analysis));
|
||||
|
||||
// Check if this is the full 5-component structure or SWOT analysis
|
||||
if (hasFullStructure(latestStrategy.comprehensive_ai_analysis)) {
|
||||
// Transform the data to match frontend expectations (full 5-component structure)
|
||||
const transformedStrategy = transformFullStructureData(latestStrategy);
|
||||
|
||||
console.log('🔄 Transformed enhanced strategy data (full structure):', transformedStrategy);
|
||||
console.log('🎯 Final strategy data structure:', {
|
||||
hasStrategicInsights: !!transformedStrategy.strategic_insights,
|
||||
hasCompetitiveAnalysis: !!transformedStrategy.competitive_analysis,
|
||||
hasPerformancePredictions: !!transformedStrategy.performance_predictions,
|
||||
hasImplementationRoadmap: !!transformedStrategy.implementation_roadmap,
|
||||
hasRiskAssessment: !!transformedStrategy.risk_assessment,
|
||||
hasSummary: !!transformedStrategy.summary
|
||||
});
|
||||
setStrategyData(transformedStrategy);
|
||||
setLoading(false);
|
||||
return;
|
||||
} else {
|
||||
// This is SWOT analysis, create a comprehensive 5-component structure enhanced with SWOT data
|
||||
console.log('🔄 Creating comprehensive 5-component structure from SWOT analysis');
|
||||
|
||||
const transformedStrategy = transformSwotToComprehensiveStructure(latestStrategy);
|
||||
|
||||
console.log('🔄 Created comprehensive 5-component structure from SWOT analysis:', transformedStrategy);
|
||||
console.log('🎯 Final strategy data structure:', {
|
||||
hasStrategicInsights: !!transformedStrategy.strategic_insights,
|
||||
hasCompetitiveAnalysis: !!transformedStrategy.competitive_analysis,
|
||||
hasPerformancePredictions: !!transformedStrategy.performance_predictions,
|
||||
hasImplementationRoadmap: !!transformedStrategy.implementation_roadmap,
|
||||
hasRiskAssessment: !!transformedStrategy.risk_assessment,
|
||||
hasSummary: !!transformedStrategy.summary,
|
||||
swotEnhanced: true
|
||||
});
|
||||
setStrategyData(transformedStrategy);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ Strategy found but no comprehensive_ai_analysis field:', {
|
||||
strategyId: latestStrategy.id,
|
||||
strategyName: latestStrategy.name,
|
||||
availableFields: Object.keys(latestStrategy)
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.log('No comprehensive strategies found in database, checking for recent generation...', dbError);
|
||||
}
|
||||
|
||||
// If no comprehensive strategy found in database, check for recent generation tasks
|
||||
try {
|
||||
// Try to get the latest strategy generation result
|
||||
const recentStrategies = await contentPlanningApi.getStrategies(userId);
|
||||
|
||||
// Handle the enhanced strategies response structure
|
||||
const strategies = recentStrategies?.data?.strategies || recentStrategies?.strategies || [];
|
||||
|
||||
if (strategies && strategies.length > 0) {
|
||||
// Find the most recent AI-generated strategy
|
||||
const aiGeneratedStrategy = strategies.find(
|
||||
(strategy: any) => strategy.comprehensive_ai_analysis
|
||||
);
|
||||
|
||||
if (aiGeneratedStrategy && aiGeneratedStrategy.comprehensive_ai_analysis) {
|
||||
console.log('✅ Found AI-generated strategy in recent strategies:', aiGeneratedStrategy);
|
||||
|
||||
// Transform the data to match frontend expectations
|
||||
const transformedStrategy = transformPollingStrategyData(aiGeneratedStrategy.comprehensive_ai_analysis);
|
||||
|
||||
console.log('🔄 Transformed recent strategy data:', transformedStrategy);
|
||||
setStrategyData(transformedStrategy);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (strategyError) {
|
||||
console.log('No recent strategies found, checking for generation tasks...', strategyError);
|
||||
}
|
||||
|
||||
// If no strategy data is available, show appropriate message
|
||||
console.log('❌ No comprehensive strategy data found');
|
||||
setStrategyData(null);
|
||||
setError('No comprehensive strategy data available. Please generate a strategy first.');
|
||||
setLoading(false);
|
||||
|
||||
} catch (err: any) {
|
||||
console.error('Error loading strategy data:', err);
|
||||
setError(err.message || 'Failed to load strategy data');
|
||||
setStrategyData(null);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Load data on mount and when component becomes visible
|
||||
useEffect(() => {
|
||||
// Always refresh data when component mounts to ensure we get the latest strategy
|
||||
loadStrategyData(true);
|
||||
|
||||
// Also set up a listener for when the tab becomes visible (for better UX)
|
||||
const handleVisibilityChange = () => {
|
||||
if (!document.hidden) {
|
||||
// Tab became visible, refresh data
|
||||
loadStrategyData(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
strategyData,
|
||||
loading,
|
||||
error,
|
||||
loadStrategyData
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
// Main Strategy Intelligence Tab
|
||||
export { default as StrategyIntelligenceTab } from '../StrategyIntelligenceTab';
|
||||
|
||||
// Components
|
||||
export { default as StrategyHeader } from './components/StrategyHeader';
|
||||
export { default as StrategicInsightsCard } from './components/StrategicInsightsCard';
|
||||
export { default as CompetitiveAnalysisCard } from './components/CompetitiveAnalysisCard';
|
||||
export { default as PerformancePredictionsCard } from './components/PerformancePredictionsCard';
|
||||
export { default as ImplementationRoadmapCard } from './components/ImplementationRoadmapCard';
|
||||
export { default as RiskAssessmentCard } from './components/RiskAssessmentCard';
|
||||
export { default as StrategyActions } from './components/StrategyActions';
|
||||
export { default as ConfirmationDialog } from './components/ConfirmationDialog';
|
||||
|
||||
// Hooks
|
||||
export { useStrategyData } from './hooks/useStrategyData';
|
||||
export { useStrategyActions } from './hooks/useStrategyActions';
|
||||
|
||||
// Types
|
||||
export * from './types/strategy.types';
|
||||
|
||||
// Utils
|
||||
export * from './utils/strategyTransformers';
|
||||
@@ -0,0 +1,377 @@
|
||||
import { Theme } from '@mui/material/styles';
|
||||
|
||||
// Color palette for analysis components
|
||||
export const ANALYSIS_COLORS = {
|
||||
primary: '#667eea',
|
||||
secondary: '#764ba2',
|
||||
accent: '#f093fb',
|
||||
success: '#4caf50',
|
||||
warning: '#ff9800',
|
||||
error: '#f44336',
|
||||
info: '#2196f3',
|
||||
background: {
|
||||
gradient: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%)',
|
||||
dark: 'rgba(0, 0, 0, 0.3)',
|
||||
glass: 'rgba(255, 255, 255, 0.05)'
|
||||
},
|
||||
text: {
|
||||
primary: 'white',
|
||||
secondary: '#e0e0e0',
|
||||
muted: '#b0b0b0'
|
||||
},
|
||||
border: {
|
||||
primary: 'rgba(102, 126, 234, 0.3)',
|
||||
secondary: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
};
|
||||
|
||||
// Priority colors for chips
|
||||
export const PRIORITY_COLORS = {
|
||||
high: '#f44336',
|
||||
medium: '#ff9800',
|
||||
low: '#4caf50',
|
||||
default: '#667eea'
|
||||
};
|
||||
|
||||
// Impact colors for chips
|
||||
export const IMPACT_COLORS = {
|
||||
high: '#f44336',
|
||||
medium: '#ff9800',
|
||||
low: '#4caf50',
|
||||
default: '#667eea'
|
||||
};
|
||||
|
||||
// Main card container styles
|
||||
export const getAnalysisCardStyles = () => ({
|
||||
card: {
|
||||
height: '100%',
|
||||
borderRadius: 3,
|
||||
background: ANALYSIS_COLORS.background.gradient,
|
||||
color: ANALYSIS_COLORS.text.primary,
|
||||
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.5), 0 0 40px rgba(102, 126, 234, 0.3)',
|
||||
border: `1px solid ${ANALYSIS_COLORS.border.primary}`,
|
||||
position: 'relative' as const,
|
||||
overflow: 'hidden',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%)',
|
||||
pointerEvents: 'none'
|
||||
},
|
||||
'&:hover': {
|
||||
boxShadow: '0 25px 70px rgba(102, 126, 234, 0.4)',
|
||||
transform: 'translateY(-4px)'
|
||||
},
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
},
|
||||
cardContent: {
|
||||
p: 2.5,
|
||||
position: 'relative' as const,
|
||||
zIndex: 1
|
||||
}
|
||||
});
|
||||
|
||||
// Header section styles
|
||||
export const getHeaderStyles = () => ({
|
||||
headerContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 2.5
|
||||
},
|
||||
iconContainer: {
|
||||
p: 1,
|
||||
borderRadius: 2,
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
mr: 1.5,
|
||||
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)'
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1
|
||||
},
|
||||
title: {
|
||||
fontWeight: 700,
|
||||
background: 'linear-gradient(45deg, #667eea, #764ba2, #f093fb)',
|
||||
backgroundSize: '200% 200%',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
animation: 'gradient 3s ease infinite',
|
||||
'@keyframes gradient': {
|
||||
'0%': { backgroundPosition: '0% 50%' },
|
||||
'50%': { backgroundPosition: '100% 50%' },
|
||||
'100%': { backgroundPosition: '0% 50%' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Market analysis header styles
|
||||
export const getMarketAnalysisHeaderStyles = () => ({
|
||||
marketAnalysisContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
p: 1.5,
|
||||
background: ANALYSIS_COLORS.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_COLORS.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)'
|
||||
},
|
||||
circularProgress: {
|
||||
color: ANALYSIS_COLORS.primary,
|
||||
'& .MuiCircularProgress-circle': {
|
||||
strokeLinecap: 'round',
|
||||
filter: 'drop-shadow(0 2px 4px rgba(102, 126, 234, 0.3))'
|
||||
}
|
||||
},
|
||||
progressText: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontWeight: 700,
|
||||
color: ANALYSIS_COLORS.primary,
|
||||
fontSize: '0.7rem'
|
||||
},
|
||||
positionText: {
|
||||
fontWeight: 600,
|
||||
color: ANALYSIS_COLORS.text.primary,
|
||||
display: 'block',
|
||||
fontSize: '0.7rem'
|
||||
},
|
||||
positionLabel: {
|
||||
color: ANALYSIS_COLORS.text.secondary,
|
||||
fontSize: '0.6rem'
|
||||
},
|
||||
marketChip: {
|
||||
background: 'rgba(0, 0, 0, 0.4)',
|
||||
color: ANALYSIS_COLORS.primary,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.6rem',
|
||||
height: 20,
|
||||
border: `1px solid rgba(102, 126, 234, 0.4)`,
|
||||
'& .MuiChip-label': {
|
||||
px: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Section container styles
|
||||
export const getSectionStyles = () => ({
|
||||
sectionContainer: {
|
||||
mb: 2.5,
|
||||
p: 2,
|
||||
background: ANALYSIS_COLORS.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_COLORS.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)'
|
||||
},
|
||||
sectionTitle: {
|
||||
fontWeight: 600,
|
||||
color: ANALYSIS_COLORS.text.primary,
|
||||
fontSize: '0.85rem'
|
||||
}
|
||||
});
|
||||
|
||||
// Accordion styles
|
||||
export const getAccordionStyles = () => ({
|
||||
accordion: {
|
||||
mb: 1,
|
||||
'&:before': { display: 'none' },
|
||||
background: ANALYSIS_COLORS.background.dark,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${ANALYSIS_COLORS.border.secondary}`,
|
||||
backdropFilter: 'blur(10px)',
|
||||
'&.Mui-expanded': {
|
||||
margin: '8px 0'
|
||||
}
|
||||
},
|
||||
accordionSummary: {
|
||||
'& .MuiAccordionSummary-content': {
|
||||
margin: '8px 0'
|
||||
}
|
||||
},
|
||||
accordionTitle: {
|
||||
fontWeight: 600,
|
||||
color: ANALYSIS_COLORS.text.primary,
|
||||
fontSize: '0.8rem'
|
||||
},
|
||||
accordionSubtitle: {
|
||||
color: ANALYSIS_COLORS.text.secondary
|
||||
},
|
||||
expandIcon: {
|
||||
color: ANALYSIS_COLORS.primary
|
||||
}
|
||||
});
|
||||
|
||||
// Enhanced chip styles
|
||||
export const getEnhancedChipStyles = (color: string) => ({
|
||||
chip: {
|
||||
background: `linear-gradient(135deg, ${color}70 0%, ${color}60 100%)`,
|
||||
color: color,
|
||||
fontWeight: 700,
|
||||
fontSize: '0.75rem',
|
||||
height: 30,
|
||||
border: `3px solid ${color}80`,
|
||||
boxShadow: `0 8px 20px ${color}50, inset 0 3px 0 rgba(255, 255, 255, 0.2), inset 0 -2px 0 rgba(0, 0, 0, 0.4), 0 0 15px ${color}30`,
|
||||
backdropFilter: 'blur(20px)',
|
||||
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
textShadow: '0 2px 4px rgba(0, 0, 0, 0.5)',
|
||||
position: 'relative' as const,
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: `linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(0, 0, 0, 0.1) 100%)`,
|
||||
borderRadius: 'inherit',
|
||||
pointerEvents: 'none'
|
||||
},
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px) scale(1.05)',
|
||||
boxShadow: `0 12px 30px ${color}60, inset 0 3px 0 rgba(255, 255, 255, 0.3), inset 0 -2px 0 rgba(0, 0, 0, 0.5), 0 0 25px ${color}40`,
|
||||
background: `linear-gradient(135deg, ${color}80 0%, ${color}70 100%)`
|
||||
},
|
||||
'& .MuiChip-label': {
|
||||
px: 2.5,
|
||||
py: 1,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.6px',
|
||||
position: 'relative' as const,
|
||||
zIndex: 1
|
||||
},
|
||||
'& .MuiChip-icon': {
|
||||
color: color,
|
||||
fontSize: '1.1rem',
|
||||
filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5))',
|
||||
position: 'relative' as const,
|
||||
zIndex: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// List item styles
|
||||
export const getListItemStyles = () => ({
|
||||
listItem: {
|
||||
px: 0,
|
||||
py: 1
|
||||
},
|
||||
listItemIcon: {
|
||||
minWidth: 32
|
||||
},
|
||||
bulletPoint: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
background: ANALYSIS_COLORS.primary,
|
||||
opacity: 0.7
|
||||
},
|
||||
insightText: {
|
||||
fontWeight: 500,
|
||||
mb: 1,
|
||||
color: ANALYSIS_COLORS.text.primary,
|
||||
lineHeight: 1.5
|
||||
},
|
||||
reasoningText: {
|
||||
color: ANALYSIS_COLORS.text.muted,
|
||||
fontStyle: 'italic',
|
||||
lineHeight: 1.4
|
||||
}
|
||||
});
|
||||
|
||||
// Fallback/empty state styles
|
||||
export const getFallbackStyles = () => ({
|
||||
fallbackContainer: {
|
||||
textAlign: 'center',
|
||||
py: 4
|
||||
},
|
||||
fallbackText: {
|
||||
mb: 2,
|
||||
color: ANALYSIS_COLORS.text.secondary
|
||||
},
|
||||
fallbackCaption: {
|
||||
color: ANALYSIS_COLORS.text.muted
|
||||
}
|
||||
});
|
||||
|
||||
// Animation styles
|
||||
export const getAnimationStyles = () => ({
|
||||
iconAnimation: {
|
||||
animate: { rotate: [0, 10, -10, 0] },
|
||||
transition: { duration: 2, repeat: Infinity, ease: "easeInOut" as const }
|
||||
},
|
||||
cardAnimation: {
|
||||
initial: { opacity: 0, y: 20 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.5 },
|
||||
whileHover: { y: -4 }
|
||||
}
|
||||
});
|
||||
|
||||
// Utility functions
|
||||
export const getPriorityColor = (priority: string): string => {
|
||||
switch (priority.toLowerCase()) {
|
||||
case 'high':
|
||||
return PRIORITY_COLORS.high;
|
||||
case 'medium':
|
||||
return PRIORITY_COLORS.medium;
|
||||
case 'low':
|
||||
return PRIORITY_COLORS.low;
|
||||
default:
|
||||
return PRIORITY_COLORS.default;
|
||||
}
|
||||
};
|
||||
|
||||
export const getImpactColor = (impact: string): string => {
|
||||
switch (impact.toLowerCase()) {
|
||||
case 'high':
|
||||
return IMPACT_COLORS.high;
|
||||
case 'medium':
|
||||
return IMPACT_COLORS.medium;
|
||||
case 'low':
|
||||
return IMPACT_COLORS.low;
|
||||
default:
|
||||
return IMPACT_COLORS.default;
|
||||
}
|
||||
};
|
||||
|
||||
// Icon mapping for different insight types
|
||||
export const getInsightIcon = (type: string) => {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'market positioning':
|
||||
return { icon: 'Business', color: ANALYSIS_COLORS.primary };
|
||||
case 'content opportunity':
|
||||
return { icon: 'Lightbulb', color: ANALYSIS_COLORS.success };
|
||||
case 'growth potential':
|
||||
return { icon: 'TrendingUp', color: ANALYSIS_COLORS.info };
|
||||
case 'competitive advantage':
|
||||
return { icon: 'Star', color: ANALYSIS_COLORS.warning };
|
||||
case 'strategic recommendation':
|
||||
return { icon: 'Psychology', color: ANALYSIS_COLORS.error };
|
||||
default:
|
||||
return { icon: 'Lightbulb', color: ANALYSIS_COLORS.primary };
|
||||
}
|
||||
};
|
||||
|
||||
// Complete style object for easy import
|
||||
export const ANALYSIS_CARD_STYLES = {
|
||||
colors: ANALYSIS_COLORS,
|
||||
priorityColors: PRIORITY_COLORS,
|
||||
impactColors: IMPACT_COLORS,
|
||||
getAnalysisCardStyles,
|
||||
getHeaderStyles,
|
||||
getMarketAnalysisHeaderStyles,
|
||||
getSectionStyles,
|
||||
getAccordionStyles,
|
||||
getEnhancedChipStyles,
|
||||
getListItemStyles,
|
||||
getFallbackStyles,
|
||||
getAnimationStyles,
|
||||
getPriorityColor,
|
||||
getImpactColor,
|
||||
getInsightIcon
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './analysisCardStyles';
|
||||
@@ -0,0 +1,267 @@
|
||||
export interface StrategyMetadata {
|
||||
generated_at?: string;
|
||||
generation_timestamp?: string;
|
||||
user_id: number;
|
||||
strategy_name: string;
|
||||
generation_version?: string;
|
||||
ai_model?: string;
|
||||
personalization_level?: string;
|
||||
ai_generated: boolean;
|
||||
comprehensive: boolean;
|
||||
content_calendar_ready: boolean;
|
||||
}
|
||||
|
||||
export interface StrategicInsights {
|
||||
market_positioning?: {
|
||||
positioning_strength: number;
|
||||
current_position: string;
|
||||
swot_analysis?: {
|
||||
strengths: string[];
|
||||
opportunities: string[];
|
||||
};
|
||||
};
|
||||
content_opportunities?: string[];
|
||||
growth_potential?: {
|
||||
market_size: string;
|
||||
growth_rate: string;
|
||||
key_drivers?: string[];
|
||||
competitive_advantages?: string[];
|
||||
};
|
||||
swot_summary?: {
|
||||
overall_score: number;
|
||||
primary_strengths: string[];
|
||||
key_opportunities: string[];
|
||||
};
|
||||
// Backend insights array structure
|
||||
insights?: Array<{
|
||||
type: string;
|
||||
insight: string;
|
||||
confidence_level: string;
|
||||
estimated_impact: string;
|
||||
implementation_time: string;
|
||||
priority: string;
|
||||
reasoning: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface CompetitiveAnalysis {
|
||||
competitors?: Array<{
|
||||
name: string;
|
||||
market_position: string;
|
||||
strengths: string[];
|
||||
weaknesses: string[];
|
||||
content_strategy?: string; // Added to match backend
|
||||
competitive_response?: string;
|
||||
}>;
|
||||
market_gaps?: string[];
|
||||
opportunities?: string[]; // Added to match backend
|
||||
recommendations?: string[]; // Added to match backend
|
||||
competitive_advantages?: {
|
||||
primary: string[];
|
||||
sustainable: string[];
|
||||
development_areas: string[];
|
||||
};
|
||||
swot_competitive_insights?: {
|
||||
leverage_strengths: string[];
|
||||
address_weaknesses: string[];
|
||||
capitalize_opportunities: string[];
|
||||
mitigate_threats: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface PerformancePredictions {
|
||||
estimated_roi?: string;
|
||||
key_metrics?: {
|
||||
engagement_rate?: string;
|
||||
conversion_rate?: string;
|
||||
reach_growth?: string;
|
||||
brand_awareness?: string;
|
||||
market_share?: string;
|
||||
};
|
||||
timeline_projections?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
success_factors?: {
|
||||
primary: string[];
|
||||
secondary: string[];
|
||||
risk_mitigation: string[];
|
||||
};
|
||||
swot_based_predictions?: {
|
||||
strength_impact: string;
|
||||
opportunity_impact: string;
|
||||
weakness_mitigation: string;
|
||||
threat_management: string;
|
||||
};
|
||||
// Nested prediction objects from backend
|
||||
traffic_predictions?: {
|
||||
monthly_traffic?: string;
|
||||
growth_rate?: string;
|
||||
traffic_growth?: string;
|
||||
month_1?: string;
|
||||
};
|
||||
engagement_predictions?: {
|
||||
engagement_rate?: string;
|
||||
brand_awareness?: string;
|
||||
time_on_page?: string;
|
||||
month_3?: string;
|
||||
success_factors?: string[];
|
||||
};
|
||||
conversion_predictions?: {
|
||||
conversion_rate?: string;
|
||||
lead_generation?: string;
|
||||
month_6?: string;
|
||||
risk_mitigation?: string[];
|
||||
};
|
||||
roi_predictions?: {
|
||||
estimated_roi?: string;
|
||||
market_share?: string;
|
||||
cost_benefit?: string;
|
||||
success_factors?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ImplementationRoadmap {
|
||||
total_duration?: string;
|
||||
phases?: Array<{
|
||||
phase: string;
|
||||
duration: string;
|
||||
tasks: string[]; // Changed from activities to match backend
|
||||
milestones: string[]; // Changed from deliverables to match backend
|
||||
resources: string[]; // Added to match backend
|
||||
swot_focus?: string;
|
||||
}>;
|
||||
resource_allocation?: {
|
||||
team_members?: string[]; // Changed from team_requirements to match backend
|
||||
team_requirements?: string[]; // Added to match backend data
|
||||
budget_allocation?: {
|
||||
total_budget?: string;
|
||||
content_creation?: string;
|
||||
technology_tools?: string;
|
||||
marketing_promotion?: string;
|
||||
external_resources?: string;
|
||||
};
|
||||
swot_priorities?: {
|
||||
high_priority: string[];
|
||||
medium_priority: string[];
|
||||
low_priority: string[];
|
||||
};
|
||||
};
|
||||
success_metrics?: string[]; // Added to match backend
|
||||
timeline?: {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
key_milestones?: string[];
|
||||
}; // Added to match backend
|
||||
swot_integration?: {
|
||||
strength_leverage: string[];
|
||||
weakness_improvement: string[];
|
||||
opportunity_capitalization: string[];
|
||||
threat_mitigation: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface RiskAssessment {
|
||||
overall_risk_level?: string;
|
||||
risks?: Array<{
|
||||
risk: string;
|
||||
probability: string;
|
||||
impact: string;
|
||||
mitigation?: string;
|
||||
contingency?: string;
|
||||
}>;
|
||||
risk_categories?: {
|
||||
market_risks?: Array<{
|
||||
risk: string;
|
||||
probability: string;
|
||||
impact: string;
|
||||
mitigation?: string;
|
||||
}>;
|
||||
operational_risks?: Array<{
|
||||
risk: string;
|
||||
probability: string;
|
||||
impact: string;
|
||||
mitigation?: string;
|
||||
}>;
|
||||
competitive_risks?: Array<{
|
||||
risk: string;
|
||||
probability: string;
|
||||
impact: string;
|
||||
mitigation?: string;
|
||||
}>;
|
||||
technical_risks?: Array<{
|
||||
risk: string;
|
||||
probability: string;
|
||||
impact: string;
|
||||
mitigation?: string;
|
||||
}>;
|
||||
financial_risks?: Array<{
|
||||
risk: string;
|
||||
probability: string;
|
||||
impact: string;
|
||||
mitigation?: string;
|
||||
}>;
|
||||
};
|
||||
monitoring_framework?: {
|
||||
escalation_procedures?: string[];
|
||||
key_indicators?: string[];
|
||||
monitoring_frequency?: string;
|
||||
review_schedule?: string;
|
||||
};
|
||||
swot_risk_mapping?: {
|
||||
strength_risks: string;
|
||||
weakness_risks: string;
|
||||
opportunity_risks: string;
|
||||
threat_risks: string;
|
||||
};
|
||||
risk_mitigation_strategies?: {
|
||||
based_on_strengths: string;
|
||||
based_on_opportunities: string;
|
||||
based_on_weaknesses: string;
|
||||
based_on_threats: string;
|
||||
};
|
||||
mitigation_strategies?: string[];
|
||||
}
|
||||
|
||||
export interface StrategySummary {
|
||||
estimated_roi: string;
|
||||
implementation_timeline: string;
|
||||
risk_level: string;
|
||||
success_probability: string;
|
||||
next_step: string;
|
||||
swot_highlights?: {
|
||||
key_strengths: string[];
|
||||
key_opportunities: string[];
|
||||
primary_risks: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface StrategyData {
|
||||
strategy_metadata?: StrategyMetadata;
|
||||
metadata?: StrategyMetadata;
|
||||
base_strategy?: any;
|
||||
strategic_insights?: StrategicInsights;
|
||||
competitive_analysis?: CompetitiveAnalysis;
|
||||
performance_predictions?: PerformancePredictions;
|
||||
implementation_roadmap?: ImplementationRoadmap;
|
||||
risk_assessment?: RiskAssessment;
|
||||
summary?: StrategySummary;
|
||||
}
|
||||
|
||||
export interface StrategyActionsProps {
|
||||
strategyData: StrategyData | null;
|
||||
strategyConfirmed: boolean;
|
||||
onConfirmStrategy: () => void;
|
||||
onGenerateContentCalendar: () => void;
|
||||
onRefreshData: () => void;
|
||||
}
|
||||
|
||||
export interface StrategyCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export interface ConfirmationDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
import { StrategyData } from '../types/strategy.types';
|
||||
|
||||
// Helper function to get user ID from context or store
|
||||
export const getUserId = (): number => {
|
||||
// TODO: Replace with actual user context/store
|
||||
// For now, return default user ID
|
||||
return 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform polling system strategy data to frontend format
|
||||
*/
|
||||
export const transformPollingStrategyData = (strategyData: any): StrategyData => {
|
||||
return {
|
||||
...strategyData,
|
||||
strategy_metadata: strategyData.metadata || strategyData.strategy_metadata,
|
||||
// Add summary if not present
|
||||
summary: strategyData.summary || {
|
||||
estimated_roi: strategyData.performance_predictions?.estimated_roi || "15-25%",
|
||||
implementation_timeline: strategyData.implementation_roadmap?.total_duration || "12 months",
|
||||
risk_level: strategyData.risk_assessment?.overall_risk_level || "Medium",
|
||||
success_probability: strategyData.performance_predictions?.success_probability || "85%",
|
||||
next_step: "Review strategy and generate content calendar"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform full 5-component structure from database
|
||||
*/
|
||||
export const transformFullStructureData = (latestStrategy: any): StrategyData => {
|
||||
const comprehensiveData = latestStrategy.comprehensive_ai_analysis;
|
||||
|
||||
return {
|
||||
// Map metadata
|
||||
strategy_metadata: comprehensiveData.metadata || comprehensiveData.strategy_metadata,
|
||||
metadata: comprehensiveData.metadata || comprehensiveData.strategy_metadata,
|
||||
|
||||
// Transform Strategic Insights
|
||||
strategic_insights: comprehensiveData.strategic_insights ? {
|
||||
market_positioning: {
|
||||
positioning_strength: 75, // Default value
|
||||
current_position: "Emerging",
|
||||
swot_analysis: {
|
||||
strengths: [],
|
||||
opportunities: []
|
||||
}
|
||||
},
|
||||
content_opportunities: comprehensiveData.strategic_insights.insights?.filter((insight: any) =>
|
||||
insight.type === 'Content Opportunity'
|
||||
).map((insight: any) => insight.insight) || [],
|
||||
growth_potential: {
|
||||
market_size: "Growing",
|
||||
growth_rate: "High",
|
||||
key_drivers: comprehensiveData.strategic_insights.insights?.filter((insight: any) =>
|
||||
insight.type === 'Growth Potential'
|
||||
).map((insight: any) => insight.insight) || [],
|
||||
competitive_advantages: []
|
||||
},
|
||||
swot_summary: {
|
||||
overall_score: 75,
|
||||
primary_strengths: comprehensiveData.strategic_insights.insights?.slice(0, 2) || [],
|
||||
key_opportunities: comprehensiveData.strategic_insights.insights?.slice(2, 4) || []
|
||||
},
|
||||
// Map the insights array directly
|
||||
insights: comprehensiveData.strategic_insights.insights || []
|
||||
} : undefined,
|
||||
|
||||
// Transform Competitive Analysis
|
||||
competitive_analysis: comprehensiveData.competitive_analysis ? {
|
||||
competitors: comprehensiveData.competitive_analysis.competitors || [],
|
||||
market_gaps: comprehensiveData.competitive_analysis.market_gaps || [],
|
||||
opportunities: comprehensiveData.competitive_analysis.opportunities || [],
|
||||
recommendations: comprehensiveData.competitive_analysis.recommendations || [],
|
||||
competitive_advantages: {
|
||||
primary: comprehensiveData.competitive_analysis.recommendations?.slice(0, 3) || [],
|
||||
sustainable: comprehensiveData.competitive_analysis.recommendations?.slice(3, 5) || [],
|
||||
development_areas: comprehensiveData.competitive_analysis.opportunities || []
|
||||
},
|
||||
swot_competitive_insights: {
|
||||
leverage_strengths: comprehensiveData.competitive_analysis.recommendations?.slice(0, 2) || [],
|
||||
address_weaknesses: comprehensiveData.competitive_analysis.recommendations?.slice(2, 4) || [],
|
||||
capitalize_opportunities: comprehensiveData.competitive_analysis.opportunities?.slice(0, 2) || [],
|
||||
mitigate_threats: comprehensiveData.competitive_analysis.recommendations?.slice(4, 6) || []
|
||||
}
|
||||
} : undefined,
|
||||
|
||||
// Transform Performance Predictions
|
||||
performance_predictions: comprehensiveData.performance_predictions ? {
|
||||
estimated_roi: comprehensiveData.performance_predictions.roi_predictions?.estimated_roi || "15-25%",
|
||||
key_metrics: {
|
||||
engagement_rate: comprehensiveData.performance_predictions.engagement_predictions?.engagement_rate || "8-12%",
|
||||
conversion_rate: comprehensiveData.performance_predictions.conversion_predictions?.conversion_rate || "3-5%",
|
||||
reach_growth: comprehensiveData.performance_predictions.traffic_predictions?.traffic_growth || "40-60%",
|
||||
brand_awareness: comprehensiveData.performance_predictions.engagement_predictions?.brand_awareness || "25-35%",
|
||||
market_share: comprehensiveData.performance_predictions.roi_predictions?.market_share || "5-8%"
|
||||
},
|
||||
timeline_projections: {
|
||||
"month_1": comprehensiveData.performance_predictions.traffic_predictions?.month_1 || "Initial setup and content creation",
|
||||
"month_3": comprehensiveData.performance_predictions.engagement_predictions?.month_3 || "Content optimization and audience growth",
|
||||
"month_6": comprehensiveData.performance_predictions.conversion_predictions?.month_6 || "Full strategy implementation and scaling"
|
||||
},
|
||||
success_factors: {
|
||||
primary: comprehensiveData.performance_predictions.roi_predictions?.success_factors?.slice(0, 3) || [],
|
||||
secondary: comprehensiveData.performance_predictions.engagement_predictions?.success_factors?.slice(0, 2) || [],
|
||||
risk_mitigation: comprehensiveData.performance_predictions.conversion_predictions?.risk_mitigation?.slice(0, 2) || []
|
||||
},
|
||||
swot_based_predictions: {
|
||||
strength_impact: "High positive impact from identified strengths",
|
||||
opportunity_impact: "Significant growth potential from market opportunities",
|
||||
weakness_mitigation: "Addressing weaknesses through strategic content planning",
|
||||
threat_management: "Proactive threat management through diversified approach"
|
||||
}
|
||||
} : undefined,
|
||||
|
||||
// Transform Implementation Roadmap
|
||||
implementation_roadmap: comprehensiveData.implementation_roadmap ? {
|
||||
total_duration: comprehensiveData.implementation_roadmap.total_duration || "6 months",
|
||||
phases: comprehensiveData.implementation_roadmap.phases || [],
|
||||
success_metrics: comprehensiveData.implementation_roadmap.success_metrics || [],
|
||||
timeline: comprehensiveData.implementation_roadmap.timeline || {
|
||||
start_date: "2024-09-01",
|
||||
end_date: "2025-02-28",
|
||||
key_milestones: []
|
||||
},
|
||||
resource_allocation: {
|
||||
team_members: comprehensiveData.implementation_roadmap.resource_allocation?.team_members ||
|
||||
comprehensiveData.implementation_roadmap.resource_allocation?.team_requirements ||
|
||||
["Content Strategist", "SEO Specialist", "Content Writer", "Editor"],
|
||||
team_requirements: comprehensiveData.implementation_roadmap.resource_allocation?.team_requirements ||
|
||||
comprehensiveData.implementation_roadmap.resource_allocation?.team_members ||
|
||||
["Content Strategist", "SEO Specialist", "Content Writer", "Editor"],
|
||||
budget_allocation: comprehensiveData.implementation_roadmap.resource_allocation?.budget_allocation || {
|
||||
total_budget: "$60,000",
|
||||
content_creation: "$30,000",
|
||||
technology_tools: "$5,000",
|
||||
marketing_promotion: "$20,000",
|
||||
external_resources: "$5,000"
|
||||
},
|
||||
swot_priorities: {
|
||||
high_priority: comprehensiveData.implementation_roadmap.success_metrics?.slice(0, 3) || [],
|
||||
medium_priority: comprehensiveData.implementation_roadmap.success_metrics?.slice(3, 6) || [],
|
||||
low_priority: comprehensiveData.implementation_roadmap.success_metrics?.slice(6, 9) || []
|
||||
}
|
||||
}
|
||||
} : undefined,
|
||||
|
||||
// Transform Risk Assessment
|
||||
risk_assessment: comprehensiveData.risk_assessment ? {
|
||||
overall_risk_level: comprehensiveData.risk_assessment.overall_risk_level || "Medium",
|
||||
risks: comprehensiveData.risk_assessment.risks || [],
|
||||
risk_categories: comprehensiveData.risk_assessment.risk_categories || {},
|
||||
monitoring_framework: comprehensiveData.risk_assessment.monitoring_framework || {
|
||||
escalation_procedures: [],
|
||||
key_indicators: [],
|
||||
monitoring_frequency: "Weekly for key performance metrics, Monthly for strategic content review, Quarterly for comprehensive analysis",
|
||||
review_schedule: "Monthly performance review meetings to discuss analytics and tactical adjustments. Quarterly strategic comprehensive review"
|
||||
},
|
||||
swot_risk_mapping: {
|
||||
strength_risks: "Leverage strengths to mitigate risks",
|
||||
weakness_risks: "Address weaknesses through strategic planning",
|
||||
opportunity_risks: "Capitalize on opportunities while managing risks",
|
||||
threat_risks: "Proactive threat management and contingency planning"
|
||||
},
|
||||
risk_mitigation_strategies: {
|
||||
based_on_strengths: comprehensiveData.risk_assessment.mitigation_strategies?.[0] || "Leverage identified strengths",
|
||||
based_on_opportunities: comprehensiveData.risk_assessment.mitigation_strategies?.[1] || "Capitalize on market opportunities",
|
||||
based_on_weaknesses: comprehensiveData.risk_assessment.mitigation_strategies?.[2] || "Address identified weaknesses",
|
||||
based_on_threats: comprehensiveData.risk_assessment.mitigation_strategies?.[3] || "Proactive threat management"
|
||||
},
|
||||
mitigation_strategies: comprehensiveData.risk_assessment.mitigation_strategies || []
|
||||
} : undefined,
|
||||
|
||||
// Add summary
|
||||
summary: comprehensiveData.summary || {
|
||||
estimated_roi: comprehensiveData.performance_predictions?.roi_predictions?.estimated_roi || "15-25%",
|
||||
implementation_timeline: comprehensiveData.implementation_roadmap?.total_duration || "6 months",
|
||||
risk_level: comprehensiveData.risk_assessment?.overall_risk_level || "Medium",
|
||||
success_probability: "85%",
|
||||
next_step: "Review strategy and generate content calendar"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform SWOT analysis into comprehensive 5-component structure
|
||||
*/
|
||||
export const transformSwotToComprehensiveStructure = (latestStrategy: any): StrategyData => {
|
||||
const swotData = latestStrategy.comprehensive_ai_analysis;
|
||||
|
||||
return {
|
||||
strategy_metadata: {
|
||||
user_id: latestStrategy.user_id,
|
||||
strategy_name: latestStrategy.name,
|
||||
ai_generated: true,
|
||||
comprehensive: true,
|
||||
content_calendar_ready: false,
|
||||
generation_timestamp: latestStrategy.created_at
|
||||
},
|
||||
// Enhanced Strategic Insights with SWOT data
|
||||
strategic_insights: {
|
||||
market_positioning: {
|
||||
positioning_strength: swotData.overall_score || 75,
|
||||
current_position: "Emerging",
|
||||
swot_analysis: {
|
||||
strengths: swotData.strengths || [],
|
||||
opportunities: swotData.opportunities || []
|
||||
}
|
||||
},
|
||||
content_opportunities: [
|
||||
...(swotData.opportunities || []),
|
||||
"Leverage identified market gaps",
|
||||
"Focus on unique value propositions",
|
||||
"Build thought leadership content"
|
||||
],
|
||||
growth_potential: {
|
||||
market_size: "Growing",
|
||||
growth_rate: "High",
|
||||
key_drivers: swotData.opportunities || [],
|
||||
competitive_advantages: swotData.strengths || []
|
||||
},
|
||||
swot_summary: {
|
||||
overall_score: swotData.overall_score || 75,
|
||||
primary_strengths: (swotData.strengths || []).slice(0, 3),
|
||||
key_opportunities: (swotData.opportunities || []).slice(0, 3)
|
||||
}
|
||||
},
|
||||
// Enhanced Competitive Analysis with SWOT data
|
||||
competitive_analysis: {
|
||||
competitors: [
|
||||
{
|
||||
name: "Direct Competitors",
|
||||
market_position: "Established",
|
||||
strengths: swotData.strengths || [],
|
||||
weaknesses: swotData.weaknesses || [],
|
||||
competitive_response: "Focus on differentiation"
|
||||
},
|
||||
{
|
||||
name: "Emerging Competitors",
|
||||
market_position: "Growing",
|
||||
strengths: [],
|
||||
weaknesses: swotData.weaknesses || [],
|
||||
competitive_response: "Establish market leadership"
|
||||
}
|
||||
],
|
||||
market_gaps: [
|
||||
...(swotData.opportunities || []),
|
||||
"Content personalization opportunities",
|
||||
"Niche market segments",
|
||||
"Innovation in content delivery"
|
||||
],
|
||||
competitive_advantages: {
|
||||
primary: swotData.strengths || [],
|
||||
sustainable: swotData.opportunities || [],
|
||||
development_areas: swotData.weaknesses || []
|
||||
},
|
||||
swot_competitive_insights: {
|
||||
leverage_strengths: swotData.strengths || [],
|
||||
address_weaknesses: swotData.weaknesses || [],
|
||||
capitalize_opportunities: swotData.opportunities || [],
|
||||
mitigate_threats: swotData.threats || []
|
||||
}
|
||||
},
|
||||
// Enhanced Performance Predictions with SWOT context
|
||||
performance_predictions: {
|
||||
estimated_roi: "20-30%",
|
||||
key_metrics: {
|
||||
engagement_rate: "5-8%",
|
||||
conversion_rate: "2-4%",
|
||||
reach_growth: "40-60%",
|
||||
brand_awareness: "25-35%",
|
||||
market_share: "3-5%"
|
||||
},
|
||||
timeline_projections: {
|
||||
"3_months": "Initial traction and brand awareness leveraging identified strengths",
|
||||
"6_months": "Established presence and engagement addressing market opportunities",
|
||||
"12_months": "Market leadership and growth capitalizing on competitive advantages"
|
||||
},
|
||||
success_factors: {
|
||||
primary: swotData.strengths || [],
|
||||
secondary: swotData.opportunities || [],
|
||||
risk_mitigation: swotData.threats || []
|
||||
},
|
||||
swot_based_predictions: {
|
||||
strength_impact: "High performance in areas of identified strengths",
|
||||
opportunity_impact: "Growth potential through market opportunities",
|
||||
weakness_mitigation: "Improved performance by addressing weaknesses",
|
||||
threat_management: "Risk-adjusted projections considering market threats"
|
||||
}
|
||||
},
|
||||
// Enhanced Implementation Roadmap with SWOT considerations
|
||||
implementation_roadmap: {
|
||||
total_duration: "12 months",
|
||||
phases: [
|
||||
{
|
||||
phase: "Foundation (Months 1-3)",
|
||||
duration: "3 months",
|
||||
tasks: [
|
||||
"Brand positioning leveraging identified strengths",
|
||||
"Content strategy development addressing market opportunities",
|
||||
"Weakness assessment and improvement planning"
|
||||
],
|
||||
milestones: ["Brand guidelines", "Content calendar", "SWOT action plan"],
|
||||
resources: ["Content Strategist", "Brand Manager", "SWOT Analyst"],
|
||||
swot_focus: "Strengths and Opportunities"
|
||||
},
|
||||
{
|
||||
phase: "Growth (Months 4-8)",
|
||||
duration: "5 months",
|
||||
tasks: [
|
||||
"Content execution based on competitive advantages",
|
||||
"Community building addressing market gaps",
|
||||
"Threat mitigation strategies implementation"
|
||||
],
|
||||
milestones: ["Content library", "Engaged audience", "Risk management framework"],
|
||||
resources: ["Content Writer", "Community Manager", "Risk Analyst"],
|
||||
swot_focus: "Opportunities and Threats"
|
||||
},
|
||||
{
|
||||
phase: "Scale (Months 9-12)",
|
||||
duration: "4 months",
|
||||
tasks: [
|
||||
"Market expansion capitalizing on strengths",
|
||||
"Performance optimization addressing weaknesses",
|
||||
"Sustainable competitive advantage development"
|
||||
],
|
||||
milestones: ["Market leadership", "Optimized strategy", "Long-term competitive position"],
|
||||
resources: ["Growth Manager", "Performance Analyst", "Strategy Consultant"],
|
||||
swot_focus: "Strengths and Weaknesses"
|
||||
}
|
||||
],
|
||||
resource_allocation: {
|
||||
team_members: ["Content Strategist", "SEO Specialist", "Content Writer", "Editor", "Marketing Manager"],
|
||||
budget_allocation: {
|
||||
total_budget: "$60,000",
|
||||
content_creation: "$30,000",
|
||||
technology_tools: "$5,000",
|
||||
marketing_promotion: "$20,000",
|
||||
external_resources: "$5,000"
|
||||
},
|
||||
swot_priorities: {
|
||||
high_priority: swotData.opportunities || [],
|
||||
medium_priority: swotData.strengths || [],
|
||||
low_priority: swotData.weaknesses || []
|
||||
}
|
||||
},
|
||||
swot_integration: {
|
||||
strength_leverage: swotData.strengths || [],
|
||||
weakness_improvement: swotData.weaknesses || [],
|
||||
opportunity_capitalization: swotData.opportunities || [],
|
||||
threat_mitigation: swotData.threats || []
|
||||
}
|
||||
},
|
||||
// Enhanced Risk Assessment with SWOT threats
|
||||
risk_assessment: {
|
||||
overall_risk_level: "Medium",
|
||||
risks: [
|
||||
...(swotData.threats?.map((threat: string) => ({
|
||||
risk: threat,
|
||||
probability: "Medium",
|
||||
impact: "High",
|
||||
mitigation: "Strategic planning and monitoring"
|
||||
})) || []),
|
||||
{
|
||||
risk: "Market saturation",
|
||||
probability: "Medium",
|
||||
impact: "Medium",
|
||||
mitigation: "Innovation and differentiation"
|
||||
}
|
||||
],
|
||||
risk_categories: {
|
||||
market_risks: [
|
||||
...(swotData.threats?.map((threat: string) => ({
|
||||
risk: threat,
|
||||
probability: "Medium",
|
||||
impact: "High",
|
||||
mitigation: "Strategic planning and monitoring"
|
||||
})) || []),
|
||||
{
|
||||
risk: "Market saturation",
|
||||
probability: "Medium",
|
||||
impact: "Medium",
|
||||
mitigation: "Innovation and differentiation"
|
||||
}
|
||||
],
|
||||
operational_risks: [
|
||||
{
|
||||
risk: "Resource constraints",
|
||||
probability: "Medium",
|
||||
impact: "Medium",
|
||||
mitigation: "Efficient resource allocation"
|
||||
},
|
||||
{
|
||||
risk: "Weakness areas",
|
||||
probability: "High",
|
||||
impact: "Medium",
|
||||
mitigation: "Targeted improvement programs"
|
||||
}
|
||||
],
|
||||
competitive_risks: [
|
||||
{
|
||||
risk: "Market competition",
|
||||
probability: "High",
|
||||
impact: "Medium",
|
||||
mitigation: "Leverage competitive advantages"
|
||||
},
|
||||
{
|
||||
risk: "Strength erosion",
|
||||
probability: "Medium",
|
||||
impact: "High",
|
||||
mitigation: "Continuous improvement and innovation"
|
||||
}
|
||||
]
|
||||
},
|
||||
swot_risk_mapping: {
|
||||
strength_risks: "Risk of over-reliance on current strengths",
|
||||
weakness_risks: "Risk of weakness exploitation by competitors",
|
||||
opportunity_risks: "Risk of missing market opportunities",
|
||||
threat_risks: "Risk of threat materialization"
|
||||
},
|
||||
risk_mitigation_strategies: {
|
||||
based_on_strengths: "Leverage strengths to mitigate threats",
|
||||
based_on_opportunities: "Use opportunities to address weaknesses",
|
||||
based_on_weaknesses: "Develop improvement plans for weak areas",
|
||||
based_on_threats: "Create contingency plans for identified threats"
|
||||
},
|
||||
mitigation_strategies: swotData.mitigation_strategies || []
|
||||
},
|
||||
// Enhanced summary with SWOT context
|
||||
summary: {
|
||||
estimated_roi: "20-30%",
|
||||
implementation_timeline: "12 months",
|
||||
risk_level: "Medium",
|
||||
success_probability: `${swotData.overall_score || 75}%`,
|
||||
next_step: "Review strategy and generate content calendar",
|
||||
swot_highlights: {
|
||||
key_strengths: (swotData.strengths || []).slice(0, 2),
|
||||
key_opportunities: (swotData.opportunities || []).slice(0, 2),
|
||||
primary_risks: (swotData.threats || []).slice(0, 2)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if strategy data has full 5-component structure
|
||||
*/
|
||||
export const hasFullStructure = (comprehensiveAnalysis: any): boolean => {
|
||||
return !!(comprehensiveAnalysis.strategic_insights ||
|
||||
comprehensiveAnalysis.competitive_analysis ||
|
||||
comprehensiveAnalysis.performance_predictions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get strategy name from metadata
|
||||
*/
|
||||
export const getStrategyName = (strategyData: StrategyData | null): string => {
|
||||
return strategyData?.strategy_metadata?.strategy_name ||
|
||||
strategyData?.metadata?.strategy_name ||
|
||||
'AI-Generated Strategy';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get strategy generation date
|
||||
*/
|
||||
export const getStrategyGenerationDate = (strategyData: StrategyData | null): string => {
|
||||
const timestamp = strategyData?.strategy_metadata?.generated_at ||
|
||||
strategyData?.strategy_metadata?.generation_timestamp ||
|
||||
strategyData?.metadata?.generated_at ||
|
||||
strategyData?.metadata?.generation_timestamp || '';
|
||||
|
||||
return new Date(timestamp).toLocaleDateString();
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
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
@@ -56,6 +56,7 @@ import {
|
||||
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
|
||||
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
||||
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
|
||||
import StrategyIntelligenceTab from '../components/StrategyIntelligenceTab';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -461,110 +462,7 @@ const ContentStrategyTab: React.FC = () => {
|
||||
|
||||
{/* Strategic Intelligence Tab */}
|
||||
<TabPanel value={tabValue} index={1}>
|
||||
{dataLoading.strategicIntelligence ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : strategicIntelligence && strategicIntelligence.market_positioning ? (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Market Positioning
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={strategicIntelligence.market_positioning.score || 0}
|
||||
size={60}
|
||||
color="primary"
|
||||
/>
|
||||
<Typography variant="h4" sx={{ ml: 2 }}>
|
||||
{strategicIntelligence.market_positioning.score || 0}/100
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Strengths:
|
||||
</Typography>
|
||||
<List dense>
|
||||
{(strategicIntelligence.market_positioning.strengths || []).map((strength: string, index: number) => (
|
||||
<ListItem key={index}>
|
||||
<ListItemIcon>
|
||||
<CheckCircleIcon color="success" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={strength} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Competitive Advantages
|
||||
</Typography>
|
||||
{(strategicIntelligence.competitive_advantages || []).map((advantage: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{advantage.advantage}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
|
||||
<Chip
|
||||
label={advantage.impact}
|
||||
color={advantage.impact === 'High' ? 'success' : 'primary'}
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={advantage.implementation}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Strategic Risks
|
||||
</Typography>
|
||||
{(strategicIntelligence.strategic_risks || []).map((risk: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle1">
|
||||
{risk.risk}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
|
||||
<Chip
|
||||
label={`Probability: ${risk.probability}`}
|
||||
color={risk.probability === 'High' ? 'error' : 'warning'}
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={`Impact: ${risk.impact}`}
|
||||
color={risk.impact === 'High' ? 'error' : 'warning'}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
|
||||
No strategic intelligence data available
|
||||
</Typography>
|
||||
)}
|
||||
<StrategyIntelligenceTab />
|
||||
</TabPanel>
|
||||
|
||||
{/* Keyword Research Tab */}
|
||||
|
||||
@@ -196,7 +196,7 @@ class ContentPlanningAPI {
|
||||
|
||||
async getStrategies(userId?: number) {
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
const response = await apiClient.get(`${this.baseURL}/strategies/`, { params });
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies`, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -582,6 +582,105 @@ class ContentPlanningAPI {
|
||||
return eventSource;
|
||||
}
|
||||
|
||||
// New polling-based strategy generation methods
|
||||
async startStrategyGenerationPolling(userId: number = 1, strategyName?: string, config?: any): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const payload = {
|
||||
user_id: userId,
|
||||
strategy_name: strategyName || 'Enhanced Content Strategy',
|
||||
config: config || {}
|
||||
};
|
||||
|
||||
console.log('🚀 Starting polling-based strategy generation:', payload);
|
||||
|
||||
const response = await apiClient.post(
|
||||
`${this.baseURL}/content-strategy/ai-generation/generate-comprehensive-strategy-polling`,
|
||||
payload
|
||||
);
|
||||
|
||||
console.log('✅ Strategy generation started:', response.data);
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getStrategyGenerationStatus(taskId: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/strategy-generation-status/${taskId}`);
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
// Get the latest generated strategy from polling system
|
||||
async getLatestGeneratedStrategy(userId: number = 1): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/latest-strategy`, {
|
||||
params: { user_id: userId }
|
||||
});
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
// Polling utility method
|
||||
async pollStrategyGeneration(
|
||||
taskId: string,
|
||||
onProgress: (status: any) => void,
|
||||
onComplete: (strategy: any) => void,
|
||||
onError: (error: any) => void,
|
||||
pollInterval: number = 10000, // 10 seconds
|
||||
maxAttempts: number = 36 // 6 minutes max (36 * 10 seconds)
|
||||
): Promise<void> {
|
||||
let attempts = 0;
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
attempts++;
|
||||
console.log(`📊 Polling attempt ${attempts}/${maxAttempts} for task: ${taskId}`);
|
||||
|
||||
const status = await this.getStrategyGenerationStatus(taskId);
|
||||
|
||||
// Call progress callback
|
||||
onProgress(status);
|
||||
|
||||
// Check if completed
|
||||
if (status.status === 'completed') {
|
||||
console.log('✅ Strategy generation completed:', status);
|
||||
onComplete(status.strategy);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if failed
|
||||
if (status.status === 'failed') {
|
||||
console.error('❌ Strategy generation failed:', status.error);
|
||||
onError(status.error || 'Strategy generation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if max attempts reached
|
||||
if (attempts >= maxAttempts) {
|
||||
console.warn('⏰ Max polling attempts reached, checking final status...');
|
||||
const finalStatus = await this.getStrategyGenerationStatus(taskId);
|
||||
|
||||
if (finalStatus.status === 'completed') {
|
||||
onComplete(finalStatus.strategy);
|
||||
} else {
|
||||
onError('Strategy generation timeout. The process may still be running in the background.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue polling
|
||||
setTimeout(poll, pollInterval);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error polling strategy generation status:', error);
|
||||
onError(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Start polling
|
||||
poll();
|
||||
}
|
||||
|
||||
async updateEnhancedStrategy(id: string, updates: any): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.put(`${this.baseURL}/enhanced-strategies/${id}`, updates);
|
||||
|
||||
Reference in New Issue
Block a user