diff --git a/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py b/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py
index 6178d124..75c2c835 100644
--- a/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py
+++ b/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py
@@ -662,6 +662,16 @@ async def get_latest_generated_strategy(
logger.info(f"๐ Querying database for strategies with user_id: {user_id}")
# Query for the most recent strategy with comprehensive AI analysis
+ # First, let's see all strategies for this user
+ all_strategies = db.query(EnhancedContentStrategy).filter(
+ EnhancedContentStrategy.user_id == user_id
+ ).order_by(desc(EnhancedContentStrategy.created_at)).all()
+
+ logger.info(f"๐ Found {len(all_strategies)} total strategies for user {user_id}")
+ for i, strategy in enumerate(all_strategies):
+ logger.info(f" Strategy {i+1}: ID={strategy.id}, name={strategy.name}, created_at={strategy.created_at}, has_comprehensive_ai_analysis={strategy.comprehensive_ai_analysis is not None}")
+
+ # Now query for the most recent strategy with comprehensive AI analysis
latest_db_strategy = db.query(EnhancedContentStrategy).filter(
EnhancedContentStrategy.user_id == user_id,
EnhancedContentStrategy.comprehensive_ai_analysis.isnot(None)
@@ -682,9 +692,31 @@ async def get_latest_generated_strategy(
}
)
else:
- logger.info(f"โ ๏ธ No strategy found in database for user: {user_id}")
- if latest_db_strategy:
- logger.info(f"๐ Strategy found but no comprehensive_ai_analysis: {latest_db_strategy.id}")
+ logger.info(f"โ ๏ธ No strategy with comprehensive_ai_analysis found in database for user: {user_id}")
+
+ # Fallback: Try to get the most recent strategy regardless of comprehensive_ai_analysis
+ fallback_strategy = db.query(EnhancedContentStrategy).filter(
+ EnhancedContentStrategy.user_id == user_id
+ ).order_by(desc(EnhancedContentStrategy.created_at)).first()
+
+ if fallback_strategy:
+ logger.info(f"๐ Found fallback strategy: ID={fallback_strategy.id}, name={fallback_strategy.name}")
+ logger.info(f"๐ Fallback strategy has ai_recommendations: {fallback_strategy.ai_recommendations is not None}")
+
+ # Try to use ai_recommendations as the strategy data
+ if fallback_strategy.ai_recommendations:
+ logger.info(f"โ Using ai_recommendations as strategy data for fallback strategy {fallback_strategy.id}")
+ return ResponseBuilder.create_success_response(
+ message="Latest generated strategy retrieved successfully from database (fallback)",
+ data={
+ "user_id": user_id,
+ "strategy": fallback_strategy.ai_recommendations,
+ "completed_at": fallback_strategy.created_at.isoformat(),
+ "strategy_id": fallback_strategy.id
+ }
+ )
+ else:
+ logger.info(f"โ ๏ธ Fallback strategy has no ai_recommendations either")
else:
logger.info(f"๐ No strategy record found at all for user: {user_id}")
except Exception as db_error:
diff --git a/backend/api/content_planning/api/enhanced_strategy_routes.py b/backend/api/content_planning/api/enhanced_strategy_routes.py
index 41ad4c41..61e405bf 100644
--- a/backend/api/content_planning/api/enhanced_strategy_routes.py
+++ b/backend/api/content_planning/api/enhanced_strategy_routes.py
@@ -1141,7 +1141,7 @@ async def stream_autofill_refresh(
async def refresh_autofill(
user_id: Optional[int] = Query(None, description="User ID to build auto-fill for"),
use_ai: bool = Query(True, description="Use AI augmentation during refresh"),
- ai_only: bool = Query(False, description="AI-first refresh: return AI overrides when available"),
+ ai_only: bool = Query(True, description="๐จ CRITICAL: Force AI-only generation to ensure real AI values"),
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Non-stream endpoint to return a fresh auto-fill payload (no DB writes)."""
@@ -1149,7 +1149,8 @@ async def refresh_autofill(
actual_user_id = user_id or 1
started = datetime.utcnow()
refresh_service = AutoFillRefreshService(db)
- payload = await refresh_service.build_fresh_payload_with_transparency(actual_user_id, use_ai=use_ai, ai_only=ai_only)
+ # ๐จ CRITICAL: Force AI-only generation for refresh to ensure real AI values
+ payload = await refresh_service.build_fresh_payload_with_transparency(actual_user_id, use_ai=True, ai_only=True)
total_ms = int((datetime.utcnow() - started).total_seconds() * 1000)
meta = payload.get('meta') or {}
meta.update({'http_total_ms': total_ms, 'http_started_at': started.isoformat()})
diff --git a/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py b/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py
index e415fa7e..69a072ab 100644
--- a/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py
+++ b/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py
@@ -68,23 +68,36 @@ class AIStrategyGenerator:
try:
self.logger.info(f"๐ Generating comprehensive AI strategy for user: {user_id}")
+ # Track which components failed during generation
+ failed_components = []
+
# Step 1: Generate base strategy fields (using existing autofill system)
base_strategy = await self._generate_base_strategy_fields(user_id, context)
# Step 2: Generate strategic insights and recommendations
strategic_insights = await self._generate_strategic_insights(base_strategy, context)
+ if strategic_insights.get("ai_generation_failed"):
+ failed_components.append("strategic_insights")
# Step 3: Generate competitive analysis
competitive_analysis = await self._generate_competitive_analysis(base_strategy, context)
+ if competitive_analysis.get("ai_generation_failed"):
+ failed_components.append("competitive_analysis")
# Step 4: Generate performance predictions
performance_predictions = await self._generate_performance_predictions(base_strategy, context)
+ if performance_predictions.get("ai_generation_failed"):
+ failed_components.append("performance_predictions")
# Step 5: Generate implementation roadmap
implementation_roadmap = await self._generate_implementation_roadmap(base_strategy, context)
+ if implementation_roadmap.get("ai_generation_failed"):
+ failed_components.append("implementation_roadmap")
# Step 6: Generate risk assessment
risk_assessment = await self._generate_risk_assessment(base_strategy, context)
+ if risk_assessment.get("ai_generation_failed"):
+ failed_components.append("risk_assessment")
# Step 7: Compile comprehensive strategy (NO CONTENT CALENDAR)
comprehensive_strategy = {
@@ -97,7 +110,9 @@ class AIStrategyGenerator:
"personalization_level": "high",
"ai_generated": True,
"comprehensive": True,
- "content_calendar_ready": False # Indicates calendar needs to be generated separately
+ "content_calendar_ready": False, # Indicates calendar needs to be generated separately
+ "failed_components": failed_components,
+ "generation_status": "partial" if failed_components else "complete"
},
"base_strategy": base_strategy,
"strategic_insights": strategic_insights,
@@ -114,7 +129,11 @@ class AIStrategyGenerator:
}
}
- self.logger.info(f"โ Comprehensive AI strategy generated successfully for user: {user_id}")
+ if failed_components:
+ self.logger.warning(f"โ ๏ธ Strategy generated with partial AI components. Failed: {failed_components}")
+ self.logger.info(f"โ Partial AI strategy generated successfully for user: {user_id}")
+ else:
+ self.logger.info(f"โ Comprehensive AI strategy generated successfully for user: {user_id}")
return comprehensive_strategy
except Exception as e:
@@ -223,8 +242,15 @@ class AIStrategyGenerator:
return transformed_response
except Exception as e:
- logger.error(f"โ Error generating strategic insights: {str(e)}")
- raise RuntimeError(f"Failed to generate strategic insights: {str(e)}")
+ logger.warning(f"โ ๏ธ AI service overload or error during strategic insights: {str(e)}")
+ logger.info("๐ Continuing strategy generation without strategic insights...")
+
+ # Return empty strategic insights to allow strategy generation to continue
+ return {
+ "insights": [],
+ "ai_generation_failed": True,
+ "failure_reason": str(e)
+ }
async def _generate_competitive_analysis(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]:
"""Generate competitive analysis using AI."""
@@ -300,8 +326,18 @@ class AIStrategyGenerator:
return transformed_response
except Exception as e:
- logger.error(f"โ Error generating competitive analysis: {str(e)}")
- raise RuntimeError(f"Failed to generate competitive analysis: {str(e)}")
+ logger.warning(f"โ ๏ธ AI service overload or error during competitive analysis: {str(e)}")
+ logger.info("๐ Continuing strategy generation without competitive analysis...")
+
+ # Return empty competitive analysis to allow strategy generation to continue
+ return {
+ "competitors": [],
+ "market_gaps": [],
+ "opportunities": [],
+ "recommendations": [],
+ "ai_generation_failed": True,
+ "failure_reason": str(e)
+ }
async def _generate_content_calendar(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]:
"""Generate content calendar using AI."""
@@ -502,8 +538,18 @@ class AIStrategyGenerator:
return transformed_response
except Exception as e:
- logger.error(f"โ Error generating performance predictions: {str(e)}")
- raise RuntimeError(f"Failed to generate performance predictions: {str(e)}")
+ logger.warning(f"โ ๏ธ AI service overload or error during performance predictions: {str(e)}")
+ logger.info("๐ Continuing strategy generation without performance predictions...")
+
+ # Return empty performance predictions to allow strategy generation to continue
+ return {
+ "traffic_predictions": {},
+ "engagement_predictions": {},
+ "conversion_predictions": {},
+ "roi_predictions": {},
+ "ai_generation_failed": True,
+ "failure_reason": str(e)
+ }
async def _generate_implementation_roadmap(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]:
"""Generate implementation roadmap using AI."""
@@ -600,8 +646,19 @@ class AIStrategyGenerator:
return transformed_response
except Exception as e:
- logger.error(f"โ Error generating implementation roadmap: {str(e)}")
- raise RuntimeError(f"Failed to generate implementation roadmap: {str(e)}")
+ logger.warning(f"โ ๏ธ AI service overload or error during implementation roadmap: {str(e)}")
+ logger.info("๐ Continuing strategy generation without implementation roadmap...")
+
+ # Return empty implementation roadmap to allow strategy generation to continue
+ return {
+ "phases": [],
+ "timeline": {},
+ "resource_allocation": {},
+ "success_metrics": [],
+ "total_duration": "TBD",
+ "ai_generation_failed": True,
+ "failure_reason": str(e)
+ }
async def _generate_risk_assessment(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]:
"""Generate risk assessment using AI."""
@@ -738,8 +795,29 @@ class AIStrategyGenerator:
return transformed_response
except Exception as e:
- logger.error(f"โ Error generating risk assessment: {str(e)}")
- raise RuntimeError(f"Failed to generate risk assessment: {str(e)}")
+ logger.warning(f"โ ๏ธ AI service overload or error during risk assessment: {str(e)}")
+ logger.info("๐ Continuing strategy generation without risk assessment...")
+
+ # Return empty risk assessment to allow strategy generation to continue
+ return {
+ "risks": [],
+ "overall_risk_level": "Medium",
+ "risk_categories": {
+ "technical_risks": [],
+ "market_risks": [],
+ "operational_risks": [],
+ "financial_risks": []
+ },
+ "mitigation_strategies": [],
+ "monitoring_framework": {
+ "key_indicators": [],
+ "monitoring_frequency": "Monthly",
+ "escalation_procedures": [],
+ "review_schedule": "Quarterly"
+ },
+ "ai_generation_failed": True,
+ "failure_reason": str(e)
+ }
def _build_strategic_insights_prompt(self, base_strategy: Dict[str, Any], context: Dict[str, Any]) -> str:
"""Build prompt for strategic insights generation."""
diff --git a/backend/app.py b/backend/app.py
index 79fbc71a..58c5effb 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -105,12 +105,17 @@ async def rate_limit_middleware(request: Request, call_next):
client_ip = request.client.host if request.client else "unknown"
current_time = time.time()
- # Exempt streaming endpoints from rate limiting
+ # Exempt streaming endpoints and frequently called endpoints from rate limiting
path = request.url.path
if any(streaming_path in path for streaming_path in [
"/stream/strategies",
"/stream/strategic-intelligence",
- "/stream/keyword-research"
+ "/stream/keyword-research",
+ "/latest-strategy", # Exempt latest strategy endpoint from rate limiting
+ "/ai-analytics", # Exempt AI analytics endpoint from rate limiting
+ "/gap-analysis", # Exempt gap analysis endpoint from rate limiting
+ "/calendar-events", # Exempt calendar events endpoint from rate limiting
+ "/health" # Exempt health check endpoints from rate limiting
]):
# Allow streaming endpoints without rate limiting
response = await call_next(request)
@@ -125,7 +130,12 @@ async def rate_limit_middleware(request: Request, call_next):
logger.warning(f"Rate limit exceeded for {client_ip}")
return JSONResponse(
status_code=429,
- content={"detail": "Too many requests", "retry_after": RATE_LIMIT_WINDOW}
+ content={"detail": "Too many requests", "retry_after": RATE_LIMIT_WINDOW},
+ headers={
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "*",
+ "Access-Control-Allow-Headers": "*"
+ }
)
# Add current request
diff --git a/frontend/build/asset-manifest.json b/frontend/build/asset-manifest.json
deleted file mode 100644
index 8412847b..00000000
--- a/frontend/build/asset-manifest.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "files": {
- "main.css": "/static/css/main.c9966057.css",
- "main.js": "/static/js/main.bdc25f29.js",
- "index.html": "/index.html",
- "main.c9966057.css.map": "/static/css/main.c9966057.css.map",
- "main.bdc25f29.js.map": "/static/js/main.bdc25f29.js.map"
- },
- "entrypoints": [
- "static/css/main.c9966057.css",
- "static/js/main.bdc25f29.js"
- ]
-}
\ No newline at end of file
diff --git a/frontend/build/index.html b/frontend/build/index.html
deleted file mode 100644
index f3930fed..00000000
--- a/frontend/build/index.html
+++ /dev/null
@@ -1 +0,0 @@
-
Alwrity - AI Content Creation Platform
\ No newline at end of file
diff --git a/frontend/build/static/css/main.c9966057.css b/frontend/build/static/css/main.c9966057.css
deleted file mode 100644
index 766f3e46..00000000
--- a/frontend/build/static/css/main.c9966057.css
+++ /dev/null
@@ -1,2 +0,0 @@
-@import url(https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap);html{scroll-behavior:smooth}:focus{outline:2px solid #667eea;outline-offset:2px}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}@keyframes shimmer{0%{background-position:-200px 0}to{background-position:calc(200px + 100%) 0}}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes glow{0%,to{box-shadow:0 0 5px #667eea80}50%{box-shadow:0 0 20px #667eeacc}}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-weight:400;line-height:1.6}button{transition:all .3s cubic-bezier(.4,0,.2,1)}button:active{transform:scale(.98)}.MuiCard-root{transition:all .3s cubic-bezier(.4,0,.2,1)}.MuiCard-root:hover{transform:translateY(-2px)}.glass-effect{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);background:#ffffff1a;border:1px solid #fff3;border-radius:12px}.glass-effect-hover:hover{background:#ffffff26;border:1px solid #ffffff4d;box-shadow:0 8px 32px #0000001a;transform:translateY(-4px)}.MuiTextField-root .MuiOutlinedInput-root{transition:all .3s ease}.MuiTextField-root .MuiOutlinedInput-root:hover{transform:translateY(-1px)}.MuiLinearProgress-root{border-radius:4px;overflow:hidden}.MuiLinearProgress-bar{transition:transform .3s ease}.MuiChip-root{font-weight:600;transition:all .3s ease}.MuiChip-root:hover{transform:translateY(-1px)}.MuiAlert-root{border-radius:8px;font-weight:500}.MuiTooltip-tooltip{border-radius:6px;font-weight:500}.MuiStepLabel-root{transition:all .3s ease}.MuiStepLabel-root:hover{transform:scale(1.05)}.MuiIconButton-root{transition:all .3s cubic-bezier(.4,0,.2,1)}.MuiIconButton-root:hover{transform:scale(1.1)}.MuiPaper-root{transition:all .3s cubic-bezier(.4,0,.2,1)}a{text-decoration:none;transition:all .3s ease}a:hover{text-decoration:underline}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#a8a8a8}.loading-shimmer{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0 50%,#f0f0f0 75%);background-size:200px 100%}.focus-ring{position:relative}.focus-ring:after{border:2px solid #0000;border-radius:inherit;bottom:-2px;content:"";left:-2px;position:absolute;right:-2px;top:-2px;transition:border-color .3s ease}.focus-ring:focus:after{border-color:#667eea}.gradient-bg{background:linear-gradient(135deg,#667eea,#764ba2)}.gradient-bg-secondary{background:linear-gradient(135deg,#8b5cf6,#7c3aed)}.gradient-bg-premium{background:linear-gradient(135deg,#667eea,#764ba2 50%,#f093fb)}.shadow-sm{box-shadow:0 1px 2px 0 #0000000d}.shadow-md{box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.shadow-lg{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}.shadow-xl{box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a}.shadow-2xl{box-shadow:0 25px 50px -12px #00000040}.glass-shadow{box-shadow:0 8px 32px #0000001a}.glass-shadow-hover:hover{box-shadow:0 16px 48px #00000026}.animate-float{animation:float 3s ease-in-out infinite}.animate-glow{animation:glow 2s ease-in-out infinite}.animate-fade-in{animation:fadeIn .6s ease-out}.animate-slide-up{animation:slideUp .6s ease-out}.hover-lift:hover{transform:translateY(-4px)}.hover-lift:hover,.hover-scale:hover{transition:transform .3s cubic-bezier(.4,0,.2,1)}.hover-scale:hover{transform:scale(1.05)}.hover-glow:hover{box-shadow:0 0 20px #667eea4d;transition:box-shadow .3s ease}.text-gradient{-webkit-text-fill-color:#0000;background:linear-gradient(135deg,#667eea,#764ba2);-webkit-background-clip:text;background-clip:text}.text-shadow{text-shadow:0 2px 4px #0000004d}@media (max-width:768px){.mobile-hidden{display:none}.mobile-full{width:100%!important}.mobile-text-center{text-align:center}}@media (max-width:480px){.mobile-padding{padding:16px}}@media print{.no-print{display:none!important}}@media (prefers-color-scheme:dark){.auto-dark{background:#1a1a1a;color:#fff}}@media (prefers-contrast:high){.high-contrast{border:2px solid}}@media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
-/*# sourceMappingURL=main.c9966057.css.map*/
\ No newline at end of file
diff --git a/frontend/build/static/css/main.c9966057.css.map b/frontend/build/static/css/main.c9966057.css.map
deleted file mode 100644
index 3607ff04..00000000
--- a/frontend/build/static/css/main.c9966057.css.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"static/css/main.c9966057.css","mappings":"sGAKA,KACE,sBACF,CAGA,OACE,yBAA0B,CAC1B,kBACF,CAGA,kBACE,GACE,SAAU,CACV,0BACF,CACA,GACE,SAAU,CACV,uBACF,CACF,CAEA,mBACE,GACE,SAAU,CACV,0BACF,CACA,GACE,SAAU,CACV,uBACF,CACF,CAEA,iBACE,MACE,SACF,CACA,IACE,UACF,CACF,CAEA,mBACE,GACE,4BACF,CACA,GACE,wCACF,CACF,CAEA,iBACE,MACE,uBACF,CACA,IACE,2BACF,CACF,CAEA,gBACE,MACE,4BACF,CACA,IACE,6BACF,CACF,CAGA,KAEE,kCAAmC,CACnC,iCAAkC,CAFlC,qGAA0H,CAI1H,eAAgB,CADhB,eAEF,CAGA,OACE,0CACF,CAEA,cACE,oBACF,CAGA,cACE,0CACF,CAEA,oBACE,0BACF,CAGA,cAEE,kCAA2B,CAA3B,0BAA2B,CAD3B,oBAAoC,CAEpC,sBAA0C,CAC1C,kBACF,CAEA,0BACE,oBAAqC,CACrC,0BAA0C,CAE1C,+BAAyC,CADzC,0BAEF,CAGA,0CACE,uBACF,CAEA,gDACE,0BACF,CAGA,wBACE,iBAAkB,CAClB,eACF,CAEA,uBACE,6BACF,CAGA,cACE,eAAgB,CAChB,uBACF,CAEA,oBACE,0BACF,CAGA,eACE,iBAAkB,CAClB,eACF,CAGA,oBACE,iBAAkB,CAClB,eACF,CAGA,mBACE,uBACF,CAEA,yBACE,qBACF,CAGA,oBACE,0CACF,CAEA,0BACE,oBACF,CAGA,eACE,0CACF,CAGA,EACE,oBAAqB,CACrB,uBACF,CAEA,QACE,yBACF,CAGA,oBACE,SACF,CAEA,0BACE,kBAAmB,CACnB,iBACF,CAEA,0BACE,kBAAmB,CACnB,iBACF,CAEA,gCACE,kBACF,CAGA,iBAGE,+BAAgC,CAFhC,qEAAyE,CACzE,0BAEF,CAGA,YACE,iBACF,CAEA,kBAOE,sBAA6B,CAC7B,qBAAsB,CAFtB,WAAY,CALZ,UAAW,CAGX,SAAU,CAFV,iBAAkB,CAGlB,UAAW,CAFX,QAAS,CAMT,gCACF,CAEA,wBACE,oBACF,CAGA,aACE,kDACF,CAEA,uBACE,kDACF,CAEA,qBACE,8DACF,CAGA,WACE,gCACF,CAEA,WACE,4DACF,CAEA,WACE,8DACF,CAEA,WACE,gEACF,CAEA,YACE,sCACF,CAGA,cACE,+BACF,CAEA,0BACE,gCACF,CAGA,eACE,uCACF,CAEA,cACE,sCACF,CAEA,iBACE,6BACF,CAEA,kBACE,8BACF,CAGA,kBACE,0BAEF,CAEA,qCAHE,gDAMF,CAHA,mBACE,qBAEF,CAEA,kBACE,6BAA6C,CAC7C,8BACF,CAGA,eAGE,6BAAoC,CAFpC,kDAA6D,CAC7D,4BAA6B,CAE7B,oBACF,CAEA,aACE,+BACF,CAGA,yBACE,eACE,YACF,CAEA,aACE,oBACF,CAEA,oBACE,iBACF,CACF,CAEA,yBACE,gBACE,YACF,CACF,CAGA,aACE,UACE,sBACF,CACF,CAGA,mCACE,WACE,kBAAmB,CACnB,UACF,CACF,CAGA,+BACE,eACE,gBACF,CACF,CAGA,uCACE,EACE,kCAAqC,CACrC,qCAAuC,CACvC,mCACF,CACF","sources":["styles/global.css"],"sourcesContent":["/* Global Styles for Alwrity Onboarding */\r\n\r\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');\r\n\r\n/* Smooth scrolling */\r\nhtml {\r\n scroll-behavior: smooth;\r\n}\r\n\r\n/* Better focus styles */\r\n*:focus {\r\n outline: 2px solid #667eea;\r\n outline-offset: 2px;\r\n}\r\n\r\n/* Enhanced custom animations */\r\n@keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n transform: translateY(10px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n@keyframes slideUp {\r\n from {\r\n opacity: 0;\r\n transform: translateY(20px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n@keyframes pulse {\r\n 0%, 100% {\r\n opacity: 1;\r\n }\r\n 50% {\r\n opacity: 0.7;\r\n }\r\n}\r\n\r\n@keyframes shimmer {\r\n 0% {\r\n background-position: -200px 0;\r\n }\r\n 100% {\r\n background-position: calc(200px + 100%) 0;\r\n }\r\n}\r\n\r\n@keyframes float {\r\n 0%, 100% {\r\n transform: translateY(0px);\r\n }\r\n 50% {\r\n transform: translateY(-10px);\r\n }\r\n}\r\n\r\n@keyframes glow {\r\n 0%, 100% {\r\n box-shadow: 0 0 5px rgba(102, 126, 234, 0.5);\r\n }\r\n 50% {\r\n box-shadow: 0 0 20px rgba(102, 126, 234, 0.8);\r\n }\r\n}\r\n\r\n/* Enhanced typography */\r\nbody {\r\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n line-height: 1.6;\r\n font-weight: 400;\r\n}\r\n\r\n/* Better button interactions */\r\nbutton {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\nbutton:active {\r\n transform: scale(0.98);\r\n}\r\n\r\n/* Enhanced card shadows and effects */\r\n.MuiCard-root {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.MuiCard-root:hover {\r\n transform: translateY(-2px);\r\n}\r\n\r\n/* Glassmorphism effects */\r\n.glass-effect {\r\n background: rgba(255, 255, 255, 0.1);\r\n backdrop-filter: blur(20px);\r\n border: 1px solid rgba(255, 255, 255, 0.2);\r\n border-radius: 12px;\r\n}\r\n\r\n.glass-effect-hover:hover {\r\n background: rgba(255, 255, 255, 0.15);\r\n border: 1px solid rgba(255, 255, 255, 0.3);\r\n transform: translateY(-4px);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n/* Better form field styling */\r\n.MuiTextField-root .MuiOutlinedInput-root {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.MuiTextField-root .MuiOutlinedInput-root:hover {\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* Enhanced progress bars */\r\n.MuiLinearProgress-root {\r\n border-radius: 4px;\r\n overflow: hidden;\r\n}\r\n\r\n.MuiLinearProgress-bar {\r\n transition: transform 0.3s ease;\r\n}\r\n\r\n/* Better chip styling */\r\n.MuiChip-root {\r\n font-weight: 600;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.MuiChip-root:hover {\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* Enhanced alert styling */\r\n.MuiAlert-root {\r\n border-radius: 8px;\r\n font-weight: 500;\r\n}\r\n\r\n/* Better tooltip styling */\r\n.MuiTooltip-tooltip {\r\n border-radius: 6px;\r\n font-weight: 500;\r\n}\r\n\r\n/* Enhanced stepper styling */\r\n.MuiStepLabel-root {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.MuiStepLabel-root:hover {\r\n transform: scale(1.05);\r\n}\r\n\r\n/* Better icon button styling */\r\n.MuiIconButton-root {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.MuiIconButton-root:hover {\r\n transform: scale(1.1);\r\n}\r\n\r\n/* Enhanced paper styling */\r\n.MuiPaper-root {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* Better link styling */\r\na {\r\n text-decoration: none;\r\n transition: all 0.3s ease;\r\n}\r\n\r\na:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n/* Custom scrollbar */\r\n::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n::-webkit-scrollbar-track {\r\n background: #f1f1f1;\r\n border-radius: 4px;\r\n}\r\n\r\n::-webkit-scrollbar-thumb {\r\n background: #c1c1c1;\r\n border-radius: 4px;\r\n}\r\n\r\n::-webkit-scrollbar-thumb:hover {\r\n background: #a8a8a8;\r\n}\r\n\r\n/* Loading animation */\r\n.loading-shimmer {\r\n background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\r\n background-size: 200px 100%;\r\n animation: shimmer 1.5s infinite;\r\n}\r\n\r\n/* Focus ring for accessibility */\r\n.focus-ring {\r\n position: relative;\r\n}\r\n\r\n.focus-ring::after {\r\n content: '';\r\n position: absolute;\r\n top: -2px;\r\n left: -2px;\r\n right: -2px;\r\n bottom: -2px;\r\n border: 2px solid transparent;\r\n border-radius: inherit;\r\n transition: border-color 0.3s ease;\r\n}\r\n\r\n.focus-ring:focus::after {\r\n border-color: #667eea;\r\n}\r\n\r\n/* Enhanced gradient backgrounds */\r\n.gradient-bg {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n}\r\n\r\n.gradient-bg-secondary {\r\n background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);\r\n}\r\n\r\n.gradient-bg-premium {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);\r\n}\r\n\r\n/* Professional shadows */\r\n.shadow-sm {\r\n box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.shadow-md {\r\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);\r\n}\r\n\r\n.shadow-lg {\r\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.shadow-xl {\r\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.shadow-2xl {\r\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n/* Glassmorphism shadows */\r\n.glass-shadow {\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.glass-shadow-hover:hover {\r\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n/* Animation classes */\r\n.animate-float {\r\n animation: float 3s ease-in-out infinite;\r\n}\r\n\r\n.animate-glow {\r\n animation: glow 2s ease-in-out infinite;\r\n}\r\n\r\n.animate-fade-in {\r\n animation: fadeIn 0.6s ease-out;\r\n}\r\n\r\n.animate-slide-up {\r\n animation: slideUp 0.6s ease-out;\r\n}\r\n\r\n/* Hover effects */\r\n.hover-lift:hover {\r\n transform: translateY(-4px);\r\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.hover-scale:hover {\r\n transform: scale(1.05);\r\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.hover-glow:hover {\r\n box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);\r\n transition: box-shadow 0.3s ease;\r\n}\r\n\r\n/* Text effects */\r\n.text-gradient {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n background-clip: text;\r\n}\r\n\r\n.text-shadow {\r\n text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n/* Responsive utilities */\r\n@media (max-width: 768px) {\r\n .mobile-hidden {\r\n display: none;\r\n }\r\n \r\n .mobile-full {\r\n width: 100% !important;\r\n }\r\n \r\n .mobile-text-center {\r\n text-align: center;\r\n }\r\n}\r\n\r\n@media (max-width: 480px) {\r\n .mobile-padding {\r\n padding: 16px;\r\n }\r\n}\r\n\r\n/* Print styles */\r\n@media print {\r\n .no-print {\r\n display: none !important;\r\n }\r\n}\r\n\r\n/* Dark mode support */\r\n@media (prefers-color-scheme: dark) {\r\n .auto-dark {\r\n background: #1a1a1a;\r\n color: #ffffff;\r\n }\r\n}\r\n\r\n/* High contrast mode */\r\n@media (prefers-contrast: high) {\r\n .high-contrast {\r\n border: 2px solid;\r\n }\r\n}\r\n\r\n/* Reduced motion support */\r\n@media (prefers-reduced-motion: reduce) {\r\n * {\r\n animation-duration: 0.01ms !important;\r\n animation-iteration-count: 1 !important;\r\n transition-duration: 0.01ms !important;\r\n }\r\n} "],"names":[],"sourceRoot":""}
\ No newline at end of file
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx
index 78513221..8d1f5fb7 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx
@@ -100,6 +100,7 @@ const ContentStrategyBuilder: React.FC = () => {
dataSources,
inputDataPoints,
personalizationData,
+ confidenceScores,
loading,
error,
saving,
@@ -271,6 +272,48 @@ const ContentStrategyBuilder: React.FC = () => {
completionStats
});
+ // Add ref for scroll to review section
+ const reviewSectionRef = useRef(null);
+
+ // Handle scroll to review section
+ const handleScrollToReview = () => {
+ if (reviewSectionRef.current) {
+ reviewSectionRef.current.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ }
+ };
+
+ // Handle continue with present values
+ const handleContinueWithPresent = () => {
+ console.log('๐ฏ Continuing with present autofilled values');
+ // This will show the next button and allow user to proceed
+ };
+
+ // Determine if we have autofill data
+ const hasAutofillData = Object.keys(autoPopulatedFields).length > 0;
+
+ // Get last autofill time from session storage or use current time
+ const lastAutofillTime = sessionStorage.getItem('lastAutofillTime') || new Date().toISOString();
+
+ // Get data source from store
+ const dataSource = Object.keys(dataSources).length > 0 ? 'Onboarding Database' : undefined;
+
+ // Log autofill data status for debugging
+ useEffect(() => {
+ console.log('๐ StrategyBuilder: Autofill data status:', {
+ hasAutofillData,
+ autoPopulatedFieldsCount: Object.keys(autoPopulatedFields).length,
+ dataSourcesCount: Object.keys(dataSources).length,
+ inputDataPointsCount: Object.keys(inputDataPoints).length,
+ personalizationDataCount: Object.keys(personalizationData).length,
+ confidenceScoresCount: Object.keys(confidenceScores).length,
+ lastAutofillTime,
+ dataSource
+ });
+ }, [hasAutofillData, autoPopulatedFields, dataSources, inputDataPoints, personalizationData, confidenceScores, lastAutofillTime, dataSource]);
+
// Enhanced handleCreateStrategy to show enterprise modal
@@ -332,39 +375,16 @@ const ContentStrategyBuilder: React.FC = () => {
if (Object.keys(completionStats.category_completion).length > 0) {
const firstCategory = Object.keys(completionStats.category_completion)[0];
- console.log('๐ฏ Setting default category:', firstCategory);
setActiveCategory(firstCategory);
hasSetDefaultCategory.current = true;
- console.log('โ hasSetDefaultCategory set to true');
}
}, [completionStats.category_completion]); // Removed activeCategory dependency
- // Debug activeCategory changes
- useEffect(() => {
- console.log('๐ activeCategory changed to:', activeCategory);
- console.trace('๐ Stack trace for activeCategory change');
- }, [activeCategory]);
-
- // Monitor modal state for debugging
- useEffect(() => {
- console.log('๐ฏ Modal state changed - transparencyModalOpen:', transparencyModalOpen);
- }, [transparencyModalOpen]);
-
// Monitor enterprise modal state for debugging
useEffect(() => {
- console.log('๐ฏ Enterprise modal state changed - showEnterpriseModal:', showEnterpriseModal);
-
// If modal was unexpectedly closed, log it
if (!showEnterpriseModal && aiGenerating) {
- console.warn('๐ฏ WARNING: Enterprise modal closed while AI is generating');
- }
-
- // Only warn about unexpected closure if it's not due to hot reload
- if (!showEnterpriseModal && !aiGenerating) {
- const savedModalState = sessionStorage.getItem('showEnterpriseModal');
- if (savedModalState !== 'true') {
- console.warn('๐ฏ WARNING: Enterprise modal closed unexpectedly (not due to hot reload)');
- }
+ console.warn('Enterprise modal closed while AI is generating');
}
}, [showEnterpriseModal, aiGenerating]);
@@ -382,14 +402,27 @@ const ContentStrategyBuilder: React.FC = () => {
// Wrapper for the hook function to maintain the same interface
const handleConfirmCategoryReviewWrapper = () => {
- console.log('๐ง Wrapper called with activeCategory:', activeCategory);
handleConfirmCategoryReview(activeCategory);
};
return (
{/* Header with Title (Region B) - Enhanced with Futuristic Styling */}
-
+
{/* Error Alert */}
{
onShowEducationalInfo={handleShowEducationalInfo}
/>
- {/* Quick Actions */}
-
-
- Quick Actions
-
-
- }
- onClick={() => setShowAIRecommendations(true)}
- fullWidth
- >
- View AI Insights
-
- }
- onClick={() => setShowDataSourceTransparency(true)}
- fullWidth
- >
- View Data Sources
-
- }
- onClick={() => autoPopulateFromOnboarding(true)}
- fullWidth
- >
- Refresh Data
-
-
-
+
{/* Main Content Area */}
- {
- // If a specific field is provided, show field-specific data source info
- if (fieldId) {
- console.log('๐ฏ Viewing data source for field:', fieldId);
- // For now, just open the general data source transparency modal
- // In the future, this could open a field-specific modal
- setShowDataSourceTransparency(true);
- } else {
- setShowDataSourceTransparency(true);
- }
- }}
- onConfirmCategoryReview={handleConfirmCategoryReviewWrapper}
- onSetActiveCategory={setActiveCategory}
- onSetShowEducationalInfo={setShowEducationalInfo}
- getCategoryIcon={getCategoryIcon}
- getCategoryColor={getCategoryColor}
- getEducationalContent={getEducationalContent}
- />
+
+ {
+ // If a specific field is provided, show field-specific data source info
+ if (fieldId) {
+ console.log('๐ฏ Viewing data source for field:', fieldId);
+ // For now, just open the general data source transparency modal
+ // In the future, this could open a field-specific modal
+ setShowDataSourceTransparency(true);
+ } else {
+ setShowDataSourceTransparency(true);
+ }
+ }}
+ onConfirmCategoryReview={handleConfirmCategoryReviewWrapper}
+ onSetActiveCategory={setActiveCategory}
+ onSetShowEducationalInfo={setShowEducationalInfo}
+ getCategoryIcon={getCategoryIcon}
+ getCategoryColor={getCategoryColor}
+ getEducationalContent={getEducationalContent}
+ />
+
@@ -560,11 +561,17 @@ const ContentStrategyBuilder: React.FC = () => {
educationalContent={storeEducationalContent}
generationProgress={storeGenerationProgress}
onReviewStrategy={() => {
- console.log('๐ฏ User clicked "Next: Review Strategy and Create Calendar"');
setShowEducationalModal(false);
+
+ // Set flag to indicate coming from strategy builder
+ sessionStorage.setItem('fromStrategyBuilder', 'true');
+
// Navigate to content planning dashboard with Content Strategy tab active
navigate('/content-planning', {
- state: { activeTab: 0 } // 0 = Content Strategy tab
+ state: {
+ activeTab: 0, // 0 = Content Strategy tab
+ fromStrategyBuilder: true
+ }
});
}}
/>
@@ -601,10 +608,6 @@ const ContentStrategyBuilder: React.FC = () => {
open={transparencyModalOpen}
onClose={() => {
setTransparencyModalOpen(false);
- // Ensure form data is refreshed after modal closes
- console.log('๐ฏ Modal closed - ensuring form data is updated');
- console.log('๐ฏ Current autoPopulatedFields:', Object.keys(autoPopulatedFields || {}));
- console.log('๐ฏ Current formData keys:', Object.keys(formData || {}));
}}
autoPopulatedFields={autoPopulatedFields}
dataSources={dataSources}
@@ -621,8 +624,6 @@ const ContentStrategyBuilder: React.FC = () => {
{
- console.log('๐ฏ Enterprise modal onClose called');
- console.log('๐ฏ Current aiGenerating state:', aiGenerating);
setShowEnterpriseModal(false);
sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage
}}
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx
index c9b4d5b3..13305888 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx
@@ -426,26 +426,15 @@ const StrategicInputField: React.FC = ({
return option === value;
}}
value={(() => {
- // Debug logging for Autocomplete value parsing
- console.log('๐ฏ Autocomplete value parsing:', {
- fieldId,
- originalValue: value,
- valueType: typeof value,
- isArray: Array.isArray(value),
- availableOptions: multiSelectConfig.options
- });
-
let parsedValues: string[] = [];
if (Array.isArray(value)) {
parsedValues = value;
- console.log('๐ฏ Using array value:', parsedValues);
} else if (typeof value === 'object' && value !== null) {
// Handle object values (convert to array of keys or values)
if (typeof value === 'object' && !Array.isArray(value)) {
// Convert object to array of keys or values based on context
const objectKeys = Object.keys(value);
- const objectValues = Object.values(value);
// For traffic_sources, we might want to use the keys or convert percentages to options
if (fieldId === 'traffic_sources') {
@@ -466,12 +455,9 @@ const StrategicInputField: React.FC = ({
parsedValues = objectKeys
.map(key => trafficMapping[key.toLowerCase()])
.filter(Boolean);
-
- console.log('๐ฏ Converted object to traffic sources:', parsedValues);
} else {
// For other fields, use object keys
parsedValues = objectKeys;
- console.log('๐ฏ Using object keys:', parsedValues);
}
}
} else if (typeof value === 'string') {
@@ -480,10 +466,8 @@ const StrategicInputField: React.FC = ({
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
parsedValues = parsed;
- console.log('๐ฏ Parsed as JSON array:', parsedValues);
}
} catch (error) {
- console.log('๐ฏ JSON parse failed, trying alternative parsing');
// If not valid JSON, try to extract array-like content
if (value.startsWith('[') && value.endsWith(']')) {
// Remove outer brackets and try to parse as comma-separated
@@ -493,37 +477,48 @@ const StrategicInputField: React.FC = ({
// Remove quotes and trim
return item.trim().replace(/^["']|["']$/g, '');
}).filter(item => item);
- console.log('๐ฏ Parsed as array-like string:', parsedValues);
} else if (value.includes(',')) {
// If not array-like, try simple comma splitting
parsedValues = value.split(',').map(item => item.trim()).filter(item => item);
- console.log('๐ฏ Parsed as comma-separated string:', parsedValues);
}
}
}
- // Filter values to only include valid options
+ // Filter values to only include valid options and improve matching
const validOptions = multiSelectConfig.options || [];
const filteredValues = parsedValues.filter(val => {
// Check for exact match
if (validOptions.includes(val)) {
return true;
}
- // Check for partial match (case-insensitive)
- const partialMatch = validOptions.find(option =>
- option.toLowerCase().includes(val.toLowerCase()) ||
- val.toLowerCase().includes(option.toLowerCase())
- );
- if (partialMatch) {
- console.log('๐ฏ Found partial match:', val, '->', partialMatch);
- return true;
- }
- console.log('๐ฏ No match found for:', val);
- return false;
+ // Check for partial match (case-insensitive) with better logic
+ const partialMatch = validOptions.find(option => {
+ const optionLower = option.toLowerCase();
+ const valLower = val.toLowerCase();
+
+ // Check if option contains the value or vice versa
+ return optionLower.includes(valLower) || valLower.includes(optionLower);
+ });
+
+ return !!partialMatch;
});
- console.log('๐ฏ Final filtered values:', filteredValues);
- return filteredValues;
+ // Map partial matches to exact options
+ const mappedValues = filteredValues.map(val => {
+ const exactMatch = validOptions.find(option => option === val);
+ if (exactMatch) return exactMatch;
+
+ // Find the best partial match
+ const partialMatch = validOptions.find(option => {
+ const optionLower = option.toLowerCase();
+ const valLower = val.toLowerCase();
+ return optionLower.includes(valLower) || valLower.includes(optionLower);
+ });
+
+ return partialMatch || val;
+ });
+
+ return mappedValues;
})()}
onChange={(_, newValue) => handleChange(newValue)}
renderInput={(params) => (
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx
index 48a4aed4..456aa38f 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx
@@ -9,6 +9,7 @@ import {
Save as SaveIcon
} from '@mui/icons-material';
import { ActionButtonsProps, ActionButtonsBusinessLogicProps } from '../types/contentStrategy.types';
+import { useContentPlanningStore } from '../../../../../stores/contentPlanningStore';
// Business Logic Hook
export const useActionButtonsBusinessLogic = ({
@@ -29,11 +30,18 @@ export const useActionButtonsBusinessLogic = ({
contentPlanningApi
}: ActionButtonsBusinessLogicProps) => {
+ // Get the content planning store to cache the latest generated strategy
+ const { setLatestGeneratedStrategy } = useContentPlanningStore();
+
const handleCreateStrategy = async () => {
try {
setAIGenerating(true);
setError(null);
+ // Clear any previous cached strategy when starting new generation
+ setLatestGeneratedStrategy(null);
+ console.log('๐งน Cleared previous cached strategy for new generation');
+
console.log('Starting strategy creation...');
console.log('Current formData:', formData);
console.log('FormData ID:', formData.id);
@@ -152,6 +160,20 @@ export const useActionButtonsBusinessLogic = ({
(strategy: any) => {
console.log('โ Strategy generation completed successfully!');
setCurrentStrategy(strategy);
+
+ // Cache the latest generated strategy in the content planning store
+ console.log('๐พ Attempting to cache strategy:', {
+ strategyId: strategy?.id || strategy?.strategy_id,
+ strategyName: strategy?.name || strategy?.strategy_name,
+ hasStrategicInsights: !!strategy?.strategic_insights,
+ hasCompetitiveAnalysis: !!strategy?.competitive_analysis,
+ hasPerformancePredictions: !!strategy?.performance_predictions,
+ hasImplementationRoadmap: !!strategy?.implementation_roadmap,
+ hasRiskAssessment: !!strategy?.risk_assessment
+ });
+ setLatestGeneratedStrategy(strategy);
+ console.log('๐พ Cached latest generated strategy in store');
+
// Set progress to 100% when completion is detected
setGenerationProgress(100);
console.log('๐ฏ Setting progress to 100% in onComplete callback');
@@ -196,6 +218,11 @@ export const useActionButtonsBusinessLogic = ({
const newStrategy = await createEnhancedStrategy(strategyData);
setCurrentStrategy(newStrategy);
+
+ // Update the cache with the saved strategy
+ setLatestGeneratedStrategy(newStrategy);
+ console.log('๐พ Updated cache with saved strategy');
+
setError('Strategy saved successfully!');
} catch (err: any) {
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/AutofillDataTransparency.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/AutofillDataTransparency.tsx
new file mode 100644
index 00000000..f278b5ee
--- /dev/null
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/AutofillDataTransparency.tsx
@@ -0,0 +1,457 @@
+import React, { useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Box,
+ Typography,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Chip,
+ Grid,
+ Paper,
+ Divider,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+ Alert,
+ LinearProgress,
+ Tooltip,
+ IconButton
+} from '@mui/material';
+import {
+ ExpandMore as ExpandMoreIcon,
+ Info as InfoIcon,
+ DataUsage as DataUsageIcon,
+ Psychology as PsychologyIcon,
+ Person as PersonIcon,
+ Analytics as AnalyticsIcon,
+ CheckCircle as CheckCircleIcon,
+ Warning as WarningIcon,
+ Close as CloseIcon,
+ Refresh as RefreshIcon,
+ TrendingUp as TrendingUpIcon,
+ Security as SecurityIcon,
+ Visibility as VisibilityIcon,
+ AutoAwesome as AutoAwesomeIcon
+} from '@mui/icons-material';
+import { motion } from 'framer-motion';
+
+interface AutofillDataTransparencyProps {
+ open: boolean;
+ onClose: () => void;
+ autoPopulatedFields: Record;
+ dataSources: Record;
+ inputDataPoints: Record;
+ personalizationData: Record;
+ confidenceScores: Record;
+ lastAutofillTime?: string;
+ dataSource?: string;
+}
+
+const AutofillDataTransparency: React.FC = ({
+ open,
+ onClose,
+ autoPopulatedFields,
+ dataSources,
+ inputDataPoints,
+ personalizationData,
+ confidenceScores,
+ lastAutofillTime,
+ dataSource
+}) => {
+ const [activeAccordion, setActiveAccordion] = useState('data-sources');
+
+ const handleAccordionChange = (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => {
+ setActiveAccordion(isExpanded ? panel : false);
+ };
+
+ // Calculate data freshness
+ const getDataFreshness = () => {
+ if (!lastAutofillTime) return 'Unknown';
+ const lastUpdate = new Date(lastAutofillTime);
+ const now = new Date();
+ const diffInHours = Math.floor((now.getTime() - lastUpdate.getTime()) / (1000 * 60 * 60));
+
+ if (diffInHours < 1) return 'Just now';
+ if (diffInHours < 24) return `${diffInHours} hours ago`;
+ const diffInDays = Math.floor(diffInHours / 24);
+ return `${diffInDays} days ago`;
+ };
+
+ // Get data quality score
+ const getDataQualityScore = () => {
+ const scores = Object.values(confidenceScores);
+ if (scores.length === 0) return 0;
+ return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
+ };
+
+ // Get field count by category
+ const getFieldCountByCategory = () => {
+ const categories: Record = {};
+ Object.keys(autoPopulatedFields).forEach(fieldId => {
+ const category = fieldId.split('_')[0] || 'other';
+ categories[category] = (categories[category] || 0) + 1;
+ });
+ return categories;
+ };
+
+ const dataQualityScore = getDataQualityScore();
+ const dataFreshness = getDataFreshness();
+ const fieldCountByCategory = getFieldCountByCategory();
+
+ return (
+
+ );
+};
+
+export default AutofillDataTransparency;
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx
index 6480197b..c12aefbe 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx
@@ -1,19 +1,118 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import {
Paper,
Box,
- Typography
+ Typography,
+ Button,
+ Chip,
+ Alert,
+ Collapse,
+ Tooltip,
+ Grid,
+ LinearProgress
} from '@mui/material';
import {
- CheckCircle as CheckCircleIcon
+ CheckCircle as CheckCircleIcon,
+ Refresh as RefreshIcon,
+ PlayArrow as PlayArrowIcon,
+ Schedule as ScheduleIcon,
+ Info as InfoIcon,
+ ArrowDownward as ArrowDownwardIcon,
+ Visibility as VisibilityIcon,
+ DataUsage as DataUsageIcon,
+ TrendingUp as TrendingUpIcon,
+ Security as SecurityIcon,
+ AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
+import AutofillDataTransparency from './AutofillDataTransparency';
interface HeaderSectionProps {
autoPopulatedFields: any;
+ dataSources: any;
+ inputDataPoints: any;
+ personalizationData: any;
+ confidenceScores: any;
+ loading: boolean;
+ error: string | null;
+ onRefreshAutofill: () => void;
+ onContinueWithPresent: () => void;
+ onScrollToReview: () => void;
+ hasAutofillData: boolean;
+ lastAutofillTime?: string;
+ dataSource?: string;
}
-const HeaderSection: React.FC = ({ autoPopulatedFields }) => {
+const HeaderSection: React.FC = ({
+ autoPopulatedFields,
+ dataSources,
+ inputDataPoints,
+ personalizationData,
+ confidenceScores,
+ loading,
+ error,
+ onRefreshAutofill,
+ onContinueWithPresent,
+ onScrollToReview,
+ hasAutofillData,
+ lastAutofillTime,
+ dataSource
+}) => {
+ const [showTransparencyModal, setShowTransparencyModal] = useState(false);
+ const [showDataInfo, setShowDataInfo] = useState(false);
+ const [showNextButton, setShowNextButton] = useState(false);
+
+ // Show next button when autofill is complete
+ useEffect(() => {
+ if (hasAutofillData && Object.keys(autoPopulatedFields).length > 0) {
+ setShowNextButton(true);
+ }
+ }, [hasAutofillData, autoPopulatedFields]);
+
+ // Determine cache status and show appropriate buttons
+ const getCacheStatus = () => {
+ if (hasAutofillData && Object.keys(autoPopulatedFields).length > 0) {
+ return 'cached';
+ } else if (Object.keys(inputDataPoints).length > 0) {
+ return 'partial';
+ } else {
+ return 'empty';
+ }
+ };
+
+ const cacheStatus = getCacheStatus();
+
+ const formatTimeAgo = (timestamp: string) => {
+ const now = new Date();
+ const time = new Date(timestamp);
+ const diffInMinutes = Math.floor((now.getTime() - time.getTime()) / (1000 * 60));
+
+ if (diffInMinutes < 1) return 'Just now';
+ if (diffInMinutes < 60) return `${diffInMinutes} minutes ago`;
+ if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)} hours ago`;
+ return `${Math.floor(diffInMinutes / 1440)} days ago`;
+ };
+
+ // Calculate data quality score
+ const getDataQualityScore = () => {
+ const scores = Object.values(confidenceScores).filter((score): score is number => typeof score === 'number');
+ if (scores.length === 0) return 0;
+ return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
+ };
+
+ // Get field count by category
+ const getFieldCountByCategory = () => {
+ const categories: Record = {};
+ Object.keys(autoPopulatedFields).forEach(fieldId => {
+ const category = fieldId.split('_')[0] || 'other';
+ categories[category] = (categories[category] || 0) + 1;
+ });
+ return categories;
+ };
+
+ const dataQualityScore = getDataQualityScore();
+ const fieldCountByCategory = getFieldCountByCategory();
+
return (
= ({ autoPopulatedFields }) =>
>
= ({ autoPopulatedFields }) =>
border: '1px solid rgba(255,255,255,0.2)',
}}
>
-
-
-
- AI Content Strategy Co-pilot
-
-
- Build a comprehensive content strategy with 30+ strategic inputs
-
-
- {/* Auto-population Status - Moved to header (Region 4) */}
- {autoPopulatedFields && Object.keys(autoPopulatedFields).length > 0 && (
-
-
-
- {Object.keys(autoPopulatedFields).length} fields auto-populated from onboarding data
+
+ {/* Main Header */}
+
+
+
+ AI Content Strategy Co-pilot
+
+
+ Build a comprehensive content strategy with 30+ strategic inputs
+
+
+
+
+ {/* Enhanced Data Status Grid */}
+
+ {/* Auto-populated Fields Count */}
+
+
+
+
+
+ {Object.keys(autoPopulatedFields).length}
+
+
+ Fields Auto-populated
+
+
+
+
+
+ {/* Data Quality Score */}
+
+
+
+
+
+ {dataQualityScore}%
+
+
+ Data Quality
+
+
+
+
+
+ {/* Last Updated */}
+
+
+
+
+
+ {lastAutofillTime ? formatTimeAgo(lastAutofillTime).split(' ')[0] : 'N/A'}
+
+
+ Last Updated
+
+
+
+
+
+ {/* Data Sources */}
+
+
+
+
+
+ {Object.keys(dataSources).length}
+
+
+ Data Sources
+
+
+
+
+
+
+ {/* Data Quality Progress Bar */}
+ {dataQualityScore > 0 && (
+
+
+
+ Data Quality Score
+
+
+ {dataQualityScore}%
+ = 80
+ ? 'linear-gradient(90deg, #4caf50, #66bb6a)'
+ : dataQualityScore >= 60
+ ? 'linear-gradient(90deg, #ff9800, #ffb74d)'
+ : 'linear-gradient(90deg, #f44336, #ef5350)',
+ borderRadius: 3
+ }
+ }}
+ />
+
+ )}
+
+ {/* Enhanced Status Chips */}
+
+ {cacheStatus === 'cached' && (
+ }
+ label={`${Object.keys(autoPopulatedFields).length} fields auto-populated`}
+ sx={{
+ backgroundColor: 'rgba(76, 175, 80, 0.2)',
+ color: 'white',
+ border: '1px solid rgba(76, 175, 80, 0.3)',
+ '& .MuiChip-icon': { color: 'rgba(76, 175, 80, 0.8)' },
+ fontWeight: 500,
+ fontSize: '0.8rem'
+ }}
+ />
+ )}
+
+ {dataSource && (
+
+ }
+ label={`Source: ${dataSource}`}
+ onClick={() => setShowDataInfo(!showDataInfo)}
+ sx={{
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ color: 'white',
+ border: '1px solid rgba(255, 255, 255, 0.2)',
+ cursor: 'pointer',
+ fontWeight: 500,
+ fontSize: '0.8rem',
+ '&:hover': {
+ backgroundColor: 'rgba(255, 255, 255, 0.2)'
+ }
+ }}
+ />
+
+ )}
+
+ {/* Category Distribution Chips */}
+ {Object.keys(fieldCountByCategory).length > 0 && (
+ }
+ label={`${Object.keys(fieldCountByCategory).length} categories`}
+ sx={{
+ backgroundColor: 'rgba(156, 39, 176, 0.2)',
+ color: 'white',
+ border: '1px solid rgba(156, 39, 176, 0.3)',
+ '& .MuiChip-icon': { color: 'rgba(156, 39, 176, 0.8)' },
+ fontWeight: 500,
+ fontSize: '0.8rem'
+ }}
+ />
+ )}
+
+
+ {/* Data Source Information */}
+
+
+
+ Data Source: {dataSource || 'Onboarding Database'}
+
+
+ Input Data Points: {Object.keys(inputDataPoints).length} available
+
+
+ Adaptive Monitoring: ALwrity continuously monitors databases for new data points to ensure you have the latest information.
+
+
+
+
+ {/* Conditional Action Buttons */}
+
+ {cacheStatus === 'cached' ? (
+ // Case 1: Data exists in cache - show refresh vs continue options
+ <>
+
+ }
+ onClick={onRefreshAutofill}
+ disabled={loading}
+ sx={{
+ color: 'white',
+ borderColor: 'rgba(255, 255, 255, 0.3)',
+ '&:hover': {
+ borderColor: 'rgba(255, 255, 255, 0.5)',
+ backgroundColor: 'rgba(255, 255, 255, 0.1)'
+ }
+ }}
+ >
+ {loading ? 'Refreshing...' : 'Refresh & Autofill Inputs'}
+
+
+
+
+ }
+ onClick={onContinueWithPresent}
+ sx={{
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
+ color: 'white',
+ '&:hover': {
+ backgroundColor: 'rgba(255, 255, 255, 0.3)'
+ }
+ }}
+ >
+ Continue with Present Values
+
+
+ >
+ ) : cacheStatus === 'partial' ? (
+ // Case 2: Partial data - show refresh option
+
+ }
+ onClick={onRefreshAutofill}
+ disabled={loading}
+ sx={{
+ backgroundColor: 'rgba(255, 193, 7, 0.8)',
+ color: 'white',
+ '&:hover': {
+ backgroundColor: 'rgba(255, 193, 7, 0.9)'
+ }
+ }}
+ >
+ {loading ? 'Refreshing...' : 'Refresh & Autofill Strategy Inputs'}
+
+
+ ) : (
+ // Case 3: No data - show initial autofill
+
+ }
+ onClick={onRefreshAutofill}
+ disabled={loading}
+ sx={{
+ backgroundColor: 'rgba(76, 175, 80, 0.8)',
+ color: 'white',
+ '&:hover': {
+ backgroundColor: 'rgba(76, 175, 80, 0.9)'
+ }
+ }}
+ >
+ {loading ? 'Autofilling...' : 'Refresh & Autofill Strategy Inputs'}
+
+
+ )}
+
+ {/* Next Step Button - shown after autofill completion */}
+ {showNextButton && (
+
+ }
+ onClick={onScrollToReview}
+ sx={{
+ background: 'linear-gradient(135deg, #4caf50 0%, #66bb6a 50%, #81c784 100%)',
+ color: 'white',
+ fontWeight: 600,
+ '&:hover': {
+ background: 'linear-gradient(135deg, #66bb6a 0%, #81c784 50%, #a5d6a7 100%)',
+ transform: 'translateY(-1px)'
+ },
+ transition: 'all 0.3s ease'
+ }}
+ >
+ Next: Review Strategy Inputs & Create Strategy
+
+
+ )}
+
+ {/* Know More Details Button - shown when autofill data exists */}
+ {hasAutofillData && Object.keys(autoPopulatedFields).length > 0 && (
+
+ }
+ onClick={() => setShowTransparencyModal(true)}
+ sx={{
+ color: 'rgba(255, 255, 255, 0.8)',
+ '&:hover': {
+ color: 'white',
+ backgroundColor: 'rgba(255, 255, 255, 0.1)'
+ }
+ }}
+ >
+ Know More Details
+
+
)}
+
+ {/* Autofill Data Transparency Modal */}
+ setShowTransparencyModal(false)}
+ autoPopulatedFields={autoPopulatedFields}
+ dataSources={dataSources}
+ inputDataPoints={inputDataPoints}
+ personalizationData={personalizationData}
+ confidenceScores={confidenceScores}
+ lastAutofillTime={lastAutofillTime}
+ dataSource={dataSource}
+ />
);
};
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts
index 12ab6021..7ba4da0a 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts
@@ -35,14 +35,12 @@ export const useAIRefresh = ({
// This approach uses direct HTTP calls with visual feedback
// Open transparency modal and initialize transparency state
- console.log('๐ฏ Opening transparency modal...');
setTransparencyModalOpen(true);
setIsGenerating(true);
setStoreGenerationProgress(0);
setCurrentPhase('autofill_initialization');
clearTransparencyMessages();
addTransparencyMessage('Starting strategy inputs generation process...');
- console.log('๐ฏ Modal state set, transparency initialized');
setAIGenerating(true);
setIsRefreshing(true);
@@ -68,12 +66,6 @@ export const useAIRefresh = ({
const transparencyInterval = setInterval(() => {
if (messageIndex < transparencyMessages.length) {
const message = transparencyMessages[messageIndex];
- console.log('๐ฏ Raw Polling Message:', {
- type: message.type,
- message: message.message,
- progress: message.progress,
- timestamp: new Date().toISOString()
- });
setCurrentPhase(message.type);
addTransparencyMessage(message.message);
setStoreGenerationProgress(message.progress);
@@ -85,80 +77,20 @@ export const useAIRefresh = ({
}, 2000); // Send a message every 2 seconds for better UX
// Call the non-streaming refresh endpoint (Polling-based approach)
- console.log('๐ฏ Calling AI refresh endpoint (Polling-based)...');
const response = await contentPlanningApi.refreshAutofill(1, true, true);
- console.log('๐ฏ Raw Polling Response:', {
- success: !!response,
- hasData: !!response?.fields,
- responseStructure: {
- hasFieldsProperty: !!response?.fields,
- hasSourcesProperty: !!response?.sources,
- hasMetaProperty: !!response?.meta
- },
- fieldsCount: Object.keys(response?.fields || {}).length,
- sourcesCount: Object.keys(response?.sources || {}).length,
- meta: response?.meta || {},
- timestamp: new Date().toISOString()
- });
// Clear the transparency interval since we got the response
clearInterval(transparencyInterval);
// Process the response
- // The API method already returns the extracted data, not the full response
if (response) {
- // Debug the actual response structure
- console.log('๐ฏ Raw response structure:', {
- responseType: typeof response,
- responseKeys: Object.keys(response),
- hasFieldsProperty: response?.hasOwnProperty('fields'),
- hasSourcesProperty: response?.hasOwnProperty('sources'),
- hasMetaProperty: response?.hasOwnProperty('meta')
- });
-
- // Debug the actual response data
- console.log('๐ฏ Raw response:', response);
- console.log('๐ฏ Raw response.fields:', response?.fields);
- console.log('๐ฏ Raw response.sources:', response?.sources);
- console.log('๐ฏ Raw response.meta:', response?.meta);
-
- // The API method already returns the extracted payload from ResponseBuilder
- // So response is already the payload with fields, sources, meta, etc.
const payload = response;
-
- // Debug the payload structure
- console.log('๐ฏ Payload structure:', {
- payloadType: typeof payload,
- payloadKeys: Object.keys(payload),
- hasFieldsProperty: payload?.hasOwnProperty('fields'),
- hasSourcesProperty: payload?.hasOwnProperty('sources'),
- hasMetaProperty: payload?.hasOwnProperty('meta'),
- fieldsKeys: payload?.fields ? Object.keys(payload.fields) : 'no fields'
- });
-
const fields = payload.fields || {};
const sources = payload.sources || {};
const inputDataPoints = payload.input_data_points || {};
const meta = payload.meta || {};
- // Debug the extracted data
- console.log('๐ฏ Extracted fields:', fields);
- console.log('๐ฏ Extracted sources:', sources);
- console.log('๐ฏ Extracted inputDataPoints:', inputDataPoints);
- console.log('๐ฏ Extracted meta:', meta);
- console.log('๐ฏ Fields count:', Object.keys(fields).length);
- console.log('๐ฏ Sources count:', Object.keys(sources).length);
- console.log('๐ฏ InputDataPoints count:', Object.keys(inputDataPoints).length);
-
- console.log('๐ฏ AI Refresh Result - Payload:', payload);
- console.log('๐ฏ AI Refresh Result - Fields:', fields);
- console.log('๐ฏ AI Refresh Result - Meta:', meta);
- console.log('๐ฏ Fields structure check:', {
- fieldsType: typeof fields,
- fieldsKeys: Object.keys(fields),
- sampleField: fields[Object.keys(fields)[0]],
- hasValueProperty: fields[Object.keys(fields)[0]]?.hasOwnProperty('value')
- });
+ console.log('๐ฏ AI Refresh - Generated fields:', Object.keys(fields).length);
// ๐จ CRITICAL: Check if AI generation failed
if (meta.error || !meta.ai_used) {
@@ -187,11 +119,11 @@ export const useAIRefresh = ({
return;
}
- console.log(`โ AI generation successful - generated ${fieldsCount} fields, AI overrides: ${meta.ai_overrides_count || 0}`);
+ console.log(`โ AI generation successful - ${fieldsCount} fields generated`);
// ๐จ CRITICAL: Validate data source (only check for explicit failure states)
if (meta.data_source === 'ai_generation_failed' || meta.data_source === 'ai_generation_error') {
- console.error('โ Invalid data source:', meta.data_source);
+ console.error('โ AI generation failed:', meta.data_source);
setError(`AI generation failed: ${meta.error || 'Invalid data source. Please try again.'}`);
setTransparencyModalOpen(false);
setAIGenerating(false);
@@ -202,51 +134,34 @@ export const useAIRefresh = ({
return;
}
- console.log('โ AI generation successful - processing real AI data');
-
const fieldValues: Record = {};
const confidenceScores: Record = {};
Object.keys(fields).forEach((fieldId) => {
const fieldData = fields[fieldId];
- console.log(`๐ฏ Processing field ${fieldId}:`, fieldData);
if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) {
fieldValues[fieldId] = fieldData.value;
- console.log(`โ Field ${fieldId} value extracted:`, fieldData.value);
// Extract confidence score if available
if (fieldData.confidence) {
confidenceScores[fieldId] = fieldData.confidence;
- console.log(`๐ฏ Field ${fieldId} confidence: ${fieldData.confidence}`);
- }
-
- // Extract personalization data if available
- if (fieldData.personalization_data) {
- console.log(`๐ฏ Field ${fieldId} personalization:`, fieldData.personalization_data);
}
} else {
- console.warn(`โ ๏ธ Field ${fieldId} has invalid structure:`, fieldData);
+ console.warn(`โ ๏ธ Field ${fieldId} has invalid structure`);
}
});
- console.log('๐ฏ Processed field values:', Object.keys(fieldValues));
- console.log('๐ฏ Confidence scores:', confidenceScores);
- console.log('๐ฏ Field values details:', fieldValues);
-
- // Update the store with the new data
+ // Update the store with the new data - COMPLETELY REPLACE old data
useStrategyBuilderStore.setState((state) => {
const newState = {
- autoPopulatedFields: { ...state.autoPopulatedFields, ...fieldValues },
- dataSources: { ...state.dataSources, ...sources },
- inputDataPoints: { ...state.inputDataPoints, ...inputDataPoints },
- confidenceScores: { ...state.confidenceScores, ...confidenceScores },
- formData: { ...state.formData, ...fieldValues }
+ autoPopulatedFields: fieldValues, // ๐จ CRITICAL: Replace, don't merge
+ dataSources: sources, // ๐จ CRITICAL: Replace, don't merge
+ inputDataPoints: inputDataPoints, // ๐จ CRITICAL: Replace, don't merge
+ confidenceScores: confidenceScores, // ๐จ CRITICAL: Replace, don't merge
+ formData: { ...state.formData, ...fieldValues } // Keep existing manual edits
};
- console.log('๐ฏ Updated store state:', newState);
- console.log('๐ฏ Field values being added:', fieldValues);
- console.log('๐ฏ Confidence scores being added:', confidenceScores);
- console.log('๐ฏ Store autoPopulatedFields count:', Object.keys(newState.autoPopulatedFields).length);
+ console.log('โ Store updated with fresh AI data:', Object.keys(fieldValues).length, 'fields');
return newState;
});
@@ -263,17 +178,14 @@ export const useAIRefresh = ({
setRefreshProgress(100);
}, 100);
+ // Update session storage with fresh autofill timestamp
+ sessionStorage.setItem('lastAutofillTime', new Date().toISOString());
+
// Reset refresh state
setAIGenerating(false);
setIsRefreshing(false);
setIsGenerating(false);
- console.log('๐ฏ Polling-based AI refresh completed successfully!', {
- fieldsGenerated: Object.keys(fieldValues).length,
- confidenceScoresCount: Object.keys(confidenceScores).length,
- dataSourcesCount: Object.keys(sources).length,
- approach: 'Polling (No SSE)',
- timestamp: new Date().toISOString()
- });
+ console.log('โ AI refresh completed:', Object.keys(fieldValues).length, 'fields generated');
} else {
throw new Error('Invalid response from AI refresh endpoint');
}
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts
index d303b14a..6aa94086 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts
@@ -15,10 +15,24 @@ export const useAutoPopulation = ({
// Auto-populate from onboarding on first load
useEffect(() => {
if (!autoPopulateAttempted && !isAutoPopulating) {
+ console.log('๐ useAutoPopulation: Triggering initial auto-population');
+ console.log('๐ useAutoPopulation: Current completion stats:', {
+ totalFields: completionStats?.total_fields || 0,
+ filledFields: completionStats?.filled_fields || 0,
+ completionPercentage: completionStats?.completion_percentage || 0
+ });
+
setIsAutoPopulating(true);
autoPopulateFromOnboarding();
setAutoPopulateAttempted(true);
setIsAutoPopulating(false);
+
+ console.log('โ useAutoPopulation: Auto-population triggered successfully');
+ } else {
+ console.log('โธ๏ธ useAutoPopulation: Auto-population skipped', {
+ autoPopulateAttempted,
+ isAutoPopulating
+ });
}
}, [autoPopulateAttempted, isAutoPopulating]); // Removed autoPopulateFromOnboarding from dependencies
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts
index 300ed96a..631648db 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts
@@ -49,30 +49,18 @@ export const useCategoryReview = ({ completionStats, setError, setActiveCategory
// Use the updated reviewedCategories state that includes the current category
const updatedReviewedCategories = new Set([...Array.from(reviewedCategories), activeCategory]);
- console.log('๐ Navigation Debug:', {
- activeCategory,
- currentIndex,
- allCategories,
- reviewedCategories: Array.from(reviewedCategories),
- updatedReviewedCategories: Array.from(updatedReviewedCategories)
- });
-
const nextUnreviewedCategory = allCategories.find((categoryId, index) => {
if (index <= currentIndex) return false;
return !updatedReviewedCategories.has(categoryId);
});
- console.log('๐ฏ Next Category Found:', nextUnreviewedCategory);
-
if (nextUnreviewedCategory) {
// Actually navigate to the next category
- console.log('๐ Navigating to:', nextUnreviewedCategory);
setActiveCategory(nextUnreviewedCategory);
setCategoryCompletionMessage(`๐ฏ Moving to next category: ${nextUnreviewedCategory.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}`);
} else {
- console.log('๐ All categories reviewed!');
setCategoryCompletionMessage('๐ All categories reviewed and confirmed! You can now create your strategy.');
}
}, 1500);
diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts
index 12dd4faa..09f260ad 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts
+++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts
@@ -22,51 +22,42 @@ export const useModalManagement = ({
// Monitor aiGenerating state for debugging
useEffect(() => {
- console.log('๐ฏ useModalManagement: aiGenerating state changed:', aiGenerating);
+ // Removed verbose logging for cleaner console
}, [aiGenerating]);
// Handle proceed with current strategy (30 fields)
const handleProceedWithCurrentStrategy = async () => {
- console.log('๐ฏ User clicked "Proceed with Current Strategy"');
setShowEnterpriseModal(false);
sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage
// Add a small delay to ensure modal closes properly before showing educational modal
setTimeout(async () => {
- console.log('๐ฏ Calling original handleCreateStrategy after enterprise modal closes');
try {
// Ensure we're not already generating
if (!aiGenerating && originalHandleCreateStrategyRef.current) {
- console.log('๐ฏ Starting strategy generation...');
await originalHandleCreateStrategyRef.current();
- } else {
- console.log('๐ฏ Already generating, skipping duplicate call');
}
} catch (error) {
- console.error('๐ฏ Error in handleProceedWithCurrentStrategy:', error);
+ console.error('Error in handleProceedWithCurrentStrategy:', error);
}
}, 300); // Increased delay to ensure modal closes completely
};
// Handle add enterprise datapoints (coming soon)
const handleAddEnterpriseDatapoints = async () => {
- console.log('๐ฏ User clicked "Add Enterprise Datapoints"');
setShowEnterpriseModal(false);
sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage
// For now, just proceed with current strategy
// In Phase 2, this will enable enterprise datapoints
setTimeout(async () => {
- console.log('๐ฏ Calling original handleCreateStrategy for enterprise datapoints');
try {
// Ensure we're not already generating
if (!aiGenerating && originalHandleCreateStrategyRef.current) {
await originalHandleCreateStrategyRef.current();
- } else {
- console.log('๐ฏ Already generating, skipping duplicate call');
}
} catch (error) {
- console.error('๐ฏ Error in handleAddEnterpriseDatapoints:', error);
+ console.error('Error in handleAddEnterpriseDatapoints:', error);
}
}, 200); // Increased delay to ensure modal closes completely
};
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx
index 8228e56b..a335f71e 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx
@@ -91,19 +91,9 @@ const StrategyAutofillTransparencyModal: React.FC {
- console.log('๐ฏ StrategyAutofillTransparencyModal Props:', {
- open,
- autoPopulatedFields: Object.keys(autoPopulatedFields || {}).length,
- dataSources: Object.keys(dataSources || {}).length,
- inputDataPoints: Object.keys(inputDataPoints || {}).length,
- isGenerating,
- generationProgress,
- currentPhase,
- transparencyMessages: transparencyMessages?.length,
- error
- });
+ // Props monitoring removed for cleaner console
}, [open, autoPopulatedFields, dataSources, inputDataPoints, isGenerating, generationProgress, currentPhase, transparencyMessages, error]);
// Auto-scroll to bottom when new messages arrive
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx
index 96f4631f..2fecc0c3 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, memo } from 'react';
import { Box, CircularProgress, Alert, Typography } from '@mui/material';
import StrategyHeader from './components/StrategyHeader';
import StrategicInsightsCard from './components/StrategicInsightsCard';
@@ -7,22 +7,33 @@ import PerformancePredictionsCard from './components/PerformancePredictionsCard'
import ImplementationRoadmapCard from './components/ImplementationRoadmapCard';
import RiskAssessmentCard from './components/RiskAssessmentCard';
import ReviewProgressHeader from './components/ReviewProgressHeader';
+import StrategyErrorBoundary from './components/StrategyErrorBoundary';
import { StrategyData } from './types/strategy.types';
import { useStrategyReviewStore } from '../../../../stores/strategyReviewStore';
+import { hasValidData } from './utils/defensiveRendering';
+import { createSafeStrategyData, validateStrategyData } from './utils/strategyDataValidator';
interface StrategyIntelligenceTabProps {
strategyData?: StrategyData | null;
loading?: boolean;
error?: string | null;
+ strategyStatus?: 'active' | 'inactive' | 'pending' | 'none';
}
const StrategyIntelligenceTab: React.FC = ({
strategyData,
loading = false,
- error = null
+ error = null,
+ strategyStatus = 'none'
}) => {
- // Get review process state from store
- const { reviewProcessStarted, startReviewProcess, components, initializeComponents } = useStrategyReviewStore();
+ // Get review process state from store with selective subscription
+ const reviewProcessStarted = useStrategyReviewStore(state => state.reviewProcessStarted);
+ const startReviewProcess = useStrategyReviewStore(state => state.startReviewProcess);
+ const components = useStrategyReviewStore(state => state.components);
+ const initializeComponents = useStrategyReviewStore(state => state.initializeComponents);
+ const isAllReviewed = useStrategyReviewStore(state => state.isAllReviewed);
+ const isActivated = useStrategyReviewStore(state => state.isActivated);
+ const resetAllReviews = useStrategyReviewStore(state => state.resetAllReviews);
// Initialize components if they don't exist
useEffect(() => {
@@ -59,7 +70,63 @@ const StrategyIntelligenceTab: React.FC = ({
}
}, [components.length, initializeComponents]);
+ // Create safe strategy data for rendering
+ const safeStrategyData = createSafeStrategyData(strategyData);
+
+ // Auto-start review process for pending strategies
+ useEffect(() => {
+ if (strategyStatus === 'pending' && !reviewProcessStarted && safeStrategyData) {
+ console.log('๐ Auto-starting review process for pending strategy');
+ console.log('๐ Review Process: Starting review workflow for newly generated strategy');
+ console.log('๐ฏ Strategy Review Workflow: User should now see the review interface');
+ startReviewProcess();
+ }
+ }, [strategyStatus, safeStrategyData, startReviewProcess, reviewProcessStarted]);
+
+ // Log review process state changes (only when important changes occur, with debounce)
+ useEffect(() => {
+ const timeoutId = setTimeout(() => {
+ if (reviewProcessStarted || strategyStatus === 'pending') {
+ console.log('๐ Review Process State:', {
+ strategyStatus,
+ reviewProcessStarted,
+ hasStrategyData: !!safeStrategyData,
+ componentsCount: components.length,
+ isAllReviewed: isAllReviewed(),
+ isActivated: isActivated()
+ });
+ }
+ }, 150); // 150ms debounce
+
+ return () => clearTimeout(timeoutId);
+ }, [strategyStatus, reviewProcessStarted, safeStrategyData, components.length, isAllReviewed, isActivated]);
+
+ // Log when review interface becomes visible
+ useEffect(() => {
+ if (reviewProcessStarted && safeStrategyData) {
+ console.log('๐ฏ RENDERING: Review interface is now visible to user');
+ console.log('๐ Review Interface: User can now review and confirm the strategy');
+ console.log('๐ Current Strategy Data:', {
+ hasStrategicInsights: !!safeStrategyData.strategic_insights,
+ hasCompetitiveAnalysis: !!safeStrategyData.competitive_analysis,
+ hasPerformancePredictions: !!safeStrategyData.performance_predictions,
+ hasImplementationRoadmap: !!safeStrategyData.implementation_roadmap,
+ hasRiskAssessment: !!safeStrategyData.risk_assessment
+ });
+ }
+ }, [reviewProcessStarted, safeStrategyData]);
+
+ // Log when StrategyHeader is rendered with new data
+ useEffect(() => {
+ if (safeStrategyData) {
+ console.log('๐ฏ RENDERING: StrategyHeader with status:', strategyStatus, 'and confirmed:', strategyStatus === 'active');
+ }
+ }, [strategyStatus, safeStrategyData]);
+
+
+
const handleStartReviewProcess = () => {
+ console.log('๐ Manual review process started by user');
startReviewProcess();
};
@@ -79,7 +146,23 @@ const StrategyIntelligenceTab: React.FC = ({
);
}
- if (!strategyData) {
+ // Validate strategy data before rendering
+ const hasValidStrategyData = (data: StrategyData | null): boolean => {
+ if (!data) return false;
+
+ // Check if the data has meaningful content
+ const hasStrategicInsights = hasValidData(data.strategic_insights);
+ const hasCompetitiveAnalysis = hasValidData(data.competitive_analysis);
+ const hasPerformancePredictions = hasValidData(data.performance_predictions);
+ const hasImplementationRoadmap = hasValidData(data.implementation_roadmap);
+ const hasRiskAssessment = hasValidData(data.risk_assessment);
+
+ // Return true if at least one component has valid data
+ return hasStrategicInsights || hasCompetitiveAnalysis || hasPerformancePredictions ||
+ hasImplementationRoadmap || hasRiskAssessment;
+ };
+
+ if (!safeStrategyData || !hasValidStrategyData(safeStrategyData)) {
return (
@@ -88,6 +171,11 @@ const StrategyIntelligenceTab: React.FC = ({
Generate a comprehensive strategy first to view strategic intelligence.
+ {strategyData && (
+
+ Strategy data exists but contains no valid components.
+
+ )}
);
}
@@ -96,13 +184,14 @@ const StrategyIntelligenceTab: React.FC = ({
{/* Header Section */}
{/* Review Progress Header - Only shown when review process is started */}
- {reviewProcessStarted && }
+ {reviewProcessStarted && }
{/* Strategy Intelligence Cards */}
= ({
}
}}
>
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
-export default StrategyIntelligenceTab;
\ No newline at end of file
+export default memo(StrategyIntelligenceTab);
\ No newline at end of file
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx
index 943bc624..acf7c04b 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx
@@ -32,6 +32,7 @@ import {
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
+import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
interface CompetitiveAnalysisCardProps {
strategyData: StrategyData | null;
@@ -68,6 +69,8 @@ const CompetitiveAnalysisCard: React.FC = ({ strat
return description.split(' ').slice(0, 2).join(' ');
};
+
+
if (!strategyData?.competitive_analysis) {
return (
= ({ strat
}} />
= ({ strat
}} />
= ({ strat
}} />
= ({ strat
opacity: 0.7
}} />
-
+
))}
@@ -433,7 +436,7 @@ const CompetitiveAnalysisCard: React.FC = ({ strat
}} />
= ({ strat
}} />
= ({ strat
}} />
= ({ strat
}} />
= ({ strat
}} />
= ({ strat
}} />
-
+
))}
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx
index d47fb4d5..f67ea504 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx
@@ -28,6 +28,7 @@ import {
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
+import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
interface ImplementationRoadmapCardProps {
strategyData: StrategyData | null;
@@ -50,6 +51,15 @@ const ImplementationRoadmapCard: React.FC = ({ s
return 'Budget allocation not specified';
};
+ // Helper function to safely render text content
+ const safeRenderText = (content: any): string => {
+ if (typeof content === 'string') return content;
+ if (typeof content === 'object' && content !== null) {
+ return JSON.stringify(content);
+ }
+ return 'Data not available';
+ };
+
if (!strategyData?.implementation_roadmap) {
return (
= ({ s
}} />
= ({ s
opacity: 0.7
}} />
-
+
))}
@@ -322,7 +332,7 @@ const ImplementationRoadmapCard: React.FC = ({ s
= ({ s
}} />
= ({ s
= ({ s
}} />
= ({ s
}} />
= ({ s
}} />
= ({
= ({
const sectionStyles = getSectionStyles();
const listItemStyles = getListItemStyles();
+ // Helper function to safely render text content
+ const safeRenderText = (content: any): string => {
+ if (typeof content === 'string') return content;
+ if (typeof content === 'object' && content !== null) {
+ return JSON.stringify(content);
+ }
+ return 'Data not available';
+ };
+
if (!strategyData?.performance_predictions) {
return (
= ({
const componentReviewedAt = component?.reviewedAt;
// Debug logging for component status
- if (componentId) {
- console.log(`๐ง ProgressiveCard [${componentId}]:`, {
- componentStatus,
- componentReviewedAt,
- allComponents: components.map(c => ({ id: c.id, status: c.status }))
- });
- }
+ // Removed verbose logging for cleaner console
// Handle hover interactions
const handleMouseEnter = () => {
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx
index ecf031d4..bc08b427 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx
@@ -26,6 +26,7 @@ import {
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { ANALYSIS_CARD_STYLES } from '../styles';
+import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
interface ReviewConfirmationDialogProps {
open: boolean;
@@ -204,7 +205,7 @@ const ReviewConfirmationDialog: React.FC = ({
}} />
= ({ strategyData }) => {
- const {
- components,
- reviewProgress,
- isAllReviewed,
- isActivated,
- resetAllReviews,
- getUnreviewedComponents,
- getReviewedComponents,
- activateStrategy
- } = useStrategyReviewStore();
+ // Use selective store subscriptions to prevent unnecessary re-renders
+ const components = useStrategyReviewStore(state => state.components);
+ const reviewProgress = useStrategyReviewStore(state => state.reviewProgress);
+ const isAllReviewed = useStrategyReviewStore(state => state.isAllReviewed);
+ const isActivated = useStrategyReviewStore(state => state.isActivated);
+ const resetAllReviews = useStrategyReviewStore(state => state.resetAllReviews);
+ const getUnreviewedComponents = useStrategyReviewStore(state => state.getUnreviewedComponents);
+ const getReviewedComponents = useStrategyReviewStore(state => state.getReviewedComponents);
+ const activateStrategy = useStrategyReviewStore(state => state.activateStrategy);
// Initialize navigation orchestrator
const navigationOrchestrator = useNavigationOrchestrator();
@@ -56,17 +55,7 @@ const ReviewProgressHeader: React.FC = ({ strategyDat
const reviewedCount = getReviewedComponents().length;
const totalCount = components.length;
- // Debug logging
- console.log('๐ ReviewProgressHeader Debug:', {
- components,
- reviewProgress,
- unreviewedCount,
- reviewedCount,
- totalCount,
- isAllReviewed: isAllReviewed(),
- isActivated: isActivated(),
- strategyData
- });
+ // Debug logging - removed for cleaner console
const getProgressColor = () => {
if (isActivated()) return ANALYSIS_CARD_STYLES.colors.success;
@@ -98,7 +87,6 @@ const ReviewProgressHeader: React.FC = ({ strategyDat
const handleConfirmStrategy = async () => {
// This will be called by the enhanced button when activation is confirmed
- console.log('๐ฏ Strategy activation confirmed');
// Activate the strategy in the store
activateStrategy();
@@ -107,7 +95,6 @@ const ReviewProgressHeader: React.FC = ({ strategyDat
};
const handleGenerateContentCalendar = () => {
- console.log('๐ฏ Generate content calendar clicked');
// Prepare strategy context for navigation
const strategyContext = {
@@ -430,4 +417,4 @@ const ReviewProgressHeader: React.FC = ({ strategyDat
);
};
-export default ReviewProgressHeader;
+export default memo(ReviewProgressHeader);
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewProgressSection.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewProgressSection.tsx
new file mode 100644
index 00000000..bada296f
--- /dev/null
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewProgressSection.tsx
@@ -0,0 +1,387 @@
+import React from 'react';
+import {
+ Box,
+ Typography,
+ Chip,
+ Tooltip,
+ CircularProgress,
+ Badge,
+ Button
+} from '@mui/material';
+import {
+ CheckCircle as CheckCircleIcon,
+ Schedule as ScheduleIcon,
+ AutoAwesome as AutoAwesomeIcon
+} from '@mui/icons-material';
+import { motion } from 'framer-motion';
+import { StrategyData } from '../types/strategy.types';
+import { useStrategyReviewStore, StrategyComponent } from '../../../../../stores/strategyReviewStore';
+import { ANALYSIS_CARD_STYLES } from '../styles';
+import EnhancedStrategyActivationButton from './EnhancedStrategyActivationButton';
+import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
+
+interface ReviewProgressSectionProps {
+ strategyData: StrategyData;
+}
+
+const ReviewProgressSection: React.FC = ({ strategyData }) => {
+ const {
+ components,
+ reviewProgress,
+ isAllReviewed,
+ isActivated,
+ resetAllReviews,
+ getUnreviewedComponents,
+ getReviewedComponents,
+ activateStrategy
+ } = useStrategyReviewStore();
+
+ // Initialize navigation orchestrator
+ const navigationOrchestrator = useNavigationOrchestrator();
+
+ // Extract domain name from strategy data
+ const getDomainName = () => {
+ // Since StrategyMetadata doesn't have domain, we'll use a fallback
+ return "alwrity.com"; // fallback
+ };
+
+ const unreviewedCount = getUnreviewedComponents().length;
+ const reviewedCount = getReviewedComponents().length;
+ const totalCount = components.length;
+
+ // Debug logging
+ console.log('๐ ReviewProgressSection Debug:', {
+ components,
+ reviewProgress,
+ unreviewedCount,
+ reviewedCount,
+ totalCount,
+ isAllReviewed: isAllReviewed(),
+ isActivated: isActivated(),
+ strategyData
+ });
+
+ const getProgressColor = () => {
+ if (isActivated()) return ANALYSIS_CARD_STYLES.colors.success;
+ if (reviewProgress === 100) return ANALYSIS_CARD_STYLES.colors.success;
+ if (reviewProgress >= 60) return ANALYSIS_CARD_STYLES.colors.primary;
+ if (reviewProgress >= 30) return ANALYSIS_CARD_STYLES.colors.warning;
+ return ANALYSIS_CARD_STYLES.colors.error;
+ };
+
+ const getProgressText = () => {
+ if (isActivated()) return 'Strategy Active & Monitored!';
+ if (reviewProgress === 100) return 'All components reviewed!';
+ if (reviewProgress >= 60) return 'Great progress!';
+ if (reviewProgress >= 30) return 'Making good progress';
+ return 'Getting started';
+ };
+
+ // Prepare strategy data for the enhanced button
+ const buttonStrategyData = strategyData ? {
+ id: strategyData.strategy_metadata?.user_id || strategyData.metadata?.user_id || 1,
+ business_name: strategyData.strategy_metadata?.strategy_name || strategyData.metadata?.strategy_name || "ALwrity",
+ domain: getDomainName(),
+ // Add other strategy data as needed
+ } : {
+ id: 1,
+ business_name: "ALwrity",
+ domain: getDomainName(),
+ };
+
+ const handleConfirmStrategy = async () => {
+ // This will be called by the enhanced button when activation is confirmed
+ console.log('๐ฏ Strategy activation confirmed');
+
+ // Activate the strategy in the store
+ activateStrategy();
+
+ // You can add additional logic here if needed
+ };
+
+ const handleGenerateContentCalendar = () => {
+ console.log('๐ฏ Generate content calendar clicked');
+
+ // Prepare strategy context for navigation
+ const strategyContext = {
+ strategyId: (strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1').toString(),
+ strategyData: strategyData,
+ activationStatus: 'active' as const,
+ activationTimestamp: new Date().toISOString(),
+ userPreferences: {},
+ strategicIntelligence: {}
+ };
+
+ // Navigate to calendar wizard using navigation orchestrator
+ navigationOrchestrator.navigateToCalendarWizard(
+ strategyContext.strategyId,
+ strategyContext
+ );
+ };
+
+ return (
+
+ {/* Header Section */}
+
+
+
+ Strategy Review Progress
+
+
+ Review all strategy components to activate your content strategy
+
+
+
+ {/* Progress Circle */}
+
+
+
+
+
+ {reviewProgress}%
+
+
+
+
+ {/* Progress Text */}
+
+
+ {getProgressText()}
+
+
+ {reviewedCount} of {totalCount} reviewed
+
+
+
+
+
+ {/* Component Status */}
+
+
+ Component Status:
+
+
+ {components.map((component: StrategyComponent) => (
+
+
+ ) : (
+
+ )
+ }
+ color={component.status === 'reviewed' ? 'success' : 'warning'}
+ >
+
+
+
+ ))}
+
+
+
+ {/* Completion Status */}
+ {isAllReviewed() && (
+
+ }
+ label={isActivated() ? "Strategy Active & Monitored" : "Ready for Calendar Creation"}
+ size="small"
+ sx={{
+ background: ANALYSIS_CARD_STYLES.colors.success,
+ color: 'white',
+ fontWeight: 500,
+ animation: 'pulse 2s infinite',
+ fontSize: '0.65rem',
+ height: 22,
+ '@keyframes pulse': {
+ '0%, 100%': { opacity: 1 },
+ '50%': { opacity: 0.7 }
+ },
+ '& .MuiChip-icon': {
+ color: 'white',
+ fontSize: 14
+ }
+ }}
+ />
+
+ )}
+
+ {/* Completion Message */}
+ {isAllReviewed() && (
+
+
+
+ {isActivated()
+ ? '๐ Your content strategy is now active and being monitored! AI-powered insights and performance tracking are now live.'
+ : '๐ All strategy components have been reviewed! You can now proceed to create your content calendar.'
+ }
+
+
+
+ )}
+
+ {/* Enhanced Strategy Activation Button - Only shown when all components are reviewed and not yet activated */}
+ {isAllReviewed() && !isActivated() && (
+
+
+
+
+
+ )}
+
+ {/* Strategy Activated Success Message */}
+ {isActivated() && (
+
+
+
+ ๐ Strategy Successfully Activated!
+
+
+ Your content strategy is now live and being monitored with AI-powered analytics.
+
+ }
+ sx={{
+ background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)',
+ borderRadius: 3,
+ px: 4,
+ py: 1.5,
+ fontWeight: 600,
+ textTransform: 'none',
+ boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #45a049 0%, #3d8b40 100%)',
+ boxShadow: '0 12px 40px rgba(76, 175, 80, 0.4)',
+ transform: 'translateY(-2px)'
+ },
+ transition: 'all 0.3s ease'
+ }}
+ >
+ Generate Content Calendar
+
+
+
+ )}
+
+ );
+};
+
+export default ReviewProgressSection;
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx
index e9ee8607..6e582e4c 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx
@@ -37,7 +37,7 @@ const ReviewStatusIndicator: React.FC = ({
isReviewing = false
}) => {
// Debug logging for status
- console.log('๐ง ReviewStatusIndicator received status:', status);
+ // Removed verbose logging for cleaner console
const getStatusConfig = () => {
switch (status) {
case 'activated':
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx
index 1adcd201..348d06a4 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx
@@ -29,6 +29,7 @@ import {
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
+import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
interface RiskAssessmentCardProps {
strategyData: StrategyData | null;
@@ -51,6 +52,15 @@ const RiskAssessmentCard: React.FC = ({ strategyData })
return ANALYSIS_CARD_STYLES.colors.info;
};
+ // Helper function to safely render text content
+ const safeRenderText = (content: any): string => {
+ if (typeof content === 'string') return content;
+ if (typeof content === 'object' && content !== null) {
+ return JSON.stringify(content);
+ }
+ return 'Data not available';
+ };
+
if (!strategyData?.risk_assessment) {
return (
= ({ strategyData })
-
+
))}
@@ -345,14 +355,14 @@ const RiskAssessmentCard: React.FC = ({ strategyData })
opacity: 0.7
}} />
-
+
))}
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx
index 382a6d3d..68e5b44b 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx
@@ -29,6 +29,7 @@ import {
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
+import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
interface StrategicInsightsCardProps {
strategyData: StrategyData | null;
@@ -80,6 +81,8 @@ const StrategicInsightsCard: React.FC = ({ strategyD
}
};
+
+
// Summary content - always visible
const summaryContent = (
@@ -236,7 +239,7 @@ const StrategicInsightsCard: React.FC = ({ strategyD
}} />
= ({ strategyD
}} />
= ({ strategyD
}} />
= ({ strategyD
}} />
= ({ strategyD
}} />
= ({ strategyD
opacity: 0.7
}} />
- = ({ strategyD
= ({ strategyD
).chip}
/>
= ({ strategyD
).chip}
/>
= ({ strategyD
).chip}
/>
- {insight.reasoning && (
+ {hasValidData(insight.reasoning) && (
- Reasoning: {insight.reasoning}
+ Reasoning: {safeRenderText(insight.reasoning)}
)}
@@ -562,7 +565,7 @@ const StrategicInsightsCard: React.FC = ({ strategyD
}} />
{
+ constructor(props: Props) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error: Error): State {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ console.error('Strategy component error:', error, errorInfo);
+ }
+
+ handleRetry = () => {
+ this.setState({ hasError: false, error: undefined });
+ };
+
+ render() {
+ if (this.state.hasError) {
+ if (this.props.fallback) {
+ return this.props.fallback;
+ }
+
+ return (
+
+
+
+ Error rendering strategy component
+
+
+ {this.state.error?.message || 'Unknown error occurred'}
+
+
+ }
+ onClick={this.handleRetry}
+ size="small"
+ >
+ Retry
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default StrategyErrorBoundary;
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx
index de458be5..b96ff663 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx
@@ -42,10 +42,12 @@ import { getStrategyName, getStrategyGenerationDate } from '../utils/strategyTra
interface StrategyHeaderProps {
strategyData: StrategyData | null;
strategyConfirmed: boolean;
+ strategyStatus?: 'active' | 'inactive' | 'pending' | 'none';
onStartReview?: () => void;
+ disableCardWrapper?: boolean; // New prop to control Card wrapper rendering
}
-const StrategyHeader: React.FC = ({ strategyData, strategyConfirmed, onStartReview }) => {
+const StrategyHeader: React.FC = ({ strategyData, strategyConfirmed, strategyStatus = 'none', onStartReview, disableCardWrapper = false }) => {
const [showNextStepText, setShowNextStepText] = useState(false);
if (!strategyData) return null;
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/defensiveRendering.ts b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/defensiveRendering.ts
new file mode 100644
index 00000000..56952745
--- /dev/null
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/defensiveRendering.ts
@@ -0,0 +1,101 @@
+// Utility functions for defensive rendering in strategy components
+
+/**
+ * Safely renders text content, handling objects, arrays, and other non-string types
+ */
+export const safeRenderText = (content: any): string => {
+ if (typeof content === 'string') return content;
+ if (typeof content === 'number') return content.toString();
+ if (typeof content === 'boolean') return content.toString();
+ if (Array.isArray(content)) return content.join(', ');
+ if (typeof content === 'object' && content !== null) {
+ // If it's an empty object, return a fallback message
+ if (Object.keys(content).length === 0) {
+ return 'Data not available';
+ }
+ // If it has a meaningful structure, try to extract useful information
+ if (content.title) return content.title;
+ if (content.name) return content.name;
+ if (content.description) return content.description;
+ if (content.text) return content.text;
+ // As a last resort, stringify the object
+ return JSON.stringify(content);
+ }
+ return 'Data not available';
+};
+
+/**
+ * Safely renders array content, ensuring it's always an array
+ */
+export const safeRenderArray = (content: any): any[] => {
+ if (Array.isArray(content)) return content;
+ if (typeof content === 'string') return [content];
+ if (typeof content === 'object' && content !== null) {
+ // If it's an empty object, return empty array
+ if (Object.keys(content).length === 0) {
+ return [];
+ }
+ // Try to extract array-like data
+ if (content.items && Array.isArray(content.items)) return content.items;
+ if (content.data && Array.isArray(content.data)) return content.data;
+ if (content.list && Array.isArray(content.list)) return content.list;
+ // As a last resort, return the object as a single item
+ return [content];
+ }
+ return [];
+};
+
+/**
+ * Safely renders object content, ensuring it's always a valid object
+ */
+export const safeRenderObject = (content: any): Record => {
+ if (typeof content === 'object' && content !== null) {
+ return content;
+ }
+ return {};
+};
+
+/**
+ * Validates if a strategy data field contains meaningful data
+ */
+export const hasValidData = (content: any): boolean => {
+ if (content === null || content === undefined) return false;
+ if (typeof content === 'string') return content.trim().length > 0;
+ if (Array.isArray(content)) return content.length > 0;
+ if (typeof content === 'object') {
+ // Check if it's an empty object
+ if (Object.keys(content).length === 0) return false;
+ // Check if it has meaningful properties
+ return Object.values(content).some(value => hasValidData(value));
+ }
+ return true;
+};
+
+/**
+ * Gets a fallback value for empty or invalid data
+ */
+export const getFallbackValue = (fieldName: string): string => {
+ const fallbacks: Record = {
+ insight: 'Insight data not available',
+ recommendation: 'Recommendation data not available',
+ prediction: 'Prediction data not available',
+ analysis: 'Analysis data not available',
+ strategy: 'Strategy data not available',
+ risk: 'Risk data not available',
+ opportunity: 'Opportunity data not available',
+ strength: 'Strength data not available',
+ weakness: 'Weakness data not available',
+ threat: 'Threat data not available',
+ milestone: 'Milestone data not available',
+ task: 'Task data not available',
+ resource: 'Resource data not available',
+ requirement: 'Requirement data not available',
+ metric: 'Metric data not available',
+ gap: 'Gap data not available',
+ advantage: 'Advantage data not available',
+ area: 'Area data not available',
+ path: 'Path data not available'
+ };
+
+ return fallbacks[fieldName.toLowerCase()] || 'Data not available';
+};
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/strategyDataValidator.ts b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/strategyDataValidator.ts
new file mode 100644
index 00000000..cf85f464
--- /dev/null
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/strategyDataValidator.ts
@@ -0,0 +1,167 @@
+import { StrategyData } from '../types/strategy.types';
+import { hasValidData } from './defensiveRendering';
+
+/**
+ * Validates strategy data to ensure it's safe for rendering
+ */
+export const validateStrategyData = (data: StrategyData | null | undefined): {
+ isValid: boolean;
+ errors: string[];
+ warnings: string[];
+} => {
+ const errors: string[] = [];
+ const warnings: string[] = [];
+
+ if (!data) {
+ return {
+ isValid: false,
+ errors: ['Strategy data is null or undefined'],
+ warnings: []
+ };
+ }
+
+ // Check for empty objects that could cause rendering issues
+ const checkForEmptyObjects = (obj: any, path: string): void => {
+ if (typeof obj === 'object' && obj !== null) {
+ if (Array.isArray(obj)) {
+ obj.forEach((item, index) => {
+ checkForEmptyObjects(item, `${path}[${index}]`);
+ });
+ } else {
+ const keys = Object.keys(obj);
+ if (keys.length === 0) {
+ warnings.push(`Empty object found at ${path}`);
+ } else {
+ keys.forEach(key => {
+ checkForEmptyObjects(obj[key], `${path}.${key}`);
+ });
+ }
+ }
+ }
+ };
+
+ // Check each major component
+ const components = [
+ { name: 'strategic_insights', data: data.strategic_insights },
+ { name: 'competitive_analysis', data: data.competitive_analysis },
+ { name: 'performance_predictions', data: data.performance_predictions },
+ { name: 'implementation_roadmap', data: data.implementation_roadmap },
+ { name: 'risk_assessment', data: data.risk_assessment }
+ ];
+
+ components.forEach(({ name, data: componentData }) => {
+ if (componentData) {
+ checkForEmptyObjects(componentData, name);
+
+ // Check if the component has meaningful data
+ if (!hasValidData(componentData)) {
+ warnings.push(`${name} component exists but contains no meaningful data`);
+ }
+ } else {
+ warnings.push(`${name} component is missing`);
+ }
+ });
+
+ // Check metadata
+ if (data.strategy_metadata) {
+ checkForEmptyObjects(data.strategy_metadata, 'strategy_metadata');
+ }
+
+ if (data.metadata) {
+ checkForEmptyObjects(data.metadata, 'metadata');
+ }
+
+ // Check summary
+ if (data.summary) {
+ checkForEmptyObjects(data.summary, 'summary');
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors,
+ warnings
+ };
+};
+
+/**
+ * Sanitizes strategy data to prevent rendering errors
+ */
+export const sanitizeStrategyData = (data: StrategyData | null | undefined): StrategyData | null => {
+ if (!data) return null;
+
+ const sanitizeValue = (value: any): any => {
+ if (value === null || value === undefined) {
+ return null;
+ }
+
+ if (typeof value === 'object') {
+ if (Array.isArray(value)) {
+ return value.map(sanitizeValue).filter(v => v !== null);
+ } else {
+ const keys = Object.keys(value);
+ if (keys.length === 0) {
+ return null; // Remove empty objects
+ }
+
+ const sanitized: any = {};
+ keys.forEach(key => {
+ const sanitizedValue = sanitizeValue(value[key]);
+ if (sanitizedValue !== null) {
+ sanitized[key] = sanitizedValue;
+ }
+ });
+
+ return Object.keys(sanitized).length > 0 ? sanitized : null;
+ }
+ }
+
+ return value;
+ };
+
+ // Create a deep copy and sanitize
+ const sanitized = JSON.parse(JSON.stringify(data));
+
+ // Sanitize each component
+ if (sanitized.strategic_insights) {
+ sanitized.strategic_insights = sanitizeValue(sanitized.strategic_insights);
+ }
+
+ if (sanitized.competitive_analysis) {
+ sanitized.competitive_analysis = sanitizeValue(sanitized.competitive_analysis);
+ }
+
+ if (sanitized.performance_predictions) {
+ sanitized.performance_predictions = sanitizeValue(sanitized.performance_predictions);
+ }
+
+ if (sanitized.implementation_roadmap) {
+ sanitized.implementation_roadmap = sanitizeValue(sanitized.implementation_roadmap);
+ }
+
+ if (sanitized.risk_assessment) {
+ sanitized.risk_assessment = sanitizeValue(sanitized.risk_assessment);
+ }
+
+ return sanitized;
+};
+
+/**
+ * Creates a safe strategy data object for rendering
+ */
+export const createSafeStrategyData = (data: StrategyData | null | undefined): StrategyData | null => {
+ if (!data) return null;
+
+ // First validate the data
+ const validation = validateStrategyData(data);
+
+ if (validation.errors.length > 0) {
+ console.warn('Strategy data validation errors:', validation.errors);
+ }
+
+ if (validation.warnings.length > 0) {
+ console.warn('Strategy data validation warnings:', validation.warnings);
+ }
+
+ // Then sanitize the data
+ return sanitizeStrategyData(data);
+};
diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx
index c86c347e..f33ed143 100644
--- a/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx
@@ -54,7 +54,7 @@ interface StrategyOnboardingDialogProps {
onEditStrategy: () => void;
onCreateNewStrategy: () => void;
currentStrategy: any;
- strategyStatus: 'active' | 'inactive' | 'none';
+ strategyStatus: 'active' | 'inactive' | 'pending' | 'none';
}
const StrategyOnboardingDialog: React.FC = ({
diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx
index 35bd6066..8a84f27a 100644
--- a/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import {
Box,
Paper,
@@ -12,6 +12,7 @@ import {
AutoAwesome as AutoAwesomeIcon,
Edit as EditIcon
} from '@mui/icons-material';
+import { useLocation } from 'react-router-dom';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import StrategyIntelligenceTab from '../components/StrategyIntelligence/StrategyIntelligenceTab';
@@ -19,17 +20,19 @@ import StrategyOnboardingDialog from '../components/StrategyOnboardingDialog';
import { StrategyData } from '../components/StrategyIntelligence/types/strategy.types';
const ContentStrategyTab: React.FC = () => {
- const {
- strategies,
- currentStrategy,
- aiInsights,
- aiRecommendations,
- loading,
- error,
- loadStrategies,
- loadAIInsights,
- loadAIRecommendations
- } = useContentPlanningStore();
+ const location = useLocation();
+ // Use selective store subscriptions to prevent unnecessary re-renders
+ const strategies = useContentPlanningStore(state => state.strategies);
+ const currentStrategy = useContentPlanningStore(state => state.currentStrategy);
+ const latestGeneratedStrategy = useContentPlanningStore(state => state.latestGeneratedStrategy);
+ const aiInsights = useContentPlanningStore(state => state.aiInsights);
+ const aiRecommendations = useContentPlanningStore(state => state.aiRecommendations);
+ const loading = useContentPlanningStore(state => state.loading);
+ const error = useContentPlanningStore(state => state.error);
+ const loadStrategies = useContentPlanningStore(state => state.loadStrategies);
+ const loadAIInsights = useContentPlanningStore(state => state.loadAIInsights);
+ const loadAIRecommendations = useContentPlanningStore(state => state.loadAIRecommendations);
+ const setLatestGeneratedStrategy = useContentPlanningStore(state => state.setLatestGeneratedStrategy);
const [strategyForm, setStrategyForm] = useState({
name: '',
@@ -52,23 +55,61 @@ const ContentStrategyTab: React.FC = () => {
});
// Strategy status and onboarding
- const [strategyStatus, setStrategyStatus] = useState<'active' | 'inactive' | 'none'>('none');
+ const [strategyStatus, setStrategyStatus] = useState<'active' | 'inactive' | 'pending' | 'none'>('none');
const [showOnboarding, setShowOnboarding] = useState(false);
const [hasCheckedStrategy, setHasCheckedStrategy] = useState(false);
+ // Navigation state detection
+ const [isFromStrategyBuilder, setIsFromStrategyBuilder] = useState(false);
+
// Load data on component mount
useEffect(() => {
loadInitialData();
}, []);
+ // Check if coming from strategy builder
+ useEffect(() => {
+ const locationState = location.state as any;
+ const isFromBuilder = locationState?.fromStrategyBuilder ||
+ locationState?.activeTab === 0 || // Content Strategy tab
+ sessionStorage.getItem('fromStrategyBuilder') === 'true';
+
+ console.log('๐ ContentStrategyTab: Navigation state check:', {
+ locationState,
+ isFromBuilder,
+ sessionStorage: sessionStorage.getItem('fromStrategyBuilder')
+ });
+
+ setIsFromStrategyBuilder(isFromBuilder);
+
+ // Clear the session storage flag after reading it
+ if (sessionStorage.getItem('fromStrategyBuilder') === 'true') {
+ sessionStorage.removeItem('fromStrategyBuilder');
+ }
+
+ // Clear the cache when navigating away from strategy builder
+ if (!isFromBuilder && latestGeneratedStrategy) {
+ console.log('๐งน Clearing latest generated strategy cache (navigating away from strategy builder)');
+ // Note: We don't clear the cache here as it might be needed for the current session
+ }
+ }, [location.state]);
+
+ // Track strategy status changes for debugging (with debounce)
+ useEffect(() => {
+ const timeoutId = setTimeout(() => {
+ console.log('๐ Strategy Status Changed:', {
+ status: strategyStatus,
+ hasStrategyData: !!strategyData,
+ strategyDataKeys: strategyData ? Object.keys(strategyData) : [],
+ isFromStrategyBuilder
+ });
+ }, 100); // 100ms debounce
+
+ return () => clearTimeout(timeoutId);
+ }, [strategyStatus, strategyData, isFromStrategyBuilder]);
+
// Check strategy status when strategies are loaded
useEffect(() => {
- console.log('๐ useEffect triggered - strategies changed:', strategies);
- console.log('๐ Strategies type:', typeof strategies);
- console.log('๐ Is Array:', Array.isArray(strategies));
- console.log('๐ Strategies length:', strategies?.length);
- console.log('๐ Has checked strategy:', hasCheckedStrategy);
-
// Handle different response formats
let strategiesArray: any[] = [];
@@ -80,64 +121,237 @@ const ContentStrategyTab: React.FC = () => {
strategiesArray = (strategies as any).strategies;
}
- console.log('๐ StrategiesArray length:', strategiesArray.length);
-
if (strategiesArray.length > 0) {
- console.log('โ Strategies found, checking status...');
checkStrategyStatus();
- loadStrategyData();
+
+ // Add debounce to prevent rapid successive calls
+ const timeoutId = setTimeout(() => {
+ loadStrategyData();
+ }, 500); // 500ms debounce
+
+ return () => clearTimeout(timeoutId);
} else if (strategiesArray.length === 0 && hasCheckedStrategy) {
// Only set to 'none' if we've already checked and confirmed no strategies
- console.log('โ No strategies found, setting status to none...');
setStrategyStatus('none');
setShowOnboarding(true);
}
// If strategiesArray.length === 0 and !hasCheckedStrategy, do nothing (wait for data to load)
- }, [strategies, loadStrategies]);
+ }, [strategies, loadStrategies, isFromStrategyBuilder]);
const loadStrategyData = async () => {
+ // Prevent multiple simultaneous requests
+ if (strategyDataLoading) {
+ console.log('๐ Strategy data loading already in progress, skipping...');
+ return;
+ }
+
try {
setStrategyDataLoading(true);
setStrategyDataError(null);
const userId = 1; // Default user ID
- // Try to get the latest generated strategy
+ // PRIORITY 0: Check cache first for latest generated strategy
+ console.log('๐ PRIORITY 0: Checking cache for latest generated strategy...');
+ console.log('๐ Cache state:', {
+ hasCache: !!latestGeneratedStrategy,
+ cacheData: latestGeneratedStrategy ? {
+ hasStrategicInsights: !!latestGeneratedStrategy.strategic_insights,
+ hasCompetitiveAnalysis: !!latestGeneratedStrategy.competitive_analysis,
+ hasPerformancePredictions: !!latestGeneratedStrategy.performance_predictions,
+ hasImplementationRoadmap: !!latestGeneratedStrategy.implementation_roadmap,
+ hasRiskAssessment: !!latestGeneratedStrategy.risk_assessment
+ } : null
+ });
+ if (latestGeneratedStrategy && latestGeneratedStrategy.strategic_insights) {
+ console.log('๐ PRIORITY 0: Found latest generated strategy in cache!');
+ console.log('๐ Cached strategy data structure:', {
+ hasStrategicInsights: !!latestGeneratedStrategy.strategic_insights,
+ hasCompetitiveAnalysis: !!latestGeneratedStrategy.competitive_analysis,
+ hasPerformancePredictions: !!latestGeneratedStrategy.performance_predictions,
+ hasImplementationRoadmap: !!latestGeneratedStrategy.implementation_roadmap,
+ hasRiskAssessment: !!latestGeneratedStrategy.risk_assessment,
+ strategyId: latestGeneratedStrategy.id,
+ strategyName: latestGeneratedStrategy.name
+ });
+ setStrategyData(latestGeneratedStrategy);
+
+ // Set strategy status to pending for newly generated strategy (needs review)
+ setStrategyStatus('pending');
+ setShowOnboarding(false);
+ console.log('๐ Set strategy status to pending for cached strategy (needs review)');
+ console.log('๐ Strategy Review Workflow: New strategy ready for review - User should see review process');
+
+ return;
+ } else {
+ console.log('โ PRIORITY 0: No strategy found in cache, proceeding to next priority...');
+ }
+
+ // PRIORITY 1: If coming from strategy builder, prioritize the latest generated strategy
+ if (isFromStrategyBuilder) {
+ // Try with exponential backoff to handle rate limits
+ for (let attempt = 1; attempt <= 2; attempt++) {
+ try {
+ console.log(`๐ PRIORITY 1 (Attempt ${attempt}/2): Trying to get latest generated strategy from polling system...`);
+
+ // Add exponential backoff delay for subsequent attempts
+ if (attempt > 1) {
+ const delay = Math.pow(2, attempt) * 1000; // 2s, 4s delays
+ console.log(`โณ Waiting ${delay}ms before retry...`);
+ await new Promise(resolve => setTimeout(resolve, delay));
+ }
+
+ const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategyWithRetry(userId);
+
+ console.log(`๐ Latest strategy response (attempt ${attempt}):`, latestStrategyResponse);
+
+ if (latestStrategyResponse && latestStrategyResponse.strategic_insights) {
+ console.log('โ Found latest generated strategy in polling system');
+ console.log('๐ Latest strategy data structure:', {
+ hasStrategicInsights: !!latestStrategyResponse.strategic_insights,
+ hasCompetitiveAnalysis: !!latestStrategyResponse.competitive_analysis,
+ hasPerformancePredictions: !!latestStrategyResponse.performance_predictions,
+ hasImplementationRoadmap: !!latestStrategyResponse.implementation_roadmap,
+ hasRiskAssessment: !!latestStrategyResponse.risk_assessment,
+ strategyId: latestStrategyResponse.id,
+ strategyName: latestStrategyResponse.name
+ });
+ setStrategyData(latestStrategyResponse);
+
+ // Cache the strategy since it wasn't cached during generation
+ console.log('๐พ Caching strategy from polling system since it was not cached during generation');
+ setLatestGeneratedStrategy(latestStrategyResponse);
+
+ // Set strategy status to pending for newly generated strategy (needs review)
+ setStrategyStatus('pending');
+ setShowOnboarding(false);
+ console.log('๐ Set strategy status to pending for polling system strategy (needs review)');
+
+ return;
+ } else {
+ console.log(`โ ๏ธ Latest strategy response missing strategic_insights (attempt ${attempt}), trying database...`);
+ console.log('๐ Latest strategy response structure:', latestStrategyResponse);
+ }
+ } catch (pollingError: any) {
+ console.log(`โ ๏ธ Error getting latest strategy from polling system (attempt ${attempt}):`, pollingError);
+
+ // Check if it's a rate limit error
+ if (pollingError?.response?.status === 429) {
+ console.log('๐ซ Rate limit hit, will retry with longer delay...');
+ if (attempt === 2) {
+ console.log('โ Rate limit persists, skipping polling system and trying database...');
+ break; // Exit the loop and try database instead
+ }
+ } else if (attempt === 2) {
+ console.log('โ All attempts failed, continuing to database fallback...');
+ }
+ }
+ }
+ }
+
+ // PRIORITY 2: Try to get the latest generated strategy from polling system
try {
const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategy(userId);
- console.log('๐ Latest strategy response from API:', latestStrategyResponse);
-
if (latestStrategyResponse && latestStrategyResponse.strategic_insights) {
- console.log('โ Found latest generated strategy:', latestStrategyResponse);
setStrategyData(latestStrategyResponse);
+
+ // Cache the strategy since it wasn't cached during generation
+ console.log('๐พ Caching strategy from general polling since it was not cached during generation');
+ setLatestGeneratedStrategy(latestStrategyResponse);
+
+ // Set strategy status to pending for newly generated strategy (needs review)
+ setStrategyStatus('pending');
+ setShowOnboarding(false);
+ console.log('๐ Set strategy status to pending for general polling strategy (needs review)');
+
return;
}
} catch (pollingError) {
- console.log('No latest strategy found in polling system, checking database...', pollingError);
+ // Continue to next priority
}
- // If no strategy found in polling system, try to get from database
+ // PRIORITY 3: If no strategy found in polling system, try to get from database
+ console.log('๐ฏ PRIORITY 3: Checking database for strategies...');
try {
const strategiesResponse = await contentPlanningApi.getEnhancedStrategies(userId);
const strategies = strategiesResponse?.data?.strategies || strategiesResponse?.strategies || [];
if (strategies && strategies.length > 0) {
- const latestStrategy = strategies[0];
+ console.log('๐ Found strategies in database:', strategies.length);
+
+ // Sort strategies by creation date (newest first) to ensure we get the latest
+ const sortedStrategies = strategies.sort((a: any, b: any) => {
+ const dateA = new Date(a.created_at || a.createdAt || 0);
+ const dateB = new Date(b.created_at || b.createdAt || 0);
+ return dateB.getTime() - dateA.getTime(); // Descending order (newest first)
+ });
+
+ console.log('๐ Sorted strategies by creation date:', sortedStrategies.map((s: any) => ({
+ id: s.id,
+ name: s.name,
+ created_at: s.created_at || s.createdAt,
+ has_comprehensive_analysis: !!s.comprehensive_ai_analysis,
+ has_ai_recommendations: !!s.ai_recommendations
+ })));
+
+ // If coming from strategy builder, prioritize strategies with comprehensive_ai_analysis
+ if (isFromStrategyBuilder) {
+ const latestComprehensiveStrategy = sortedStrategies.find((s: any) => s.comprehensive_ai_analysis);
+ if (latestComprehensiveStrategy) {
+ console.log('โ Found latest comprehensive strategy in database:', latestComprehensiveStrategy.id);
+ setStrategyData(latestComprehensiveStrategy.comprehensive_ai_analysis);
+
+ // Set strategy status to active for existing database strategy
+ setStrategyStatus('active');
+ setShowOnboarding(false);
+ console.log('โ Set strategy status to active for database comprehensive strategy');
+
+ return;
+ }
+
+ // Fallback to ai_recommendations if comprehensive_ai_analysis not available
+ const latestWithRecommendations = sortedStrategies.find((s: any) => s.ai_recommendations);
+ if (latestWithRecommendations) {
+ console.log('โ Found latest strategy with ai_recommendations in database:', latestWithRecommendations.id);
+ setStrategyData(latestWithRecommendations.ai_recommendations);
+
+ // Set strategy status to active for existing database strategy
+ setStrategyStatus('active');
+ setShowOnboarding(false);
+ console.log('โ Set strategy status to active for database recommendations strategy');
+
+ return;
+ }
+ }
+
+ // Get the most recent strategy (first after sorting)
+ const latestStrategy = sortedStrategies[0];
if (latestStrategy.comprehensive_ai_analysis) {
- console.log('โ Found comprehensive strategy in database:', latestStrategy);
setStrategyData(latestStrategy.comprehensive_ai_analysis);
+
+ // Set strategy status to active for existing database strategy
+ setStrategyStatus('active');
+ setShowOnboarding(false);
+ console.log('โ Set strategy status to active for database latest strategy');
+
return;
}
}
- } catch (dbError) {
- console.log('No comprehensive strategies found in database:', dbError);
+ } catch (dbError: any) {
+ console.error('Database error:', dbError);
+
+ // Check if it's a rate limit error
+ if (dbError?.response?.status === 429) {
+ console.log('๐ซ Database request also hit rate limit, showing error to user...');
+ setStrategyDataError('Server is temporarily overloaded. Please try again in a few moments.');
+ return;
+ }
}
// If no strategy data is available
- console.log('โ No comprehensive strategy data found');
setStrategyData(null);
setStrategyDataError('No comprehensive strategy data available. Please generate a strategy first.');
@@ -150,13 +364,20 @@ const ContentStrategyTab: React.FC = () => {
}
};
+ // Add a timeout to prevent infinite loading
+ useEffect(() => {
+ if (strategyDataLoading) {
+ const timeout = setTimeout(() => {
+ console.log('โฐ Strategy data loading timeout, resetting state...');
+ setStrategyDataLoading(false);
+ setStrategyDataError('Loading timeout. Please refresh the page.');
+ }, 30000); // 30 second timeout
+
+ return () => clearTimeout(timeout);
+ }
+ }, [strategyDataLoading]);
+
const checkStrategyStatus = () => {
- console.log('๐ Checking strategy status...');
- console.log('๐ Strategies from store:', strategies);
- console.log('๐ Strategies type:', typeof strategies);
- console.log('๐ Is Array:', Array.isArray(strategies));
- console.log('๐ Strategies length:', strategies?.length);
-
// Handle different response formats
let strategiesArray: any[] = [];
@@ -168,21 +389,15 @@ const ContentStrategyTab: React.FC = () => {
strategiesArray = (strategies as any).strategies;
}
- console.log('๐ StrategiesArray length:', strategiesArray.length);
-
if (strategiesArray.length > 0) {
// Find the most recent strategy
const latestStrategy = strategiesArray[0]; // Assuming strategies are sorted by date
- console.log('โ Found strategies in database:', strategiesArray.length);
- console.log('๐ Latest strategy:', latestStrategy);
-
// For now, we'll assume strategies are active if they exist
// In a real implementation, you would check a status field from the database
setStrategyStatus('active');
setShowOnboarding(false);
} else {
- console.log('โ No strategies found in database');
setStrategyStatus('none');
setShowOnboarding(true);
}
@@ -193,14 +408,30 @@ const ContentStrategyTab: React.FC = () => {
try {
setDataLoading({ strategies: true, insights: true, recommendations: true, strategicIntelligence: true });
- // Load strategies
+ // Load strategies first (most important)
+ console.log('๐ Loading strategies...');
await loadStrategies();
- // Load AI insights and recommendations
- await Promise.all([
- loadAIInsights(),
- loadAIRecommendations()
- ]);
+ // Add delay between requests to avoid rate limits
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Load AI insights and recommendations sequentially instead of in parallel
+ console.log('๐ Loading AI insights...');
+ try {
+ await loadAIInsights();
+ } catch (error) {
+ console.warn('โ ๏ธ Failed to load AI insights:', error);
+ }
+
+ // Add delay between requests
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ console.log('๐ Loading AI recommendations...');
+ try {
+ await loadAIRecommendations();
+ } catch (error) {
+ console.warn('โ ๏ธ Failed to load AI recommendations:', error);
+ }
} catch (error) {
console.error('Error loading initial data:', error);
@@ -245,6 +476,28 @@ const ContentStrategyTab: React.FC = () => {
)}
+ {/* Pending Strategy Status Banner */}
+ {strategyStatus === 'pending' && (
+ setShowOnboarding(true)}
+ startIcon={}
+ >
+ Review & Activate
+
+ }
+ >
+
+ Strategy Ready for Review: Your AI-generated content strategy is ready! Please review all components and confirm to activate your strategy.
+
+
+ )}
+
{/* Strategy Status Banner */}
{strategyStatus === 'inactive' && (
{
)}
- {/* Strategic Intelligence - Only show if there's an active strategy */}
- {strategyStatus === 'active' && (
+ {/* Strategic Intelligence - Show for both active and pending strategies */}
+ {(strategyStatus === 'active' || strategyStatus === 'pending') && (
-
+ {strategyDataLoading ? (
+
+
+
+ Loading strategy data...
+
+
+ ) : strategyDataError ? (
+
+ {strategyDataError}
+
+ ) : (
+
+ )}
+
+ )}
+
+ {/* Loading indicator for initial data */}
+ {Object.values(dataLoading).some(loading => loading) && (
+
+
+
+
+ Loading dashboard data...
+
+
)}
diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx
index 79d66c68..8049c5e7 100644
--- a/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx
+++ b/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx
@@ -44,7 +44,7 @@ const CreateTab: React.FC = () => {
// Get strategy context from the provider
const { state: { strategyContext }, isFromStrategyActivation } = useStrategyCalendarContext();
- console.log('๐ CreateTab: Rendering', { userData, strategyContext, locationState: location.state });
+ // Removed verbose logging for cleaner console
// Memoize the strategy activation status to avoid infinite re-renders
const fromStrategyActivation = useMemo(() => {
@@ -72,7 +72,7 @@ const CreateTab: React.FC = () => {
// Auto-switch to Calendar Wizard tab when coming from strategy activation
useEffect(() => {
- console.log('๐ CreateTab: Checking strategy activation status:', { fromStrategyActivation });
+ // Removed verbose logging for cleaner console
// Check multiple sources for strategy activation status
const isFromStrategy = fromStrategyActivation ||
diff --git a/frontend/src/contexts/StrategyCalendarContext.tsx b/frontend/src/contexts/StrategyCalendarContext.tsx
index 6684e15b..4fdf93b5 100644
--- a/frontend/src/contexts/StrategyCalendarContext.tsx
+++ b/frontend/src/contexts/StrategyCalendarContext.tsx
@@ -282,11 +282,6 @@ export const StrategyCalendarProvider: React.FC =
// Check if we have a preserved strategy context from navigation
const result = state.strategyContext?.activationStatus === 'active' &&
state.strategyContext?.activationTimestamp !== null;
- console.log('๐ StrategyCalendarContext: isFromStrategyActivation check:', {
- activationStatus: state.strategyContext?.activationStatus,
- activationTimestamp: state.strategyContext?.activationTimestamp,
- result
- });
return result;
}, [state.strategyContext?.activationStatus, state.strategyContext?.activationTimestamp]);
diff --git a/frontend/src/services/contentPlanningApi.ts b/frontend/src/services/contentPlanningApi.ts
index 2e4511f2..30064f00 100644
--- a/frontend/src/services/contentPlanningApi.ts
+++ b/frontend/src/services/contentPlanningApi.ts
@@ -458,6 +458,27 @@ class ContentPlanningAPI {
return this.handleRequest(() => this.getAIAnalytics(userId), true);
}
+ // Enhanced version with rate limit handling for AI analytics
+ async getAIAnalyticsWithRetry(userId?: number, maxRetries: number = 2): Promise {
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
+ try {
+ const response = await apiClient.get(`${this.baseURL}/ai-analytics/`, {
+ params: { user_id: userId || 1 }
+ });
+ return response.data;
+ } catch (error: any) {
+ if (error.response?.status === 429 && attempt < maxRetries) {
+ // Rate limit hit, wait and retry
+ const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
+ console.log(`๐ซ Rate limit hit for AI analytics, waiting ${delay}ms before retry ${attempt + 1}/${maxRetries}`);
+ await new Promise(resolve => setTimeout(resolve, delay));
+ continue;
+ }
+ throw error; // Re-throw if it's not a rate limit or we've exhausted retries
+ }
+ }
+ }
+
// AI Analytics with force refresh option
async getAIAnalyticsWithRefresh(userId?: number, forceRefresh = false): Promise {
try {
@@ -517,7 +538,7 @@ class ContentPlanningAPI {
async getComprehensiveUserData(userId?: number): Promise {
return this.handleRequest(async () => {
- const response = await apiClient.get(`${this.baseURL}/comprehensive-user-data`, {
+ const response = await apiClient.get(`${this.baseURL}/calendar-generation/comprehensive-user-data`, {
params: { user_id: userId }
});
return response.data;
@@ -593,30 +614,17 @@ class ContentPlanningAPI {
// Non-streaming autofill refresh method
async refreshAutofill(userId?: number, useAI: boolean = true, aiOnly: boolean = false): Promise {
- const params: any = { use_ai: useAI, ai_only: aiOnly };
+ const params: any = {
+ use_ai: useAI,
+ ai_only: aiOnly,
+ _t: Date.now() // ๐จ CRITICAL: Cache-busting timestamp to ensure fresh AI generation
+ };
if (userId) params.user_id = userId;
const response = await apiClient.post(`${this.baseURL}/enhanced-strategies/autofill/refresh`, null, { params });
- // Debug the API response
- console.log('๐ฏ API refreshAutofill response:', {
- responseType: typeof response,
- responseKeys: Object.keys(response),
- dataType: typeof response.data,
- dataKeys: response.data ? Object.keys(response.data) : 'no data',
- hasDataProperty: response.data?.hasOwnProperty('data'),
- hasFieldsProperty: response.data?.hasOwnProperty('fields'),
- dataDataKeys: response.data?.data ? Object.keys(response.data.data) : 'no data.data'
- });
-
// The backend returns ResponseBuilder format: { status, message, data, status_code, timestamp }
// We need to return the actual payload from response.data.data
const result = response.data?.data || response.data;
- console.log('๐ฏ API refreshAutofill returning:', {
- resultType: typeof result,
- resultKeys: Object.keys(result),
- hasFields: result?.hasOwnProperty('fields'),
- fieldsCount: result?.fields ? Object.keys(result.fields).length : 0
- });
return result;
}
@@ -714,21 +722,33 @@ class ContentPlanningAPI {
return this.handleRequest(async () => {
const params = userId ? { user_id: userId } : {};
const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/latest-strategy`, { params });
- console.log('๐ getLatestGeneratedStrategy response:', response.data);
- console.log('๐ Response structure:', {
- hasData: !!response.data,
- dataKeys: Object.keys(response.data || {}),
- hasStrategy: !!response.data?.data?.strategy,
- strategyKeys: response.data?.data?.strategy ? Object.keys(response.data.data.strategy) : []
- });
// Return the strategy data from the nested response structure
const result = response.data?.data?.strategy;
- console.log('๐ Returning result:', result);
- console.log('๐ Result keys:', Object.keys(result || {}));
return result;
});
}
+ // Enhanced version with rate limit handling
+ async getLatestGeneratedStrategyWithRetry(userId?: number, maxRetries: number = 2): Promise {
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
+ try {
+ const params = userId ? { user_id: userId } : {};
+ const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/latest-strategy`, { params });
+ const result = response.data?.data?.strategy;
+ return result;
+ } catch (error: any) {
+ if (error.response?.status === 429 && attempt < maxRetries) {
+ // Rate limit hit, wait and retry
+ const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
+ console.log(`๐ซ Rate limit hit, waiting ${delay}ms before retry ${attempt + 1}/${maxRetries}`);
+ await new Promise(resolve => setTimeout(resolve, delay));
+ continue;
+ }
+ throw error; // Re-throw if it's not a rate limit or we've exhausted retries
+ }
+ }
+ }
+
async startStrategyGenerationPolling(userId: number, strategyName: string): Promise {
return this.handleRequest(async () => {
const response = await apiClient.post(`${this.baseURL}/content-strategy/ai-generation/generate-comprehensive-strategy-polling`, {
diff --git a/frontend/src/stores/contentPlanningStore.ts b/frontend/src/stores/contentPlanningStore.ts
index bf803620..a634c3d7 100644
--- a/frontend/src/stores/contentPlanningStore.ts
+++ b/frontend/src/stores/contentPlanningStore.ts
@@ -151,6 +151,7 @@ interface ContentPlanningStore {
// State
strategies: ContentStrategy[];
currentStrategy: ContentStrategy | null;
+ latestGeneratedStrategy: any | null; // Cache for the latest generated strategy
calendarEvents: CalendarEvent[];
gapAnalyses: ContentGapAnalysis[];
aiRecommendations: AIRecommendation[];
@@ -182,6 +183,7 @@ interface ContentPlanningStore {
updateStrategy: (id: string, updates: Partial) => Promise;
deleteStrategy: (id: string) => Promise;
setCurrentStrategy: (strategy: ContentStrategy | null) => void;
+ setLatestGeneratedStrategy: (strategy: any | null) => void;
// Calendar actions
createEvent: (event: Omit) => Promise;
@@ -269,6 +271,7 @@ export const useContentPlanningStore = create((set, get) =
// Initial state
strategies: [],
currentStrategy: null,
+ latestGeneratedStrategy: null,
calendarEvents: [],
gapAnalyses: [],
aiRecommendations: [],
@@ -345,6 +348,18 @@ export const useContentPlanningStore = create((set, get) =
},
setCurrentStrategy: (strategy) => set({ currentStrategy: strategy }),
+ setLatestGeneratedStrategy: (strategy) => {
+ console.log('๐ง Store: Setting latest generated strategy:', {
+ strategyId: strategy?.id || strategy?.strategy_id,
+ strategyName: strategy?.name || strategy?.strategy_name,
+ hasStrategicInsights: !!strategy?.strategic_insights,
+ hasCompetitiveAnalysis: !!strategy?.competitive_analysis,
+ hasPerformancePredictions: !!strategy?.performance_predictions,
+ hasImplementationRoadmap: !!strategy?.implementation_roadmap,
+ hasRiskAssessment: !!strategy?.risk_assessment
+ });
+ set({ latestGeneratedStrategy: strategy });
+ },
// Calendar actions
createEvent: async (event) => {
@@ -476,24 +491,17 @@ export const useContentPlanningStore = create((set, get) =
loadStrategies: async () => {
set({ loading: true, error: null });
try {
- console.log('๐ Loading strategies from API...');
const strategies = await contentPlanningApi.getStrategiesSafe();
- console.log('๐ API response for strategies:', strategies);
- console.log('๐ Strategies type:', typeof strategies);
- console.log('๐ Is Array:', Array.isArray(strategies));
if (Array.isArray(strategies)) {
- console.log('โ Strategies loaded successfully (direct array):', strategies.length);
set({ strategies, loading: false });
} else if (strategies && strategies.strategies && Array.isArray(strategies.strategies)) {
- console.log('โ Strategies found in response.strategies:', strategies.strategies.length);
set({ strategies: strategies.strategies, loading: false });
} else {
- console.log('โ No strategies found in response');
set({ strategies: [], loading: false });
}
} catch (error: any) {
- console.error('โ Error loading strategies:', error);
+ console.error('Error loading strategies:', error);
set({ error: error.message || 'Failed to load strategies', loading: false });
}
},
@@ -521,7 +529,7 @@ export const useContentPlanningStore = create((set, get) =
loadAIInsights: async () => {
set({ loading: true, error: null });
try {
- const response = await contentPlanningApi.getAIAnalyticsSafe();
+ const response = await contentPlanningApi.getAIAnalyticsWithRetry();
// Validate response structure
if (!response || typeof response !== 'object') {
@@ -559,6 +567,21 @@ export const useContentPlanningStore = create((set, get) =
set({ aiInsights: transformedInsights, loading: false });
} catch (error: any) {
console.error('Error loading AI insights:', error);
+
+ // Check if it's a rate limit error
+ if (error.message?.includes('rate limit') || error.message?.includes('429')) {
+ console.warn('โ ๏ธ Rate limit hit for AI insights, using empty data');
+ set({ aiInsights: [], loading: false });
+ return;
+ }
+
+ // Check if it's a network error
+ if (error.message?.includes('network') || error.message?.includes('connection')) {
+ console.warn('โ ๏ธ Network error for AI insights, using empty data');
+ set({ aiInsights: [], loading: false });
+ return;
+ }
+
set({ error: error.message || 'Failed to load AI insights', loading: false, aiInsights: [] });
}
},
@@ -566,7 +589,7 @@ export const useContentPlanningStore = create((set, get) =
loadAIRecommendations: async () => {
set({ loading: true, error: null });
try {
- const response = await contentPlanningApi.getAIAnalyticsSafe();
+ const response = await contentPlanningApi.getAIAnalyticsWithRetry();
// Validate response structure
if (!response || typeof response !== 'object') {
@@ -593,6 +616,21 @@ export const useContentPlanningStore = create((set, get) =
set({ aiRecommendations: transformedRecommendations, loading: false });
} catch (error: any) {
console.error('Error loading AI recommendations:', error);
+
+ // Check if it's a rate limit error
+ if (error.message?.includes('rate limit') || error.message?.includes('429')) {
+ console.warn('โ ๏ธ Rate limit hit for AI recommendations, using empty data');
+ set({ aiRecommendations: [], loading: false });
+ return;
+ }
+
+ // Check if it's a network error
+ if (error.message?.includes('network') || error.message?.includes('connection')) {
+ console.warn('โ ๏ธ Network error for AI recommendations, using empty data');
+ set({ aiRecommendations: [], loading: false });
+ return;
+ }
+
set({ error: error.message || 'Failed to load AI recommendations', loading: false, aiRecommendations: [] });
}
},
diff --git a/frontend/src/stores/strategyBuilderStore.ts b/frontend/src/stores/strategyBuilderStore.ts
index 7435cce4..143d599e 100644
--- a/frontend/src/stores/strategyBuilderStore.ts
+++ b/frontend/src/stores/strategyBuilderStore.ts
@@ -4,6 +4,78 @@ import { contentPlanningApi } from '../services/contentPlanningApi';
// Global flag to prevent multiple simultaneous auto-population calls
let isAutoPopulating = false;
+// Helper function to detect generic placeholder values
+const isGenericPlaceholder = (fieldId: string, value: any): boolean => {
+ if (!value) return false;
+
+ const stringValue = String(value).toLowerCase();
+
+ // Common generic placeholder patterns
+ const genericPatterns = [
+ 'example.com',
+ 'your website',
+ 'your business',
+ 'your industry',
+ 'your target audience',
+ 'your content',
+ 'your goals',
+ 'your objectives',
+ 'your metrics',
+ 'your budget',
+ 'your team',
+ 'your timeline',
+ 'your competitors',
+ 'your market',
+ 'your brand',
+ 'your voice',
+ 'your tone',
+ 'your strategy',
+ 'your plan',
+ 'your approach',
+ 'your focus',
+ 'your niche',
+ 'your sector',
+ 'your domain',
+ 'your company',
+ 'your organization',
+ 'your brand',
+ 'your product',
+ 'your service',
+ 'your offering',
+ 'your solution',
+ 'your value proposition',
+ 'your unique selling point',
+ 'your competitive advantage',
+ 'your market position',
+ 'your customer base',
+ 'your user base',
+ 'your audience',
+ 'your customers',
+ 'your clients',
+ 'your users',
+ 'your visitors',
+ 'your readers',
+ 'your followers',
+ 'your subscribers',
+ 'your leads',
+ 'your prospects',
+ 'your potential customers',
+ 'your target market',
+ 'your target segment',
+ 'your target demographic',
+ 'your target group',
+ 'your target population',
+ 'your target users',
+ 'your target readers',
+ 'your target audience',
+ 'your target customers',
+ 'your target clients'
+ ];
+
+ // Check if the value contains any generic placeholder patterns
+ return genericPatterns.some(pattern => stringValue.includes(pattern));
+};
+
// Enhanced Strategy Types
export interface EnhancedStrategy {
id: string;
@@ -638,7 +710,6 @@ export const useStrategyBuilderStore = create((set, get) =
if (forceRefresh) {
try {
await contentPlanningApi.clearEnhancedCache(1);
- console.log('โป๏ธ Cleared enhanced strategy cache for fresh onboarding data');
} catch (e) {
console.warn('Cache clear failed (non-blocking):', e);
}
@@ -646,16 +717,55 @@ export const useStrategyBuilderStore = create((set, get) =
// Fetch onboarding data to auto-populate fields
const response = await contentPlanningApi.getOnboardingData();
- console.log('๐ก Backend response:', response);
+
+ // Enhanced logging for autofill data
+ console.log('๐ Autofill Response Structure:', {
+ hasResponse: !!response,
+ responseKeys: response ? Object.keys(response) : [],
+ fieldsCount: response?.fields ? Object.keys(response.fields).length : 0,
+ sourcesCount: response?.sources ? Object.keys(response.sources).length : 0,
+ inputDataPointsCount: response?.input_data_points ? Object.keys(response.input_data_points).length : 0,
+ hasMeta: !!response?.meta
+ });
+
+ // Validate response structure
+ if (!response) {
+ throw new Error('Invalid response structure from backend');
+ }
// Extract field values and sources from the new backend format
- const fields = response.data?.fields || {};
- const sources = response.data?.sources || {};
- const inputDataPoints = response.data?.input_data_points || {};
+ const fields = response.fields || {};
+ const sources = response.sources || {};
+ const inputDataPoints = response.input_data_points || {};
- console.log('๐ Extracted fields:', fields);
- console.log('๐ Data sources:', sources);
- console.log('๐ Input data points:', inputDataPoints);
+ // Log detailed field information
+ console.log('๐ฏ Autofill Field Details:', {
+ totalFields: Object.keys(fields).length,
+ fieldIds: Object.keys(fields),
+ sampleFieldData: Object.keys(fields).slice(0, 3).map(id => ({
+ id,
+ hasValue: !!fields[id]?.value,
+ hasPersonalization: !!fields[id]?.personalization_data,
+ hasConfidence: !!fields[id]?.confidence_score,
+ valueType: typeof fields[id]?.value
+ }))
+ });
+
+ // Validate AI generation success
+ const meta = response.meta || {};
+ console.log('๐ค AI Generation Meta:', {
+ aiUsed: meta.ai_used,
+ aiOverridesCount: meta.ai_overrides_count,
+ error: meta.error,
+ processingTime: meta.processing_time
+ });
+
+ if (meta.ai_used === false || meta.ai_overrides_count === 0) {
+ console.log('โ AI generation failed - no real AI values produced');
+ throw new Error(meta.error || 'AI generation failed to produce strategy fields. Please try again.');
+ }
+
+ console.log('โ AI generation successful:', Object.keys(fields).length, 'fields');
// Transform the fields object to extract values for formData
const fieldValues: Record = {};
@@ -663,61 +773,77 @@ export const useStrategyBuilderStore = create((set, get) =
const personalizationData: Record = {};
const confidenceScores: Record = {};
- // Check if fields is empty and provide fallback
- if (Object.keys(fields).length === 0) {
- console.log('โ ๏ธ No fields found in onboarding data, using default values');
+ // Check if fields is empty - no fallback to mock data
+ if (Object.keys(fields).length === 0) {
+ console.log('โ No fields found in onboarding data - AI generation may have failed');
+ console.log('๐ซ No fallback to mock data - user must retry or provide manual input');
+
+ // Set error state instead of mock data
+ set({
+ loading: false,
+ error: 'AI generation failed to produce strategy fields. Please try again or provide manual input.',
+ autoPopulatedFields: {},
+ personalizationData: {},
+ dataSources: {},
+ inputDataPoints: {}
+ });
+ return;
+ }
+
+ // Process actual fields from backend
+ let processedFields = 0;
+ let skippedFields = 0;
+ let fieldsWithPersonalization = 0;
+ let fieldsWithConfidence = 0;
- // Set default values for strategy builder
- const defaultFields: Record = {
- industry: 'Technology',
- business_objectives: 'Increase brand awareness and drive sales',
- target_metrics: { traffic: 10000, conversion_rate: 2.5 },
- content_budget: 5000,
- team_size: 3,
- content_preferences: ['Blog posts', 'Social media', 'Email marketing'],
- preferred_formats: ['Blog posts', 'Whitepapers', 'Videos'],
- content_mix: { blog_posts: 40, whitepapers: 20, videos: 15, social_media: 25 }
- };
-
- Object.keys(defaultFields).forEach(fieldId => {
- fieldValues[fieldId] = defaultFields[fieldId];
- autoPopulatedFields[fieldId] = defaultFields[fieldId];
- confidenceScores[fieldId] = 0.7; // Medium confidence for defaults
- console.log(`โ Set default value for ${fieldId}:`, defaultFields[fieldId]);
- });
- } else {
- // Process actual fields from backend
Object.keys(fields).forEach(fieldId => {
const fieldData = fields[fieldId];
- console.log(`๐ Processing field ${fieldId}:`, fieldData);
if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) {
- fieldValues[fieldId] = fieldData.value;
- autoPopulatedFields[fieldId] = fieldData.value;
+ // Validate that the value is not a generic placeholder
+ const value = fieldData.value;
+ const isGenericPlaceholderValue = isGenericPlaceholder(fieldId, value);
+
+ if (isGenericPlaceholderValue) {
+ console.log(`โ ๏ธ Skipping ${fieldId} - contains generic placeholder value: "${value}"`);
+ skippedFields++;
+ return; // Skip this field
+ }
+
+ fieldValues[fieldId] = value;
+ autoPopulatedFields[fieldId] = value;
+ processedFields++;
// Extract personalization data if available
if (fieldData.personalization_data) {
personalizationData[fieldId] = fieldData.personalization_data;
- console.log(`๐ฏ Personalization data for ${fieldId}:`, fieldData.personalization_data);
+ fieldsWithPersonalization++;
}
// Extract confidence score if available
if (fieldData.confidence_score) {
confidenceScores[fieldId] = fieldData.confidence_score;
- console.log(`๐ฏ Confidence score for ${fieldId}:`, fieldData.confidence_score);
+ fieldsWithConfidence++;
}
-
- console.log(`โ Auto-populated ${fieldId}:`, fieldData.value);
} else {
- console.log(`โ Skipping ${fieldId} - invalid data structure`);
+ console.log(`โ Skipping ${fieldId} - invalid data structure:`, fieldData);
+ skippedFields++;
}
});
- }
+
+ // Log field processing summary
+ console.log('๐ Field Processing Summary:', {
+ totalFields: Object.keys(fields).length,
+ processedFields,
+ skippedFields,
+ fieldsWithPersonalization,
+ fieldsWithConfidence,
+ averageConfidence: fieldsWithConfidence > 0
+ ? (Object.values(confidenceScores).filter((score): score is number => typeof score === 'number').reduce((a, b) => a + b, 0) / fieldsWithConfidence).toFixed(1)
+ : 'N/A'
+ });
- console.log('๐ Final field values:', fieldValues);
- console.log('๐ Final auto-populated fields:', autoPopulatedFields);
- console.log('๐ฏ Personalization data:', personalizationData);
- console.log('๐ฏ Confidence scores:', confidenceScores);
+ console.log('โ Auto-population completed:', Object.keys(fieldValues).length, 'fields');
set((state) => ({
autoPopulatedFields,
@@ -728,85 +854,100 @@ export const useStrategyBuilderStore = create((set, get) =
formData: { ...state.formData, ...fieldValues }
}));
- console.log('โ Auto-population completed successfully');
- } catch (error: any) {
- console.error('โ Auto-population error:', error);
- const errorMessage = error.message || 'Failed to auto-populate from onboarding';
- set({
- error: errorMessage,
- loading: false
+ // Log final state summary
+ console.log('๐ Auto-population State Summary:', {
+ autoPopulatedFieldsCount: Object.keys(autoPopulatedFields).length,
+ dataSourcesCount: Object.keys(sources).length,
+ inputDataPointsCount: Object.keys(inputDataPoints).length,
+ personalizationDataCount: Object.keys(personalizationData).length,
+ confidenceScoresCount: Object.keys(confidenceScores).length,
+ formDataUpdated: Object.keys(fieldValues).length,
+ timestamp: new Date().toISOString()
});
- // If it's a rate limit error, set a flag to prevent further attempts
- if (errorMessage.includes('Too many requests') || errorMessage.includes('No response from server')) {
- set({ autoPopulationBlocked: true });
- }
- } finally {
- set({ loading: false });
- isAutoPopulating = false; // Reset global flag
- }
- },
-
- updateAutoPopulatedField: (fieldId, value, source) => {
- set((state) => ({
- autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value },
- dataSources: { ...state.dataSources, [fieldId]: source }
- }));
- },
-
- overrideAutoPopulatedField: (fieldId, value) => {
- set((state) => ({
- formData: { ...state.formData, [fieldId]: value },
- autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value }
- }));
- },
-
- // UI Actions
- setLoading: (loading) => set({ loading }),
- setError: (error) => set({ error }),
- setSaving: (saving) => set({ saving }),
-
- // Completion Tracking
- calculateCompletionPercentage: () => {
- const formData = get().formData;
- const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
- const filledRequiredFields = requiredFields.filter(field => {
- const value = formData[field.id];
- return value && (Array.isArray(value) ? value.length > 0 : true);
- });
-
- return requiredFields.length > 0 ? (filledRequiredFields.length / requiredFields.length) * 100 : 0;
- },
-
- getCompletionStats: () => {
- const formData = get().formData;
- const totalFields = STRATEGIC_INPUT_FIELDS.length;
- const filledFields = STRATEGIC_INPUT_FIELDS.filter(field => {
- const value = formData[field.id];
- return value && (Array.isArray(value) ? value.length > 0 : true);
- }).length;
-
- const completionPercentage = totalFields > 0 ? (filledFields / totalFields) * 100 : 0;
-
- // Calculate completion by category
- const categoryCompletion: Record = {};
- const categories = Array.from(new Set(STRATEGIC_INPUT_FIELDS.map(field => field.category)));
-
- categories.forEach(category => {
- const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
- const filledCategoryFields = categoryFields.filter(field => {
- const value = formData[field.id];
- return value && (Array.isArray(value) ? value.length > 0 : true);
- }).length;
-
- categoryCompletion[category] = categoryFields.length > 0 ? (filledCategoryFields / categoryFields.length) * 100 : 0;
- });
-
- return {
- total_fields: totalFields,
- filled_fields: filledFields,
- completion_percentage: completionPercentage,
- category_completion: categoryCompletion
- };
- }
-}));
+ console.log('โ Auto-population completed successfully');
+
+ // Store the autofill completion time
+ sessionStorage.setItem('lastAutofillTime', new Date().toISOString());
+
+ } catch (error: any) {
+ console.error('โ Auto-population error:', error);
+ const errorMessage = error.message || 'Failed to auto-populate from onboarding';
+ set({
+ error: errorMessage,
+ loading: false
+ });
+
+ // If it's a rate limit error, set a flag to prevent further attempts
+ if (errorMessage.includes('Too many requests') || errorMessage.includes('No response from server')) {
+ set({ autoPopulationBlocked: true });
+ }
+ } finally {
+ set({ loading: false });
+ isAutoPopulating = false; // Reset global flag
+ }
+ },
+
+ updateAutoPopulatedField: (fieldId: string, value: any, source: string) => {
+ set((state) => ({
+ autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value },
+ dataSources: { ...state.dataSources, [fieldId]: source }
+ }));
+ },
+
+ overrideAutoPopulatedField: (fieldId: string, value: any) => {
+ set((state) => ({
+ formData: { ...state.formData, [fieldId]: value },
+ autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value }
+ }));
+ },
+
+ // UI Actions
+ setLoading: (loading: boolean) => set({ loading }),
+ setError: (error: string | null) => set({ error }),
+ setSaving: (saving: boolean) => set({ saving }),
+
+ // Completion Tracking
+ calculateCompletionPercentage: () => {
+ const formData = get().formData;
+ const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
+ const filledRequiredFields = requiredFields.filter(field => {
+ const value = formData[field.id];
+ return value && (Array.isArray(value) ? value.length > 0 : true);
+ });
+
+ return requiredFields.length > 0 ? (filledRequiredFields.length / requiredFields.length) * 100 : 0;
+ },
+
+ getCompletionStats: () => {
+ const formData = get().formData;
+ const totalFields = STRATEGIC_INPUT_FIELDS.length;
+ const filledFields = STRATEGIC_INPUT_FIELDS.filter(field => {
+ const value = formData[field.id];
+ return value && (Array.isArray(value) ? value.length > 0 : true);
+ }).length;
+
+ const completionPercentage = totalFields > 0 ? (filledFields / totalFields) * 100 : 0;
+
+ // Calculate completion by category
+ const categoryCompletion: Record = {};
+ const categories = Array.from(new Set(STRATEGIC_INPUT_FIELDS.map(field => field.category)));
+
+ categories.forEach(category => {
+ const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
+ const filledCategoryFields = categoryFields.filter(field => {
+ const value = formData[field.id];
+ return value && (Array.isArray(value) ? value.length > 0 : true);
+ }).length;
+
+ categoryCompletion[category] = categoryFields.length > 0 ? (filledCategoryFields / categoryFields.length) * 100 : 0;
+ });
+
+ return {
+ total_fields: totalFields,
+ filled_fields: filledFields,
+ completion_percentage: completionPercentage,
+ category_completion: categoryCompletion
+ };
+ }
+ }));
diff --git a/frontend/src/stores/strategyReviewStore.ts b/frontend/src/stores/strategyReviewStore.ts
index c80a0dca..5183c3e4 100644
--- a/frontend/src/stores/strategyReviewStore.ts
+++ b/frontend/src/stores/strategyReviewStore.ts
@@ -171,7 +171,24 @@ export const useStrategyReviewStore = create()(
// Start review process
startReviewProcess: () => {
- set({ reviewProcessStarted: true });
+ console.log('๐ง Starting review process - resetting all reviews first');
+ // Reset all reviews when starting a new review process
+ const { components } = get();
+ const resetComponents = components.map(comp => ({
+ ...comp,
+ status: 'not_reviewed' as ReviewStatus,
+ reviewedAt: undefined,
+ reviewedBy: undefined,
+ notes: undefined
+ }));
+
+ set({
+ reviewProcessStarted: true,
+ components: resetComponents,
+ reviewProgress: 0
+ });
+
+ console.log('๐ง Review process started with reset components');
},
// Update review progress
@@ -181,7 +198,6 @@ export const useStrategyReviewStore = create()(
const totalCount = components.length;
const progress = totalCount > 0 ? (reviewedCount / totalCount) * 100 : 0;
- console.log('๐ง Updating progress:', { reviewedCount, totalCount, progress, components: components.map(c => ({ id: c.id, status: c.status })) });
set({ reviewProgress: progress });
},