From ae42720c2a62eec2b5baee08a047132c81cc13ce Mon Sep 17 00:00:00 2001 From: ajaysi Date: Sat, 6 Sep 2025 15:28:05 +0530 Subject: [PATCH] Alwrity today's tasks workflow implementation plan. --- ...DAYS_TASKS_WORKFLOW_IMPLEMENTATION_PLAN.md | 273 ++++++++ .../MainDashboard/ContentLifecyclePillars.tsx | 517 +++++++++++++++ .../MainDashboard/MainDashboard.tsx | 146 ++++- .../components/AnalyzePillarChips.tsx | 246 +++++++ .../components/EngagePillarChips.tsx | 235 +++++++ .../components/EnhancedTodayChip.tsx | 233 +++++++ .../components/EnhancedTodayModal.tsx | 498 ++++++++++++++ .../components/GeneratePillarChips.tsx | 609 +++++++++++++++++ .../components/OnboardingModal.tsx | 324 ++++++++++ .../MainDashboard/components/PillarData.tsx | 576 +++++++++++++++++ .../components/PublishPillarChips.tsx | 338 ++++++++++ .../components/TaskNavigationControls.tsx | 354 ++++++++++ .../MainDashboard/components/WorkflowDemo.tsx | 496 ++++++++++++++ .../components/WorkflowProgressBar.tsx | 251 +++++++ .../src/components/shared/DashboardHeader.tsx | 245 ++++++- .../src/components/shared/SearchFilter.tsx | 13 +- frontend/src/components/shared/styled.ts | 41 +- frontend/src/components/shared/types.ts | 13 +- frontend/src/data/toolCategories.ts | 1 - .../src/services/TaskCompletionVerifier.ts | 426 ++++++++++++ .../src/services/TaskDependencyManager.ts | 433 +++++++++++++ .../src/services/TaskNavigationService.ts | 469 ++++++++++++++ .../src/services/TaskWorkflowOrchestrator.ts | 611 ++++++++++++++++++ frontend/src/stores/workflowStore.ts | 366 +++++++++++ frontend/src/types/workflow.ts | 168 +++++ 25 files changed, 7836 insertions(+), 46 deletions(-) create mode 100644 docs/TODAYS_TASKS_WORKFLOW_IMPLEMENTATION_PLAN.md create mode 100644 frontend/src/components/MainDashboard/ContentLifecyclePillars.tsx create mode 100644 frontend/src/components/MainDashboard/components/AnalyzePillarChips.tsx create mode 100644 frontend/src/components/MainDashboard/components/EngagePillarChips.tsx create mode 100644 frontend/src/components/MainDashboard/components/EnhancedTodayChip.tsx create mode 100644 frontend/src/components/MainDashboard/components/EnhancedTodayModal.tsx create mode 100644 frontend/src/components/MainDashboard/components/GeneratePillarChips.tsx create mode 100644 frontend/src/components/MainDashboard/components/OnboardingModal.tsx create mode 100644 frontend/src/components/MainDashboard/components/PillarData.tsx create mode 100644 frontend/src/components/MainDashboard/components/PublishPillarChips.tsx create mode 100644 frontend/src/components/MainDashboard/components/TaskNavigationControls.tsx create mode 100644 frontend/src/components/MainDashboard/components/WorkflowDemo.tsx create mode 100644 frontend/src/components/MainDashboard/components/WorkflowProgressBar.tsx create mode 100644 frontend/src/services/TaskCompletionVerifier.ts create mode 100644 frontend/src/services/TaskDependencyManager.ts create mode 100644 frontend/src/services/TaskNavigationService.ts create mode 100644 frontend/src/services/TaskWorkflowOrchestrator.ts create mode 100644 frontend/src/stores/workflowStore.ts create mode 100644 frontend/src/types/workflow.ts diff --git a/docs/TODAYS_TASKS_WORKFLOW_IMPLEMENTATION_PLAN.md b/docs/TODAYS_TASKS_WORKFLOW_IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..812124f8 --- /dev/null +++ b/docs/TODAYS_TASKS_WORKFLOW_IMPLEMENTATION_PLAN.md @@ -0,0 +1,273 @@ +# Today's Tasks Workflow System - Implementation Plan + +## 📋 **Overview** + +The Today's Tasks Workflow System is designed to transform ALwrity's complex digital marketing platform into a guided, user-friendly daily workflow. This system addresses the challenge of navigating multiple social media platforms, website management, and analytics by providing a single glass pane view with actionable daily tasks. + +## 🎯 **Core Vision** + +### **Problem Statement** +- Digital marketing is complex and daunting for non-technical users +- Multiple platforms and tools create navigation confusion +- Users need guidance on what actions to take daily +- Lack of structured workflow leads to incomplete marketing activities + +### **Solution Approach** +- Present users with a curated set of daily actions via "Today's Tasks" in each pillar +- Guide users through a structured workflow using the "ALwrity it" button +- Automatically navigate users between tasks and platforms +- Provide completion tracking and progress indicators +- Hand-hold users through the entire marketing workflow + +## 🏗️ **System Architecture** + +### **Core Components** + +#### **1. Task Management System** +- Centralized task repository with status tracking +- Task dependency management +- Priority and time estimation system +- Completion verification mechanisms + +#### **2. Workflow Orchestrator** +- Daily workflow generation and management +- Task sequencing and dependency resolution +- Progress tracking and state management +- Auto-navigation between tasks + +#### **3. User Interface Components** +- Enhanced Today's Task modals with workflow features +- Progress indicators and completion tracking +- Seamless navigation between tasks +- Task status visualization + +#### **4. Intelligence Layer** +- AI-powered task generation based on user behavior +- Personalized task recommendations +- Completion verification and validation +- Analytics and insights generation + +## 🔄 **Workflow Design** + +### **Task Flow Sequence** +1. **Plan Pillar**: Content strategy and calendar review +2. **Generate Pillar**: Content creation tasks +3. **Publish Pillar**: Social media and website publishing +4. **Analyze Pillar**: Performance review and insights +5. **Engage Pillar**: Community interaction and responses +6. **Remarket Pillar**: Retargeting and follow-up campaigns + +### **User Journey** +1. User logs into ALwrity dashboard +2. System presents Today's Tasks for each pillar +3. User clicks "Start Today's Workflow" or individual task +4. System guides user through task completion +5. Auto-navigation to next task in sequence +6. Progress tracking and completion celebration +7. Daily workflow completion summary + +## 📊 **Data Models** + +### **Task Structure** +- Unique task identifier +- Pillar association and priority level +- Task title, description, and estimated time +- Status tracking (pending, in-progress, completed, skipped) +- Dependencies and prerequisites +- Action type and navigation details +- Completion metadata and timestamps + +### **Workflow State** +- Daily workflow instance +- Current task index and progress +- Completed tasks count and percentage +- Workflow status and user session data +- Task completion history and analytics + +## 🎨 **User Experience Design** + +### **Visual Enhancements** +- Workflow progress bar on main dashboard +- Enhanced Today's Task modals with status indicators +- Task completion animations and celebrations +- Real-time progress updates across components +- Mobile-responsive workflow interface + +### **Interaction Patterns** +- One-click task initiation +- Guided navigation between platforms +- Contextual help and tooltips +- Task completion confirmation +- Next task auto-suggestion + +## 🚀 **Implementation Phases** + +### **Phase 1: Foundation (Weeks 1-2)** +**Objective**: Establish core workflow infrastructure + +**Deliverables**: +- TaskWorkflowOrchestrator service implementation +- Basic task data structure and persistence +- Enhanced Today's Task modal with workflow features +- Workflow progress indicators on dashboard +- Task status tracking system + +**Key Features**: +- Manual task creation and management +- Basic progress tracking +- Simple navigation between tasks +- Task completion marking + +### **Phase 2: Smart Navigation (Weeks 3-4)** +**Objective**: Implement intelligent task flow and navigation + +**Deliverables**: +- Auto-navigation system between tasks +- Task dependency management +- Completion verification mechanisms +- Task sequencing logic +- Cross-platform navigation handling + +**Key Features**: +- Seamless transitions between ALwrity tools +- Task prerequisite checking +- Progress persistence across sessions +- Error handling and fallback mechanisms + +### **Phase 3: Intelligence Layer (Weeks 5-6)** +**Objective**: Add AI-powered task generation and personalization + +**Deliverables**: +- AI-powered daily task generation +- User behavior analysis and learning +- Personalized task recommendations +- Completion verification using platform APIs +- Smart task prioritization + +**Key Features**: +- Dynamic task generation based on user activity +- Learning from user completion patterns +- Integration with existing ALwrity features +- Intelligent task ordering and timing + +### **Phase 4: Advanced Features (Weeks 7-8)** +**Objective**: Enhance user experience and add advanced capabilities + +**Deliverables**: +- Gamification elements (points, streaks, achievements) +- Team collaboration features +- Advanced analytics and insights +- Mobile optimization +- A/B testing framework + +**Key Features**: +- User engagement and motivation systems +- Multi-user workflow coordination +- Performance analytics and reporting +- Mobile-responsive design +- Continuous improvement mechanisms + +## 🎯 **Success Metrics** + +### **User Engagement** +- Daily workflow completion rate +- Task completion time reduction +- User retention and return visits +- Feature adoption rates + +### **Business Impact** +- Marketing activity completion increase +- Content publishing frequency improvement +- Social media engagement growth +- Overall platform usage enhancement + +### **Technical Performance** +- Task generation accuracy +- Navigation success rate +- System response times +- Error rates and recovery + +## 🔧 **Technical Considerations** + +### **Integration Points** +- Existing ALwrity platform components +- Social media platform APIs +- Analytics and tracking systems +- User authentication and profiles +- Content management systems + +### **Scalability Requirements** +- Support for multiple user workflows +- Real-time progress synchronization +- Offline task completion support +- Performance optimization for large task sets + +### **Security and Privacy** +- User data protection and encryption +- Secure API integrations +- Privacy-compliant analytics +- Access control and permissions + +## 📈 **Future Enhancements** + +### **Advanced AI Features** +- Predictive task generation +- Automated content suggestions +- Performance optimization recommendations +- Intelligent scheduling and timing + +### **Collaboration Features** +- Team workflow coordination +- Task assignment and delegation +- Progress sharing and reporting +- Multi-user dashboard views + +### **Integration Expansions** +- Third-party tool integrations +- Advanced analytics platforms +- CRM and marketing automation +- E-commerce platform connections + +## 🎉 **Expected Outcomes** + +### **User Benefits** +- Simplified daily marketing workflow +- Reduced cognitive load and decision fatigue +- Increased marketing activity completion +- Improved platform adoption and retention + +### **Business Benefits** +- Higher user engagement and satisfaction +- Increased platform stickiness +- Better marketing results for users +- Competitive differentiation in the market + +### **Technical Benefits** +- Modular and extensible architecture +- Reusable workflow components +- Scalable task management system +- Foundation for future AI features + +## 📝 **Next Steps** + +1. **Immediate Actions**: + - Review and approve implementation plan + - Set up development environment and tools + - Create detailed technical specifications + - Begin Phase 1 development + +2. **Stakeholder Alignment**: + - Present plan to development team + - Gather feedback from product team + - Validate approach with user research + - Secure necessary resources and timeline + +3. **Development Preparation**: + - Create detailed user stories and acceptance criteria + - Set up project tracking and milestone management + - Establish testing and quality assurance processes + - Plan for user feedback and iteration cycles + +--- + +*This document serves as the foundation for implementing the Today's Tasks Workflow System. It should be reviewed and updated regularly as the project progresses and new insights are gained.* diff --git a/frontend/src/components/MainDashboard/ContentLifecyclePillars.tsx b/frontend/src/components/MainDashboard/ContentLifecyclePillars.tsx new file mode 100644 index 00000000..6fa43585 --- /dev/null +++ b/frontend/src/components/MainDashboard/ContentLifecyclePillars.tsx @@ -0,0 +1,517 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Container, + Typography, + Card, + CardContent, + useTheme, + useMediaQuery, + Chip, + Tooltip, + Paper, + Modal, + Button, + IconButton, + Divider, + LinearProgress, + Avatar, + Stack +} from '@mui/material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Close as CloseIcon, + Settings as SettingsIcon, + CheckCircle as CheckIcon, + RadioButtonUnchecked as UncheckedIcon, + TrendingUp as TrendingUpIcon +} from '@mui/icons-material'; +import GeneratePillarChips from './components/GeneratePillarChips'; +import PublishPillarChips from './components/PublishPillarChips'; +import AnalyzePillarChips from './components/AnalyzePillarChips'; +import EngagePillarChips from './components/EngagePillarChips'; +import EnhancedTodayChip from './components/EnhancedTodayChip'; +import OnboardingModal from './components/OnboardingModal'; +import { pillarData } from './components/PillarData'; +import { useWorkflowStore } from '../../stores/workflowStore'; + + +// Enhanced Glassomorphic Chip Component with Popping Effects +const ChipWithTooltip: React.FC<{ + chip: any; + delay?: number; + onOnboardingClick?: () => void; +}> = ({ chip, delay = 0, onOnboardingClick }) => { + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setCurrentIndex((prev) => (prev + 1) % chip.bubbles.length); + }, 2000 + delay * 300); + + return () => clearInterval(interval); + }, [chip.bubbles.length, delay]); + + const IconComponent = chip.icon; + + const handleClick = () => { + if (chip.label === 'On-Boarding' && onOnboardingClick) { + onOnboardingClick(); + } + }; + + return ( + + + {chip.label} + + + + + {chip.bubbles[currentIndex]} + + + + + } + arrow + placement="top" + > + + {/* Glow Effect */} + + + {/* Shadow Effect */} + + + {/* Main Chip */} + } + label={ + + + {chip.label} + + {chip.value && ( + + {chip.value} + + )} + + } + size="small" + sx={{ + background: `linear-gradient(135deg, + rgba(255,255,255,0.25) 0%, + rgba(255,255,255,0.1) 50%, + rgba(255,255,255,0.05) 100%)`, + backdropFilter: 'blur(20px)', + border: '1px solid rgba(255,255,255,0.3)', + color: 'white', + fontSize: '0.7rem', + fontWeight: 600, + height: 28, + minWidth: 100, + position: 'relative', + overflow: 'hidden', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: '-100%', + width: '100%', + height: '100%', + background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)', + transition: 'left 0.6s ease', + zIndex: 1 + }, + '&:hover::before': { + left: '100%' + }, + '& .MuiChip-label': { + px: 1, + zIndex: 2, + position: 'relative' + }, + '& .MuiChip-icon': { + zIndex: 2, + position: 'relative' + }, + '&:hover': { + background: `linear-gradient(135deg, + rgba(255,255,255,0.35) 0%, + rgba(255,255,255,0.2) 50%, + rgba(255,255,255,0.1) 100%)`, + border: '1px solid rgba(255,255,255,0.5)', + boxShadow: `0 8px 32px ${chip.color}40, + 0 4px 16px rgba(0,0,0,0.1), + inset 0 1px 0 rgba(255,255,255,0.3)` + } + }} + /> + + + ); +}; + +// Enhanced Pillar Component with Progressive Disclosure +const PillarCard: React.FC<{ + pillar: typeof pillarData[0]; + index: number; + onOnboardingClick?: () => void; +}> = ({ pillar, index, onOnboardingClick }) => { + const IconComponent = pillar.icon; + const [isHovered, setIsHovered] = useState(false); + const { currentWorkflow } = useWorkflowStore(); + + // Use live workflow tasks if available + const liveTasksForPillar = (currentWorkflow?.tasks && currentWorkflow.tasks.length > 0 + ? currentWorkflow.tasks + : pillar.todayTasks || []).filter((t: any) => t.pillarId === pillar.id); + const totalForPillar = liveTasksForPillar.length; + const doneForPillar = liveTasksForPillar.filter((t: any) => t.status === 'completed' || t.status === 'skipped').length; + + return ( + + 0 && doneForPillar === totalForPillar ? '"✓"' : '""', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + fontSize: '64px', + color: 'rgba(255,255,255,0.9)', + textShadow: '0 4px 12px rgba(0,0,0,0.5)', + pointerEvents: 'none', + zIndex: 10, // Ensure tick is above all content + fontWeight: 'bold' + }, + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, transparent 50%)', + opacity: isHovered ? 1 : 0, + transition: 'opacity 0.3s ease' + }, + '&:hover': { + boxShadow: `0 12px 24px ${pillar.color}40` + } + }} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + {/* Header */} + + + + + + {pillar.title} + + {/* Pillar task count badge */} + + + {totalForPillar} + + + {/* More Options Indicator */} + {!isHovered && ( + + + ⋯ + + + )} + + + {/* Chips Layout with Progressive Disclosure */} + {pillar.id === 'generate' ? ( + + ) : pillar.id === 'publish' ? ( + + ) : pillar.id === 'analyze' ? ( + + ) : pillar.id === 'engage' ? ( + + ) : ( + + {/* Today Chip - Always Visible */} + + + {/* Additional Chips - Progressive Disclosure */} + + {isHovered && ( + + + {pillar.id === 'plan' ? ( + <> + + + + + + + + + + + + + + ) : pillar.id === 'remarket' ? ( + <> + + + + + + + + + + + + + + ) : null} + + + )} + + + )} + + + + ); +}; + +// Main Content Lifecycle Pillars Component +const ContentLifecyclePillars: React.FC = () => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const [onboardingModalOpen, setOnboardingModalOpen] = useState(false); + + const handleOnboardingClick = () => { + setOnboardingModalOpen(true); + }; + + const handleCloseModal = () => { + setOnboardingModalOpen(false); + }; + + return ( + <> + + + {/* Pillars Grid */} + + {pillarData.map((pillar, index) => ( + + ))} + + + + + {/* Onboarding Modal */} + + + ); +}; + +export default ContentLifecyclePillars; \ No newline at end of file diff --git a/frontend/src/components/MainDashboard/MainDashboard.tsx b/frontend/src/components/MainDashboard/MainDashboard.tsx index dd032c6d..d7f79b75 100644 --- a/frontend/src/components/MainDashboard/MainDashboard.tsx +++ b/frontend/src/components/MainDashboard/MainDashboard.tsx @@ -10,6 +10,7 @@ import { } from '@mui/material'; import { motion, AnimatePresence } from 'framer-motion'; import { useNavigate } from 'react-router-dom'; +import AskAlwrityIcon from '../../assets/images/AskAlwrity-min.ico'; // Shared components import DashboardHeader from '../shared/DashboardHeader'; @@ -20,13 +21,15 @@ import CategoryHeader from '../shared/CategoryHeader'; import LoadingSkeleton from '../shared/LoadingSkeleton'; import ErrorDisplay from '../shared/ErrorDisplay'; import EmptyState from '../shared/EmptyState'; +import ContentLifecyclePillars from './ContentLifecyclePillars'; // Shared types and utilities -import { Tool, Category } from '../shared/types'; +import { Tool } from '../shared/types'; import { getFilteredCategories, getToolsForCategory } from '../shared/utils'; -// Zustand store +// Zustand stores import { useDashboardStore } from '../../stores/dashboardStore'; +import { useWorkflowStore } from '../../stores/workflowStore'; // Data import { toolCategories } from '../../data/toolCategories'; @@ -34,7 +37,6 @@ import { toolCategories } from '../../data/toolCategories'; // Main dashboard component const MainDashboard: React.FC = () => { const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); const navigate = useNavigate(); // Zustand store hooks @@ -50,13 +52,114 @@ const MainDashboard: React.FC = () => { setSearchQuery, setSelectedCategory, setSelectedSubCategory, - setError, - setLoading, showSnackbar, hideSnackbar, clearFilters, } = useDashboardStore(); + // Workflow store hooks + const { + currentWorkflow, + workflowProgress, + isLoading: workflowLoading, + generateDailyWorkflow, + startWorkflow, + pauseWorkflow, + stopWorkflow + } = useWorkflowStore(); + + // Initialize workflow on component mount + React.useEffect(() => { + const initializeWorkflow = async () => { + try { + // Generate daily workflow for current user + // In a real app, you'd get the actual user ID from auth context + const userId = 'demo-user'; // Replace with actual user ID + await generateDailyWorkflow(userId); + } catch (error) { + console.warn('Failed to initialize workflow:', error); + } + }; + + initializeWorkflow(); + }, [generateDailyWorkflow]); + + // Debug logging for workflow state + React.useEffect(() => { + console.log('Workflow Debug:', { + currentWorkflow, + workflowProgress, + isWorkflowActive: currentWorkflow?.workflowStatus === 'in_progress', + workflowStatus: currentWorkflow?.workflowStatus, + hasWorkflow: !!currentWorkflow + }); + }, [currentWorkflow, workflowProgress]); + + // State to track if we need to start a newly generated workflow + const [shouldStartWorkflow, setShouldStartWorkflow] = React.useState(false); + + // Handle workflow start + const handleStartWorkflow = async () => { + try { + if (currentWorkflow) { + await startWorkflow(currentWorkflow.id); + } else { + // Generate workflow first, then mark that we should start it + await generateDailyWorkflow('demo-user'); + setShouldStartWorkflow(true); + } + } catch (error) { + console.error('Failed to start workflow:', error); + } + }; + + // Auto-start workflow after generation + React.useEffect(() => { + if (shouldStartWorkflow && currentWorkflow && currentWorkflow.workflowStatus === 'not_started') { + const startGeneratedWorkflow = async () => { + try { + await startWorkflow(currentWorkflow.id); + setShouldStartWorkflow(false); + } catch (error) { + console.error('Failed to start generated workflow:', error); + setShouldStartWorkflow(false); + } + }; + startGeneratedWorkflow(); + } + }, [shouldStartWorkflow, currentWorkflow, startWorkflow]); + + // Handle workflow pause + const handlePauseWorkflow = async () => { + if (currentWorkflow) { + try { + await pauseWorkflow(currentWorkflow.id); + } catch (error) { + console.error('Failed to pause workflow:', error); + } + } + }; + + // Handle workflow stop + const handleStopWorkflow = async () => { + if (currentWorkflow) { + try { + await stopWorkflow(currentWorkflow.id); + } catch (error) { + console.error('Failed to stop workflow:', error); + } + } + }; + + // Resume Plan modal from header In-Progress button + const handleResumePlanModal = () => { + // Programmatically click the Plan pillar Today chip + const planChip = document.querySelector('[data-pillar-id="plan"]'); + if (planChip) { + (planChip as HTMLElement).click(); + } + }; + const handleToolClick = (tool: Tool) => { console.log('Navigating to tool:', tool.path); if (tool.path) { @@ -120,12 +223,27 @@ const MainDashboard: React.FC = () => { > {/* Dashboard Header */} } + customIcon={AskAlwrityIcon} + workflowControls={{ + onStartWorkflow: handleStartWorkflow, + onPauseWorkflow: handlePauseWorkflow, + onStopWorkflow: handleStopWorkflow, + onResumePlanModal: handleResumePlanModal, + isWorkflowActive: currentWorkflow?.workflowStatus === 'in_progress', + completedTasks: workflowProgress?.completedTasks || 0, + totalTasks: workflowProgress?.totalTasks || 0, + isLoading: workflowLoading + }} /> + + {/* Content Lifecycle Pillars - First Panel */} + + {/* Search and Filter */} { transition={{ duration: 0.5, delay: categoryIndex * 0.1 }} > - {/* Category Header */} - + {/* Only show Category Header when no specific category is selected (showing all tools) */} + {selectedCategory === null && ( + + )} {getToolsForCategory(category, selectedSubCategory).map((tool: Tool, toolIndex: number) => ( diff --git a/frontend/src/components/MainDashboard/components/AnalyzePillarChips.tsx b/frontend/src/components/MainDashboard/components/AnalyzePillarChips.tsx new file mode 100644 index 00000000..9637f971 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/AnalyzePillarChips.tsx @@ -0,0 +1,246 @@ +import React from 'react'; +import { Box, Chip, useTheme } from '@mui/material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; +import { + Facebook, + LinkedIn, + Twitter, + Web, + Analytics, + Dashboard +} from '@mui/icons-material'; +import EnhancedTodayChip from './EnhancedTodayChip'; +import { TodayTask } from '../../../types/workflow'; + +interface AnalyzePillarChipsProps { + isHovered: boolean; + pillarColor: string; +} + +const AnalyzePillarChips: React.FC = ({ + isHovered, + pillarColor +}) => { + const theme = useTheme(); + const navigate = useNavigate(); + + // Today's tasks for Analyze pillar + const todayTasks: TodayTask[] = [ + { + id: "analyze-content-performance", + pillarId: "analyze", + title: "Review content performance", + description: "Analyze last week's content engagement metrics", + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/content-planning-dashboard', + icon: Analytics, + color: "#9C27B0", + enabled: true, + action: () => navigate('/content-planning-dashboard') + }, + { + id: "analyze-strategy-alignment", + pillarId: "analyze", + title: "Check strategy alignment", + description: "Review content strategy against performance data", + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/content-planning-dashboard', + icon: Dashboard, + color: "#673AB7", + enabled: true, + action: () => navigate('/content-planning-dashboard') + }, + { + id: "analyze-update-dashboard", + pillarId: "analyze", + title: "Update analytics dashboard", + description: "Refresh analytics data for all platforms", + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 30, + actionType: 'navigate' as const, + actionUrl: '/analytics-dashboard', + icon: Analytics, + color: "#3F51B5", + enabled: false, + action: () => {} + } + ]; + + const handlePlanDashboardClick = () => { + navigate('/content-planning-dashboard'); + }; + + return ( + + {/* Today Chip - Always visible */} + + + {/* Progressive disclosure chips */} + + {isHovered && ( + <> + {/* Plan Dashboard Chip */} + + } + label="Plan Dashboard" + onClick={handlePlanDashboardClick} + sx={{ + height: 28, + minWidth: 120, + background: 'linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%)', + color: 'white', + fontWeight: 600, + fontSize: '0.75rem', + border: '2px solid #9C27B0', + boxShadow: '0 4px 12px rgba(156, 39, 176, 0.3), 0 0 0 1px rgba(255,255,255,0.1)', + backdropFilter: 'blur(10px)', + cursor: 'pointer', + '&:hover': { + transform: 'translateY(-2px) scale(1.05)', + boxShadow: '0 6px 20px rgba(156, 39, 176, 0.4), 0 0 0 1px rgba(255,255,255,0.2)', + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + }} + /> + + + {/* Disabled Analytics Chips */} + + } + label="LinkedIn Analytics" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(0, 119, 181, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(0, 119, 181, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Facebook Analytics" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(24, 119, 242, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(24, 119, 242, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Twitter Analytics" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(29, 161, 242, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(29, 161, 242, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Website Analytics" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(255, 107, 53, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(255, 107, 53, 0.2)', + opacity: 0.6, + }} + /> + + + )} + + + {/* Ellipsis indicator when not hovered */} + {!isHovered && ( + + ⋯ + + )} + + ); +}; + +export default AnalyzePillarChips; diff --git a/frontend/src/components/MainDashboard/components/EngagePillarChips.tsx b/frontend/src/components/MainDashboard/components/EngagePillarChips.tsx new file mode 100644 index 00000000..484a01d8 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/EngagePillarChips.tsx @@ -0,0 +1,235 @@ +import React from 'react'; +import { Box, Chip, useTheme } from '@mui/material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; +import { + Facebook, + LinkedIn, + Twitter, + Forum, + Comment, + Chat, + Groups +} from '@mui/icons-material'; +import EnhancedTodayChip from './EnhancedTodayChip'; +import { TodayTask } from '../../../types/workflow'; + +interface EngagePillarChipsProps { + isHovered: boolean; + pillarColor: string; +} + +const EngagePillarChips: React.FC = ({ + isHovered, + pillarColor +}) => { + const theme = useTheme(); + const navigate = useNavigate(); + + // Today's tasks for Engage pillar + const todayTasks: TodayTask[] = [ + { + id: "engage-blog-comment", + pillarId: "engage", + title: "Reply to blog comment", + description: "Received comment on blog 'AI Persona for Content writing'", + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 10, + actionType: 'navigate' as const, + actionUrl: '/content-planning-dashboard', + icon: Comment, + color: "#E91E63", + enabled: true, + action: () => navigate('/content-planning-dashboard') + }, + { + id: "engage-twitter-mention", + pillarId: "engage", + title: "Respond to Twitter mention", + description: "Reply to Twitter comment from @username", + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 5, + actionType: 'navigate' as const, + actionUrl: '/content-planning-dashboard', + icon: Twitter, + color: "#1DA1F2", + enabled: true, + action: () => navigate('/content-planning-dashboard') + }, + { + id: "engage-linkedin-post", + pillarId: "engage", + title: "Engage with LinkedIn post", + description: "Respond to comments on latest LinkedIn post", + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/linkedin-engagement', + icon: LinkedIn, + color: "#0077B5", + enabled: false, + action: () => {} + } + ]; + + return ( + + {/* Today Chip - Always visible */} + + + {/* Progressive disclosure chips */} + + {isHovered && ( + <> + {/* Disabled Engagement Chips */} + + } + label="LinkedIn Comments" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(0, 119, 181, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(0, 119, 181, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Facebook Comments" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(24, 119, 242, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(24, 119, 242, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Community Engagement" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(233, 30, 99, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(233, 30, 99, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Live Chat Support" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(76, 175, 80, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(76, 175, 80, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Forum Discussions" + disabled + sx={{ + height: 28, + minWidth: 120, + background: 'rgba(255, 152, 0, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(255, 152, 0, 0.2)', + opacity: 0.6, + }} + /> + + + )} + + + {/* Ellipsis indicator when not hovered */} + {!isHovered && ( + + ⋯ + + )} + + ); +}; + +export default EngagePillarChips; diff --git a/frontend/src/components/MainDashboard/components/EnhancedTodayChip.tsx b/frontend/src/components/MainDashboard/components/EnhancedTodayChip.tsx new file mode 100644 index 00000000..b151117a --- /dev/null +++ b/frontend/src/components/MainDashboard/components/EnhancedTodayChip.tsx @@ -0,0 +1,233 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Typography, + Chip, + Tooltip +} from '@mui/material'; +import { motion } from 'framer-motion'; +import { + Today as TodayIcon +} from '@mui/icons-material'; +import { useWorkflowStore } from '../../../stores/workflowStore'; +import { TodayTask } from '../../../types/workflow'; +import EnhancedTodayModal from './EnhancedTodayModal'; + +interface EnhancedTodayChipProps { + pillarId: string; + pillarTitle: string; + pillarColor: string; + tasks: TodayTask[]; + delay?: number; +} + + +// Enhanced Today Chip Component +const EnhancedTodayChip: React.FC = ({ + pillarId, + pillarTitle, + pillarColor, + tasks, + delay = 0 +}) => { + const [modalOpen, setModalOpen] = useState(false); + const [shouldShake, setShouldShake] = useState(false); + const [userManuallyClosed, setUserManuallyClosed] = useState(false); + const { workflowProgress, navigationState, currentWorkflow } = useWorkflowStore(); + + // Prefer live workflow tasks (to reflect updated statuses), fallback to props + const liveTasks = currentWorkflow?.tasks && Array.isArray(currentWorkflow.tasks) && currentWorkflow.tasks.length > 0 + ? currentWorkflow.tasks + : tasks; + + // Get pillar-specific progress + const pillarTasks = liveTasks.filter(task => task.pillarId === pillarId); + const completedPillarTasks = pillarTasks.filter(task => task.status === 'completed' || task.status === 'skipped').length; + const pillarProgress = pillarTasks.length > 0 ? (completedPillarTasks / pillarTasks.length) * 100 : 0; + const isPillarComplete = pillarTasks.length > 0 && completedPillarTasks === pillarTasks.length; + + // Auto-shake animation (only when pillar is not complete) + useEffect(() => { + if (isPillarComplete) { + setShouldShake(false); // Stop any ongoing animation + return; // Don't animate if pillar is complete + } + + const interval = setInterval(() => { + setShouldShake(true); + setTimeout(() => setShouldShake(false), 600); + }, 8000 + delay * 1000); + + return () => clearInterval(interval); + }, [delay, isPillarComplete, liveTasks]); + + // Auto-open Plan pillar modal when workflow starts (only if user hasn't manually closed it AND tasks are incomplete) + useEffect(() => { + if (pillarId === 'plan' && + currentWorkflow?.workflowStatus === 'in_progress' && + !modalOpen && + !userManuallyClosed && + !isPillarComplete) { // Only auto-open if Plan pillar tasks are not complete + // Small delay to ensure smooth transition + const timer = setTimeout(() => { + setModalOpen(true); + }, 500); + return () => clearTimeout(timer); + } + }, [currentWorkflow?.workflowStatus, pillarId, modalOpen, userManuallyClosed, isPillarComplete]); + + const handleClick = () => { + setModalOpen(true); + setUserManuallyClosed(false); // Reset the flag when user manually opens + }; + + const handleCloseModal = () => { + setModalOpen(false); + if (pillarId === 'plan') { + setUserManuallyClosed(true); // Mark that user manually closed the Plan modal + } + }; + + return ( + <> + + + + + + + ⚡ + + + } + label="Today" + sx={{ + height: 32, + minWidth: 110, + background: `linear-gradient(135deg, ${pillarColor} 0%, ${pillarColor}CC 100%)`, + color: 'white', + fontWeight: 700, + fontSize: '0.75rem', + border: `2px solid ${pillarColor}`, + boxShadow: ` + 0 4px 12px ${pillarColor}40, + 0 0 0 1px rgba(255,255,255,0.1), + inset 0 1px 0 rgba(255,255,255,0.2) + `, + backdropFilter: 'blur(25px)', + position: 'relative', + zIndex: 1, // Lower z-index to not cover the large tick + '&:hover': { + boxShadow: ` + 0 6px 20px ${pillarColor}60, + 0 0 0 1px rgba(255,255,255,0.2), + inset 0 1px 0 rgba(255,255,255,0.3) + `, + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + '&::after': { + content: '""', + position: 'absolute', + top: -2, + left: -2, + right: -2, + bottom: -2, + background: `linear-gradient(45deg, ${pillarColor}, transparent, ${pillarColor})`, + borderRadius: 'inherit', + zIndex: -1, + '@keyframes attention-ring': { + '0%, 100%': { opacity: 0, transform: 'scale(1)' }, + '50%': { opacity: 0.3, transform: 'scale(1.1)' } + }, + animation: 'attention-ring 2s ease-in-out infinite' + } + }} + /> + + {/* Progress indicator */} + {pillarProgress > 0 && ( + + {pillarProgress === 100 ? '✓' : Math.round(pillarProgress)} + + )} + + + + + {/* Enhanced Modal */} + setUserManuallyClosed(true)} + /> + + + ); +}; + +export default EnhancedTodayChip; diff --git a/frontend/src/components/MainDashboard/components/EnhancedTodayModal.tsx b/frontend/src/components/MainDashboard/components/EnhancedTodayModal.tsx new file mode 100644 index 00000000..709ac945 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/EnhancedTodayModal.tsx @@ -0,0 +1,498 @@ +import React, { useState } from 'react'; +import { + Box, + Typography, + Chip, + Tooltip, + Modal, + Paper, + Button, + IconButton, + Avatar, + Stack, + LinearProgress, + CircularProgress, + Card, + CardContent +} from '@mui/material'; +import { motion } from 'framer-motion'; +import { + Today as TodayIcon, + Close as CloseIcon, + AutoAwesome as AlwrityIcon, + CheckCircle as CheckIcon, + PlayArrow as PlayIcon, + SkipNext as SkipIcon, + NavigateNext +} from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; +import { useWorkflowStore } from '../../../stores/workflowStore'; +import { TodayTask } from '../../../types/workflow'; + +interface EnhancedTodayModalProps { + open: boolean; + onClose: () => void; + pillarId: string; + pillarTitle: string; + pillarColor: string; + tasks: TodayTask[]; + // When navigating away (Next), prevent the previous pillar modal from auto-reopening + onPreventAutoReopen?: () => void; +} + +// Enhanced Today Modal with Workflow Integration +const EnhancedTodayModal: React.FC = ({ + open, + onClose, + pillarId, + pillarTitle, + pillarColor, + tasks, + onPreventAutoReopen +}) => { + const navigate = useNavigate(); + const { + currentWorkflow, + workflowProgress, + navigationState, + completeTask, + skipTask, + moveToNextTask, + isLoading, + isWorkflowComplete + } = useWorkflowStore(); + + const [selectedTask, setSelectedTask] = useState(null); + + // Prefer live workflow tasks (to reflect updated statuses), fallback to props + const liveTasks = currentWorkflow?.tasks && Array.isArray(currentWorkflow.tasks) && currentWorkflow.tasks.length > 0 + ? currentWorkflow.tasks + : tasks; + + // Filter tasks for this pillar + const pillarTasks = liveTasks.filter(task => task.pillarId === pillarId); + const currentTask = navigationState?.currentTask; + const isComplete = isWorkflowComplete(); + + const handleTaskAction = async (task: TodayTask) => { + if (!task.enabled) return; + + try { + // Execute the task action + if (task.action) { + task.action(); + } else if (task.actionUrl) { + navigate(task.actionUrl); + } + + // Mark task as completed in workflow + if (currentWorkflow) { + await completeTask(task.id); + } + } catch (error) { + console.error('Error executing task:', error); + } + }; + + const handleSkipTask = async (task: TodayTask) => { + if (currentWorkflow) { + await skipTask(task.id); + } + }; + + const handleStartWorkflow = async () => { + if (currentWorkflow) { + await moveToNextTask(); + } + }; + + const handleNextPillar = async () => { + // Close current modal + onClose(); + + // Prevent auto-reopen of current modal during navigation + if (onPreventAutoReopen) { + onPreventAutoReopen(); + } + + // Navigate to next pillar + if (nextPillarId) { + setTimeout(() => { + // Trigger next pillar modal opening + const nextChip = document.querySelector(`[data-pillar-id="${nextPillarId}"]`); + if (nextChip) { + (nextChip as HTMLElement).click(); + } + }, 300); + } + }; + + const handleWorkflowComplete = async () => { + // Mark all remaining tasks in this pillar as completed + const incompleteTasks = pillarTasks.filter(task => + task.status !== 'completed' && task.status !== 'skipped' + ); + + for (const task of incompleteTasks) { + try { + await completeTask(task.id); + } catch (error) { + console.error(`Failed to complete task ${task.id}:`, error); + } + } + + // Close the modal + onClose(); + }; + + // Check if all tasks in this pillar are completed or skipped + const areAllTasksCompleted = pillarTasks.every(task => + task.status === 'completed' || task.status === 'skipped' + ); + + // Check if this is the Plan pillar + const isPlanPillar = pillarId === 'plan'; + + // Define pillar order for navigation + const pillarOrder = ['plan', 'generate', 'publish', 'analyze', 'engage', 'remarket']; + const currentPillarIndex = pillarOrder.indexOf(pillarId); + const isLastPillar = currentPillarIndex === pillarOrder.length - 1; + const nextPillarId = !isLastPillar ? pillarOrder[currentPillarIndex + 1] : null; + + const getTaskStatus = (task: TodayTask) => { + if (task.status === 'completed') return 'completed'; + if (task.status === 'in_progress') return 'active'; + if (task.status === 'skipped') return 'skipped'; + return 'pending'; + }; + + const getTaskStatusColor = (status: string) => { + switch (status) { + case 'completed': return '#4CAF50'; + case 'active': return '#2196F3'; + case 'skipped': return '#FF9800'; + default: return '#9E9E9E'; + } + }; + + return ( + + + {/* Header */} + + + + + + + + + Today's {pillarTitle} Tasks + + + Complete your daily marketing workflow + + + + + + + + + + {/* Workflow Progress - Circular in Header */} + {workflowProgress && ( + + + + Overall Progress + + + + + + {`${Math.round(workflowProgress.completionPercentage)}%`} + + + + + + {workflowProgress.completedTasks} of {workflowProgress.totalTasks} tasks + + + )} + + {/* Tasks List */} + + + {pillarTitle} Tasks + + + + {pillarTasks.map((task, index) => { + const status = getTaskStatus(task); + const statusColor = getTaskStatusColor(status); + const isCurrentTask = currentTask?.id === task.id; + const IconComponent = (typeof task.icon === 'function' ? task.icon : undefined) as any; + + return ( + + + + + {IconComponent && ( + + + + )} + + + + {task.title} + + + {task.description} + + + + + + + + {task.estimatedTime} min + + + + + {/* Task Actions */} + + {status === 'pending' && task.enabled && ( + + )} + + {status === 'active' && ( + + )} + + {status === 'completed' && ( + + )} + + {status === 'pending' && ( + + )} + + + + + ); + })} + + + + {/* Footer Actions */} + + + + {isComplete ? '🎉 All tasks completed!' : `${pillarTasks.length} tasks in this pillar`} + + + + {/* Next button for all pillars except the last one */} + {!isLastPillar && ( + <> + + + + + + + + )} + + {/* Last pillar (Remarket) - Workflow Complete button acts as close */} + {isLastPillar && ( + + )} + + + + + + ); +}; + +export default EnhancedTodayModal; diff --git a/frontend/src/components/MainDashboard/components/GeneratePillarChips.tsx b/frontend/src/components/MainDashboard/components/GeneratePillarChips.tsx new file mode 100644 index 00000000..db20af20 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/GeneratePillarChips.tsx @@ -0,0 +1,609 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Typography, + Chip, + Tooltip, + Modal, + Paper, + Button, + IconButton, + Divider, + Avatar, + Stack +} from '@mui/material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Today as TodayIcon, + TextFields as TextIcon, + Image as ImageIcon, + AudioFile as AudioIcon, + VideoFile as VideoIcon, + Close as CloseIcon, + Facebook as FacebookIcon, + LinkedIn as LinkedInIcon, + Language as WebsiteIcon, + AutoAwesome as AlwrityIcon +} from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; +import EnhancedTodayChip from './EnhancedTodayChip'; +import { TodayTask } from '../../../types/workflow'; + +// Today Modal Component +const TodayModal: React.FC<{ + open: boolean; + onClose: () => void; +}> = ({ open, onClose }) => { + const navigate = useNavigate(); + + const tasks = [ + { + id: 'facebook', + title: "Post 'ALwrity AI Content Generation' on Facebook", + platform: 'Facebook', + icon: FacebookIcon, + color: '#1877F2', + enabled: true, + action: () => navigate('/facebook-writer') + }, + { + id: 'website', + title: 'Write a Blog on "AI Image generation prompts" for wix website', + platform: 'Website', + icon: WebsiteIcon, + color: '#FF6B35', + enabled: false, + action: () => {} + }, + { + id: 'linkedin', + title: "Write & Post on LinkedIn on 'AI Agents frameworks latest news'", + platform: 'LinkedIn', + icon: LinkedInIcon, + color: '#0077B5', + enabled: true, + action: () => navigate('/linkedin-writer') + } + ]; + + return ( + + + + {/* Header */} + + + + + + + + + + + + Today's Tasks + + + AI-powered content generation for today + + + + + + {/* Content */} + + + 🚀 Ready to Generate Content + + + + {tasks.map((task, index) => { + const IconComponent = task.icon; + return ( + + + + + + + + + + {task.title} + + + + + + + + + ); + })} + + + + + + + 💡 Tip: Use ALwrity's AI to generate engaging content tailored to each platform + + + + + + + ); +}; + +// Enhanced Chip Component for Generate Pillar +const GenerateChip: React.FC<{ + chip: any; + delay?: number; + onTodayClick?: () => void; +}> = ({ chip, delay = 0, onTodayClick }) => { + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + if (chip.bubbles && chip.bubbles.length > 0) { + const interval = setInterval(() => { + setCurrentIndex((prev) => (prev + 1) % chip.bubbles.length); + }, 2000 + delay * 300); + + return () => clearInterval(interval); + } + }, [chip.bubbles?.length, delay]); + + const IconComponent = chip.icon; + + const handleClick = () => { + if (chip.label === 'Today' && onTodayClick) { + onTodayClick(); + } + }; + + return ( + 0 ? ( + + + {chip.label} + + + + + {chip.bubbles[currentIndex]} + + + + + ) : chip.label + } + arrow + placement="top" + > + + {/* Glow Effect */} + + + {/* Shadow Effect */} + + + {/* Main Chip */} + } + label={ + + + {chip.label} + + {chip.value && ( + + {chip.value} + + )} + + } + size="small" + sx={{ + background: `linear-gradient(135deg, + rgba(255,255,255,0.25) 0%, + rgba(255,255,255,0.1) 50%, + rgba(255,255,255,0.05) 100%)`, + backdropFilter: 'blur(20px)', + border: '1px solid rgba(255,255,255,0.3)', + color: 'white', + fontSize: '0.7rem', + fontWeight: 600, + height: 28, + minWidth: 100, + position: 'relative', + overflow: 'hidden', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: '-100%', + width: '100%', + height: '100%', + background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)', + transition: 'left 0.6s ease', + zIndex: 1 + }, + '&:hover::before': { + left: '100%' + }, + '& .MuiChip-label': { + px: 1, + zIndex: 2, + position: 'relative' + }, + '& .MuiChip-icon': { + zIndex: 2, + position: 'relative' + }, + '&:hover': { + background: `linear-gradient(135deg, + rgba(255,255,255,0.35) 0%, + rgba(255,255,255,0.2) 50%, + rgba(255,255,255,0.1) 100%)`, + border: '1px solid rgba(255,255,255,0.5)', + boxShadow: `0 8px 32px ${chip.color}40, + 0 4px 16px rgba(0,0,0,0.1), + inset 0 1px 0 rgba(255,255,255,0.3)` + } + }} + /> + + + ); +}; + +// Generate Pillar Chips Component +const GeneratePillarChips: React.FC<{ + index: number; + isHovered?: boolean; +}> = ({ index, isHovered = false }) => { + // Generate pillar Today tasks + const generateTodayTasks: TodayTask[] = [ + { + id: 'facebook-post', + pillarId: 'generate', + title: "Post 'ALwrity AI Content Generation' on Facebook", + description: 'Create and publish engaging Facebook content', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/facebook-writer', + icon: FacebookIcon, + color: '#1877F2', + enabled: true, + action: () => console.log('Navigate to Facebook writer') + }, + { + id: 'blog-post', + pillarId: 'generate', + title: 'Write Blog on "AI Image Generation Prompts"', + description: 'Create comprehensive blog post for website', + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 30, + actionType: 'navigate' as const, + actionUrl: '/blog-writer', + icon: WebsiteIcon, + color: '#FF6B35', + enabled: false, + action: () => {} + }, + { + id: 'linkedin-post', + pillarId: 'generate', + title: "Write & Post on LinkedIn 'AI Agents Frameworks'", + description: 'Create professional LinkedIn content', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/linkedin-writer', + icon: LinkedInIcon, + color: '#0077B5', + enabled: true, + action: () => console.log('Navigate to LinkedIn writer') + } + ]; + + // Generate pillar chips data + const generateChips = { + text: { + label: 'Text', + icon: TextIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['Blog posts', 'Social media', 'Email content'] + }, + image: { + label: 'Image', + icon: ImageIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['Visual content', 'Infographics', 'Social images'] + }, + audio: { + label: 'Audio', + icon: AudioIcon, + color: '#9C27B0', + gradient: 'linear-gradient(135deg, #9C27B0 0%, #6A1B9A 100%)', + bubbles: ['Podcast scripts', 'Voice content', 'Audio ads'] + }, + video: { + label: 'Video', + icon: VideoIcon, + color: '#E91E63', + gradient: 'linear-gradient(135deg, #E91E63 0%, #C2185B 100%)', + bubbles: ['Video scripts', 'YouTube content', 'Social videos'] + } + }; + + return ( + + {/* Today Chip - Always Visible */} + + + {/* More Options Indicator */} + {!isHovered && ( + + + ⋯ + + + )} + + {/* Content Type Chips - Progressive Disclosure */} + + {isHovered && ( + + + + + + + + + + + + + + + + + )} + + + ); +}; + +export default GeneratePillarChips; diff --git a/frontend/src/components/MainDashboard/components/OnboardingModal.tsx b/frontend/src/components/MainDashboard/components/OnboardingModal.tsx new file mode 100644 index 00000000..a50e8531 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/OnboardingModal.tsx @@ -0,0 +1,324 @@ +import React from 'react'; +import { + Box, + Typography, + Modal, + Paper, + Button, + IconButton, + Divider, + LinearProgress, + Avatar, + Stack, + Chip +} from '@mui/material'; +import { motion } from 'framer-motion'; +import { + Close as CloseIcon, + Settings as SettingsIcon, + PersonAdd as OnboardingIcon, + CheckCircle as CheckIcon, + TrendingUp as TrendingUpIcon, + Psychology as PsychologyIcon +} from '@mui/icons-material'; + +// Onboarding Modal Component +const OnboardingModal: React.FC<{ + open: boolean; + onClose: () => void; +}> = ({ open, onClose }) => { + // Mock onboarding data - in real app, this would come from database + const onboardingData = { + userProfile: { + name: 'John Doe', + company: 'TechCorp Inc.', + role: 'Marketing Manager', + completion: 85 + }, + preferences: { + contentTypes: ['Blog Posts', 'Social Media', 'Email Campaigns'], + platforms: ['LinkedIn', 'Facebook', 'Twitter'], + tone: 'Professional', + frequency: 'Daily' + }, + goals: { + primary: 'Increase brand awareness', + secondary: 'Generate leads', + metrics: ['Engagement Rate', 'Click-through Rate', 'Conversion Rate'] + }, + aiAnalysis: { + score: 8.5, + insights: [ + 'Strong foundation with clear goals and preferences', + 'Content strategy well-aligned with target audience', + 'Consider expanding to Instagram for better reach', + 'Email campaigns could benefit from A/B testing' + ], + recommendations: [ + 'Set up automated content scheduling', + 'Implement advanced analytics tracking', + 'Create content templates for consistency', + 'Establish brand voice guidelines' + ] + } + }; + + return ( + + + + {/* Header */} + + + + + + + + + + + + Onboarding Status + + + Complete your setup to unlock full potential + + + + + + + + Overall Progress + + + {onboardingData.userProfile.completion}% + + + + + + + {/* Content */} + + {/* User Profile Section */} + + + 👤 User Profile + + + + Name + {onboardingData.userProfile.name} + + + Company + {onboardingData.userProfile.company} + + + Role + {onboardingData.userProfile.role} + + + Completion + {onboardingData.userProfile.completion}% + + + + + {/* Preferences Section */} + + + ⚙️ Preferences + + + + Content Types + + {onboardingData.preferences.contentTypes.map((type, idx) => ( + + ))} + + + + Platforms + + {onboardingData.preferences.platforms.map((platform, idx) => ( + + ))} + + + + Tone + {onboardingData.preferences.tone} + + + Frequency + {onboardingData.preferences.frequency} + + + + + {/* Goals Section */} + + + 🎯 Goals + + + + Primary Goal + {onboardingData.goals.primary} + + + Secondary Goal + {onboardingData.goals.secondary} + + + Key Metrics + + {onboardingData.goals.metrics.map((metric, idx) => ( + + ))} + + + + + + {/* AI Analysis Section */} + + + 🤖 AI Analysis + + + + + + Analysis Score: {onboardingData.aiAnalysis.score}/10 + + + + + + Key Insights: + + {onboardingData.aiAnalysis.insights.map((insight, idx) => ( + + + {insight} + + ))} + + + + + Recommendations: + + {onboardingData.aiAnalysis.recommendations.map((rec, idx) => ( + + + {rec} + + ))} + + + + + {/* Settings Button */} + + + + + Configure your preferences and goals in the Settings page + + + + + + + ); +}; + +export default OnboardingModal; diff --git a/frontend/src/components/MainDashboard/components/PillarData.tsx b/frontend/src/components/MainDashboard/components/PillarData.tsx new file mode 100644 index 00000000..a746251e --- /dev/null +++ b/frontend/src/components/MainDashboard/components/PillarData.tsx @@ -0,0 +1,576 @@ +import React from 'react'; +import { + // Plan pillar icons + Assignment as PlanIcon, + PersonAdd as OnboardingIcon, + Business as StrategyIcon, + CalendarMonth as CalendarIcon, + RateReview as ReviewIcon, + + // Generate pillar icons + AutoAwesome as GenerateIcon, + ThumbUp as GoodIcon, + ThumbDown as BadIcon, + Warning as UglyIcon, + + // Publish pillar icons + Publish as PublishIcon, + + // Analyze pillar icons + Analytics as AnalyzeIcon, + + // Engage pillar icons + Campaign as EngageIcon, + + // Remarket pillar icons + Psychology as RemarketIcon, + + // Task icons + Facebook as FacebookIcon, + LinkedIn as LinkedInIcon, + Language as WebsiteIcon, + ChatBubbleOutline as ChatIcon, + Assessment as AssessmentIcon, + Share as ShareIcon, + ThumbUp as ThumbUpIcon, + Refresh as RefreshIcon, + Article as ArticleIcon +} from '@mui/icons-material'; +import { TodayTask } from '../../../types/workflow'; + +// Define the chip interface +export interface PillarChip { + label: string; + icon: React.ComponentType; + color: string; + gradient: string; + bubbles: string[]; + value?: number | null; +} + +// Define the pillar data interface +export interface PillarData { + id: string; + title: string; + icon: React.ComponentType; + color: string; + gradient: string; + chips: { + [key: string]: PillarChip; + }; + todayTasks: TodayTask[]; +} + +// Enhanced pillar data with Today tasks +export const pillarData: PillarData[] = [ + { + id: 'plan', + title: 'Plan', + icon: PlanIcon, + color: '#2E7D32', + gradient: 'linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%)', + chips: { + onboarding: { + label: 'On-Boarding', + icon: OnboardingIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['User Profile Setup', 'Preferences Configured', 'Goals Defined'], + value: 2 + }, + strategy: { + label: 'Strategy', + icon: StrategyIcon, + color: '#2196F3', + gradient: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)', + bubbles: ['Content Strategy Defined', 'Target Audience Identified', 'Brand Voice Established'], + value: 7 + }, + calendar: { + label: 'Calendar', + icon: CalendarIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['Publishing Schedule Set', 'Content Calendar Created', 'Campaign Timeline Planned'], + value: 11 + }, + review: { + label: 'Review & Optimize', + icon: ReviewIcon, + color: '#9C27B0', + gradient: 'linear-gradient(135deg, #9C27B0 0%, #6A1B9A 100%)', + bubbles: ['Content Calendar Generated', 'SEO Strategy Optimized', 'Topic Clusters Identified'], + value: null + } + }, + todayTasks: [ + { + id: 'content-calendar', + pillarId: 'plan', + title: 'Create Weekly Content Calendar', + description: 'Plan and schedule content for the upcoming week', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/content-planning-dashboard', + icon: CalendarIcon, + color: '#2E7D32', + enabled: true, + action: () => console.log('Navigate to content calendar') + }, + { + id: 'seo-strategy', + pillarId: 'plan', + title: 'Update SEO Strategy', + description: 'Review and optimize SEO keywords and content strategy', + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/seo-strategy', + icon: AssessmentIcon, + color: '#2196F3', + enabled: true, + action: () => console.log('Navigate to SEO strategy') + }, + { + id: 'competitor-analysis', + pillarId: 'plan', + title: 'Competitor Analysis', + description: 'Analyze competitor content and identify opportunities', + status: 'pending' as const, + priority: 'low' as const, + estimatedTime: 30, + actionType: 'navigate' as const, + actionUrl: '/competitor-analysis', + icon: AnalyzeIcon, + color: '#FF9800', + enabled: false, + action: () => {} + } + ] + }, + { + id: 'generate', + title: 'Generate', + icon: GenerateIcon, + color: '#1565C0', + gradient: 'linear-gradient(135deg, #1565C0 0%, #0D47A1 100%)', + chips: { + good: { + label: 'Quality Content', + icon: GoodIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['SEO Optimized', 'Brand Voice Consistent', 'Engaging Headlines'] + }, + bad: { + label: 'Content Issues', + icon: BadIcon, + color: '#F44336', + gradient: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', + bubbles: ['Poor Grammar', 'Weak CTAs', 'Generic Content'] + }, + ugly: { + label: 'Critical Problems', + icon: UglyIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['No Brand Voice', 'Plagiarized Content', 'No SEO Optimization'] + }, + review: { + label: 'Review & Optimize', + icon: ReviewIcon, + color: '#2196F3', + gradient: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)', + bubbles: ['Blog Post Generated', 'Social Media Content Created', 'Email Campaign Written'] + } + }, + todayTasks: [ + { + id: 'facebook-post', + pillarId: 'generate', + title: "Post 'ALwrity AI Content Generation' on Facebook", + description: 'Create and publish engaging Facebook content', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/facebook-writer', + icon: FacebookIcon, + color: '#1877F2', + enabled: true, + action: () => console.log('Navigate to Facebook writer') + }, + { + id: 'blog-post', + pillarId: 'generate', + title: 'Write Blog on "AI Image Generation Prompts"', + description: 'Create comprehensive blog post for website', + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 45, + actionType: 'navigate' as const, + actionUrl: '/blog-writer', + icon: ArticleIcon, + color: '#FF6B35', + enabled: false, + action: () => {} + }, + { + id: 'linkedin-post', + pillarId: 'generate', + title: "Write & Post on LinkedIn 'AI Agents Frameworks'", + description: 'Create professional LinkedIn content', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/linkedin-writer', + icon: LinkedInIcon, + color: '#0077B5', + enabled: true, + action: () => console.log('Navigate to LinkedIn writer') + } + ] + }, + { + id: 'publish', + title: 'Publish', + icon: PublishIcon, + color: '#E65100', + gradient: 'linear-gradient(135deg, #E65100 0%, #BF360C 100%)', + chips: { + good: { + label: 'Smooth Publishing', + icon: GoodIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['Multi-Platform Sync', 'Optimal Timing', 'Auto-Scheduling'] + }, + bad: { + label: 'Publishing Issues', + icon: BadIcon, + color: '#F44336', + gradient: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', + bubbles: ['Manual Publishing', 'Poor Timing', 'Platform Errors'] + }, + ugly: { + label: 'Critical Failures', + icon: UglyIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['No Publishing Strategy', 'Content Not Published', 'Platform Disconnects'] + }, + review: { + label: 'Review & Optimize', + icon: ReviewIcon, + color: '#2196F3', + gradient: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)', + bubbles: ['Content Published to LinkedIn', 'Facebook Post Scheduled', 'Twitter Thread Live'] + } + }, + todayTasks: [ + { + id: 'schedule-posts', + pillarId: 'publish', + title: 'Schedule Today\'s Content', + description: 'Schedule all content for optimal engagement times', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 10, + actionType: 'navigate' as const, + actionUrl: '/publishing-scheduler', + icon: PublishIcon, + color: '#E65100', + enabled: true, + action: () => console.log('Navigate to publishing scheduler') + }, + { + id: 'cross-platform', + pillarId: 'publish', + title: 'Cross-Platform Publishing', + description: 'Publish content across all connected platforms', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/cross-platform-publisher', + icon: ShareIcon, + color: '#4CAF50', + enabled: true, + action: () => console.log('Navigate to cross-platform publisher') + }, + { + id: 'publish-analytics', + pillarId: 'publish', + title: 'Publishing Analytics Review', + description: 'Review publishing performance and optimize timing', + status: 'pending' as const, + priority: 'low' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/publishing-analytics', + icon: AnalyzeIcon, + color: '#2196F3', + enabled: false, + action: () => {} + } + ] + }, + { + id: 'analyze', + title: 'Analyze', + icon: AnalyzeIcon, + color: '#6A1B9A', + gradient: 'linear-gradient(135deg, #6A1B9A 0%, #4A148C 100%)', + chips: { + good: { + label: 'Great Analytics', + icon: GoodIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['Real-time Tracking', 'Detailed Insights', 'ROI Measurement'] + }, + bad: { + label: 'Analytics Gaps', + icon: BadIcon, + color: '#F44336', + gradient: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', + bubbles: ['Limited Tracking', 'No ROI Data', 'Poor Reporting'] + }, + ugly: { + label: 'No Analytics', + icon: UglyIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['No Tracking Setup', 'No Performance Data', 'Blind Publishing'] + }, + review: { + label: 'Review & Optimize', + icon: ReviewIcon, + color: '#2196F3', + gradient: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)', + bubbles: ['Engagement Rate: +25%', 'Click-Through Rate Improved', 'Social Shares Increased'] + } + }, + todayTasks: [ + { + id: 'performance-report', + pillarId: 'analyze', + title: 'Generate Performance Report', + description: 'Create comprehensive analytics report for this week', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 25, + actionType: 'navigate' as const, + actionUrl: '/analytics-dashboard', + icon: AnalyzeIcon, + color: '#6A1B9A', + enabled: true, + action: () => console.log('Navigate to analytics dashboard') + }, + { + id: 'engagement-analysis', + pillarId: 'analyze', + title: 'Engagement Analysis', + description: 'Analyze engagement metrics and identify trends', + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/engagement-analytics', + icon: ThumbUpIcon, + color: '#4CAF50', + enabled: true, + action: () => console.log('Navigate to engagement analytics') + }, + { + id: 'roi-calculator', + pillarId: 'analyze', + title: 'ROI Calculator Update', + description: 'Update ROI calculations and performance metrics', + status: 'pending' as const, + priority: 'low' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/roi-calculator', + icon: AssessmentIcon, + color: '#FF9800', + enabled: false, + action: () => {} + } + ] + }, + { + id: 'engage', + title: 'Engage', + icon: EngageIcon, + color: '#C2185B', + gradient: 'linear-gradient(135deg, #C2185B 0%, #880E4F 100%)', + chips: { + good: { + label: 'High Engagement', + icon: GoodIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['Active Community', 'Quick Responses', 'Viral Content'] + }, + bad: { + label: 'Low Engagement', + icon: BadIcon, + color: '#F44336', + gradient: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', + bubbles: ['Slow Responses', 'Few Interactions', 'Poor Community'] + }, + ugly: { + label: 'No Engagement', + icon: UglyIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['No Community', 'No Responses', 'Ignored Content'] + }, + review: { + label: 'Review & Optimize', + icon: ReviewIcon, + color: '#2196F3', + gradient: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)', + bubbles: ['Comments Responded Automatically', 'Community Engagement Boosted', 'Customer Queries Resolved'] + } + }, + todayTasks: [ + { + id: 'respond-comments', + pillarId: 'engage', + title: 'Respond to Comments', + description: 'Engage with audience comments across all platforms', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 30, + actionType: 'navigate' as const, + actionUrl: '/comment-management', + icon: ChatIcon, + color: '#C2185B', + enabled: true, + action: () => console.log('Navigate to comment management') + }, + { + id: 'community-building', + pillarId: 'engage', + title: 'Community Building', + description: 'Foster community engagement and build relationships', + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 25, + actionType: 'navigate' as const, + actionUrl: '/community-tools', + icon: ThumbUpIcon, + color: '#4CAF50', + enabled: true, + action: () => console.log('Navigate to community tools') + }, + { + id: 'engagement-strategy', + pillarId: 'engage', + title: 'Engagement Strategy Review', + description: 'Review and optimize engagement strategies', + status: 'pending' as const, + priority: 'low' as const, + estimatedTime: 20, + actionType: 'navigate' as const, + actionUrl: '/engagement-strategy', + icon: AssessmentIcon, + color: '#FF9800', + enabled: false, + action: () => {} + } + ] + }, + { + id: 'remarket', + title: 'Remarket', + icon: RemarketIcon, + color: '#00695C', + gradient: 'linear-gradient(135deg, #00695C 0%, #004D40 100%)', + chips: { + good: { + label: 'Smart Remarketing', + icon: GoodIcon, + color: '#4CAF50', + gradient: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', + bubbles: ['Targeted Campaigns', 'High Conversion', 'ROI Optimized'] + }, + bad: { + label: 'Poor Remarketing', + icon: BadIcon, + color: '#F44336', + gradient: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', + bubbles: ['Low Conversion', 'Poor Targeting', 'Wasted Budget'] + }, + ugly: { + label: 'No Remarketing', + icon: UglyIcon, + color: '#FF9800', + gradient: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', + bubbles: ['No Retargeting', 'Lost Opportunities', 'No Lead Nurturing'] + }, + review: { + label: 'Review & Optimize', + icon: ReviewIcon, + color: '#2196F3', + gradient: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)', + bubbles: ['Remarketing Campaign Launched', 'Content Amplified Successfully', 'Lead Nurturing Sequence Active'] + } + }, + todayTasks: [ + { + id: 'retargeting-campaign', + pillarId: 'remarket', + title: 'Launch Retargeting Campaign', + description: 'Create and launch targeted remarketing campaigns', + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 35, + actionType: 'navigate' as const, + actionUrl: '/remarketing-dashboard', + icon: RemarketIcon, + color: '#00695C', + enabled: true, + action: () => console.log('Navigate to remarketing dashboard') + }, + { + id: 'lead-nurturing', + pillarId: 'remarket', + title: 'Lead Nurturing Sequence', + description: 'Set up automated lead nurturing workflows', + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 30, + actionType: 'navigate' as const, + actionUrl: '/lead-nurturing', + icon: RefreshIcon, + color: '#4CAF50', + enabled: true, + action: () => console.log('Navigate to lead nurturing') + }, + { + id: 'conversion-optimization', + pillarId: 'remarket', + title: 'Conversion Optimization', + description: 'Optimize remarketing campaigns for better conversion', + status: 'pending' as const, + priority: 'low' as const, + estimatedTime: 25, + actionType: 'navigate' as const, + actionUrl: '/conversion-optimization', + icon: AssessmentIcon, + color: '#FF9800', + enabled: false, + action: () => {} + } + ] + } +]; + +export default pillarData; diff --git a/frontend/src/components/MainDashboard/components/PublishPillarChips.tsx b/frontend/src/components/MainDashboard/components/PublishPillarChips.tsx new file mode 100644 index 00000000..6392b7d4 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/PublishPillarChips.tsx @@ -0,0 +1,338 @@ +import React from 'react'; +import { Box, Chip, useTheme } from '@mui/material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; +import { + Facebook, + LinkedIn, + Twitter, + Instagram, + YouTube, + Article, + CheckCircle +} from '@mui/icons-material'; +import EnhancedTodayChip from './EnhancedTodayChip'; +import { TodayTask } from '../../../types/workflow'; + +interface PublishPillarChipsProps { + isHovered: boolean; + pillarColor: string; +} + +const PublishPillarChips: React.FC = ({ + isHovered, + pillarColor +}) => { + const theme = useTheme(); + const navigate = useNavigate(); + + // Today's tasks for Publish pillar + const todayTasks: TodayTask[] = [ + { + id: "publish-facebook-post", + pillarId: "publish", + title: "Publish reviewed Facebook post", + description: "Post 'ALwrity AI Content Generation' on Facebook", + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 10, + actionType: 'navigate' as const, + actionUrl: '/facebook-writer', + icon: Facebook, + color: "#1877F2", + enabled: true, + action: () => navigate('/facebook-writer') + }, + { + id: "publish-linkedin-article", + pillarId: "publish", + title: "Schedule LinkedIn article", + description: "Publish 'AI Agents frameworks latest news' on LinkedIn", + status: 'pending' as const, + priority: 'high' as const, + estimatedTime: 15, + actionType: 'navigate' as const, + actionUrl: '/linkedin-writer', + icon: LinkedIn, + color: "#0077B5", + enabled: true, + action: () => navigate('/linkedin-writer') + }, + { + id: "publish-review-content", + pillarId: "publish", + title: "Review pending content", + description: "Review 3 pending blog posts for website", + status: 'pending' as const, + priority: 'medium' as const, + estimatedTime: 25, + actionType: 'navigate' as const, + actionUrl: '/content-review', + icon: Article, + color: "#FF6B35", + enabled: false, + action: () => {} + } + ]; + + const handleChipClick = (platform: string) => { + switch (platform) { + case 'facebook': + navigate('/facebook-writer'); + break; + case 'linkedin': + navigate('/linkedin-writer'); + break; + default: + break; + } + }; + + return ( + + {/* Today Chip - Always visible */} + + + {/* Progressive disclosure chips */} + + {isHovered && ( + <> + {/* Reviewed Chip */} + + } + label="Reviewed" + sx={{ + height: 28, + minWidth: 100, + background: 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)', + color: 'white', + fontWeight: 600, + fontSize: '0.75rem', + border: '2px solid #4CAF50', + boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3), 0 0 0 1px rgba(255,255,255,0.1)', + backdropFilter: 'blur(10px)', + '&:hover': { + transform: 'translateY(-2px) scale(1.05)', + boxShadow: '0 6px 20px rgba(76, 175, 80, 0.4), 0 0 0 1px rgba(255,255,255,0.2)', + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + }} + /> + + 3 + + + + {/* Facebook Chip */} + + } + label="Facebook" + onClick={() => handleChipClick('facebook')} + sx={{ + height: 28, + minWidth: 100, + background: 'linear-gradient(135deg, #1877F2 0%, #166FE5 100%)', + color: 'white', + fontWeight: 600, + fontSize: '0.75rem', + border: '2px solid #1877F2', + boxShadow: '0 4px 12px rgba(24, 119, 242, 0.3), 0 0 0 1px rgba(255,255,255,0.1)', + backdropFilter: 'blur(10px)', + cursor: 'pointer', + '&:hover': { + transform: 'translateY(-2px) scale(1.05)', + boxShadow: '0 6px 20px rgba(24, 119, 242, 0.4), 0 0 0 1px rgba(255,255,255,0.2)', + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + }} + /> + + + {/* LinkedIn Chip */} + + } + label="LinkedIn" + onClick={() => handleChipClick('linkedin')} + sx={{ + height: 28, + minWidth: 100, + background: 'linear-gradient(135deg, #0077B5 0%, #005885 100%)', + color: 'white', + fontWeight: 600, + fontSize: '0.75rem', + border: '2px solid #0077B5', + boxShadow: '0 4px 12px rgba(0, 119, 181, 0.3), 0 0 0 1px rgba(255,255,255,0.1)', + backdropFilter: 'blur(10px)', + cursor: 'pointer', + '&:hover': { + transform: 'translateY(-2px) scale(1.05)', + boxShadow: '0 6px 20px rgba(0, 119, 181, 0.4), 0 0 0 1px rgba(255,255,255,0.2)', + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + }} + /> + + + {/* Disabled Social Media Chips */} + + } + label="Twitter" + disabled + sx={{ + height: 28, + minWidth: 100, + background: 'rgba(29, 161, 242, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(29, 161, 242, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Instagram" + disabled + sx={{ + height: 28, + minWidth: 100, + background: 'rgba(225, 48, 108, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(225, 48, 108, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="YouTube" + disabled + sx={{ + height: 28, + minWidth: 100, + background: 'rgba(255, 0, 0, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(255, 0, 0, 0.2)', + opacity: 0.6, + }} + /> + + + + } + label="Wix/WordPress" + disabled + sx={{ + height: 28, + minWidth: 100, + background: 'rgba(255, 107, 53, 0.1)', + color: 'rgba(255, 255, 255, 0.4)', + fontWeight: 600, + fontSize: '0.75rem', + border: '1px solid rgba(255, 107, 53, 0.2)', + opacity: 0.6, + }} + /> + + + )} + + + {/* Ellipsis indicator when not hovered */} + {!isHovered && ( + + ⋯ + + )} + + ); +}; + +export default PublishPillarChips; diff --git a/frontend/src/components/MainDashboard/components/TaskNavigationControls.tsx b/frontend/src/components/MainDashboard/components/TaskNavigationControls.tsx new file mode 100644 index 00000000..c0721613 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/TaskNavigationControls.tsx @@ -0,0 +1,354 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Button, + IconButton, + Tooltip, + Typography, + Card, + CardContent, + Chip, + useTheme +} from '@mui/material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + ArrowBack as BackIcon, + ArrowForward as ForwardIcon, + PlayArrow as PlayIcon, + Pause as PauseIcon, + SkipNext as SkipIcon, + CheckCircle as CompleteIcon, + Navigation as NavigationIcon +} from '@mui/icons-material'; +import { useWorkflowStore } from '../../../stores/workflowStore'; +import { taskNavigationService } from '../../../services/TaskNavigationService'; +import { taskDependencyManager } from '../../../services/TaskDependencyManager'; + +interface TaskNavigationControlsProps { + compact?: boolean; + showTaskInfo?: boolean; + onTaskChange?: (taskId: string) => void; +} + +const TaskNavigationControls: React.FC = ({ + compact = false, + showTaskInfo = true, + onTaskChange +}) => { + const theme = useTheme(); + const { + currentWorkflow, + navigationState, + moveToNextTask, + moveToPreviousTask, + completeTask, + skipTask, + isLoading + } = useWorkflowStore(); + + const [isNavigating, setIsNavigating] = useState(false); + const [navigationError, setNavigationError] = useState(null); + + // Navigation event listener + useEffect(() => { + const handleNavigationEvent = (event: any) => { + console.log('Navigation event:', event); + if (onTaskChange && event.detail?.taskId) { + onTaskChange(event.detail.taskId); + } + }; + + taskNavigationService.addNavigationListener(handleNavigationEvent); + + return () => { + taskNavigationService.removeNavigationListener(handleNavigationEvent); + }; + }, [onTaskChange]); + + const handleNavigateToNext = async () => { + if (!currentWorkflow || !navigationState?.nextTask) return; + + setIsNavigating(true); + setNavigationError(null); + + try { + await moveToNextTask(); + } catch (error) { + setNavigationError(error instanceof Error ? error.message : 'Navigation failed'); + } finally { + setIsNavigating(false); + } + }; + + const handleNavigateBack = async () => { + if (!currentWorkflow || !navigationState?.canGoBack) return; + + setIsNavigating(true); + setNavigationError(null); + + try { + await moveToPreviousTask(); + } catch (error) { + setNavigationError(error instanceof Error ? error.message : 'Back navigation failed'); + } finally { + setIsNavigating(false); + } + }; + + const handleCompleteCurrentTask = async () => { + if (!currentWorkflow || !navigationState?.currentTask) return; + + try { + await completeTask(navigationState.currentTask.id); + } catch (error) { + setNavigationError(error instanceof Error ? error.message : 'Task completion failed'); + } + }; + + const handleSkipCurrentTask = async () => { + if (!currentWorkflow || !navigationState?.currentTask) return; + + try { + await skipTask(navigationState.currentTask.id); + } catch (error) { + setNavigationError(error instanceof Error ? error.message : 'Task skip failed'); + } + }; + + const getReadyTasks = () => { + if (!currentWorkflow) return []; + return taskDependencyManager.getReadyTasks(currentWorkflow); + }; + + const getBlockedTasks = () => { + if (!currentWorkflow) return []; + return taskDependencyManager.getBlockedTasks(currentWorkflow); + }; + + if (!currentWorkflow || !navigationState) { + return null; + } + + const currentTask = navigationState.currentTask; + const readyTasks = getReadyTasks(); + const blockedTasks = getBlockedTasks(); + + return ( + + + + {/* Header */} + + + + Task Navigation + + {navigationError && ( + + )} + + + {/* Current Task Info */} + {showTaskInfo && currentTask && ( + + + Current Task: + + + + {currentTask.title} + + + {currentTask.description} + + + + + + + + )} + + {/* Navigation Controls */} + + {/* Back Button */} + + + + + + + {/* Complete Task Button */} + + + {/* Skip Task Button */} + + + + + + + {/* Forward Button */} + + + + + + + + {/* Task Status Summary */} + {!compact && ( + + + + + + )} + + {/* Loading State */} + + {(isLoading || isNavigating) && ( + + + {isNavigating ? 'Navigating...' : 'Loading...'} + + + )} + + + + + ); +}; + +export default TaskNavigationControls; diff --git a/frontend/src/components/MainDashboard/components/WorkflowDemo.tsx b/frontend/src/components/MainDashboard/components/WorkflowDemo.tsx new file mode 100644 index 00000000..d6089611 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/WorkflowDemo.tsx @@ -0,0 +1,496 @@ +import React, { useState } from 'react'; +import { + Box, + Card, + CardContent, + Typography, + Button, + Chip, + Stack, + Alert, + AlertTitle, + Divider, + IconButton, + Tooltip, + Collapse, + LinearProgress, + Paper, + Grid +} from '@mui/material'; +import { + PlayArrow, + Pause, + Stop, + Info, + ExpandMore, + ExpandLess, + CheckCircle, + Schedule, + TrendingUp, + NavigateNext, + NavigateBefore, + SkipNext, + TaskAlt, + Timer, + Assignment +} from '@mui/icons-material'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useWorkflowStore } from '../../../stores/workflowStore'; + +interface WorkflowDemoProps { + compact?: boolean; +} + +const WorkflowDemo: React.FC = ({ compact = false }) => { + const [expanded, setExpanded] = useState(false); + const { + currentWorkflow, + workflowProgress, + navigationState, + isLoading, + generateDailyWorkflow, + startWorkflow, + completeTask, + skipTask, + moveToNextTask, + moveToPreviousTask, + isWorkflowComplete + } = useWorkflowStore(); + + const handleGenerateWorkflow = async () => { + try { + await generateDailyWorkflow('demo-user'); + } catch (error) { + console.error('Failed to generate workflow:', error); + } + }; + + const handleStartWorkflow = async () => { + if (currentWorkflow) { + try { + await startWorkflow(currentWorkflow.id); + } catch (error) { + console.error('Failed to start workflow:', error); + } + } + }; + + const handleCompleteTask = async (taskId: string) => { + try { + await completeTask(taskId); + } catch (error) { + console.error('Failed to complete task:', error); + } + }; + + const handleSkipTask = async (taskId: string) => { + try { + await skipTask(taskId); + } catch (error) { + console.error('Failed to skip task:', error); + } + }; + + const handleNextTask = async () => { + try { + await moveToNextTask(); + } catch (error) { + console.error('Failed to move to next task:', error); + } + }; + + const handlePreviousTask = async () => { + try { + await moveToPreviousTask(); + } catch (error) { + console.error('Failed to move to previous task:', error); + } + }; + + const isComplete = isWorkflowComplete(); + const hasWorkflow = !!currentWorkflow; + const isInProgress = currentWorkflow?.workflowStatus === 'in_progress'; + + if (compact) { + return ( + + + + + + + Today's Workflow + + {hasWorkflow && ( + + )} + + + {!hasWorkflow && ( + + )} + {hasWorkflow && !isInProgress && !isComplete && ( + + )} + setExpanded(!expanded)} + > + {expanded ? : } + + + + + + + {workflowProgress && ( + + + Progress: {workflowProgress.completedTasks} of {workflowProgress.totalTasks} tasks + + + )} + + {currentWorkflow && ( + + {currentWorkflow.tasks.slice(0, 3).map((task) => ( + + + + {task.title} + + + {task.estimatedTime} min • {task.priority} + + + + {task.status === 'pending' && isInProgress && ( + <> + + handleCompleteTask(task.id)} + sx={{ color: 'success.main' }} + > + + + + + handleSkipTask(task.id)} + sx={{ color: 'warning.main' }} + > + + + + + )} + {task.status === 'completed' && ( + + )} + {task.status === 'skipped' && ( + + )} + + + ))} + + )} + + + + + ); + } + + const getStatusColor = () => { + if (isComplete) return 'success'; + if (isInProgress) return 'primary'; + return 'default'; + }; + + const getStatusText = () => { + if (isComplete) return 'Workflow Complete! 🎉'; + if (isInProgress) return 'In Progress'; + if (!hasWorkflow) return 'No Workflow Generated'; + return 'Ready to Start'; + }; + + return ( + + + {/* Header Section */} + + + + {isComplete ? : + isInProgress ? : + } + Today's Marketing Workflow + + + + + + + + + + {/* Current Task Navigation */} + {navigationState?.currentTask && isInProgress && ( + + + + + Current Task + + + + {navigationState.currentTask.title} + + + {navigationState.currentTask.description} + + + {/* Navigation Controls */} + + + + + + + + )} + + {/* Task List */} + {currentWorkflow && ( + + + + Today's Tasks + + + + {currentWorkflow.tasks.map((task, index) => ( + + + + + + {task.title} + + {task.id === navigationState?.currentTask?.id && ( + + )} + + + + {task.description} + + + + } + /> + + + + + + {task.status === 'pending' && isInProgress && ( + <> + + handleCompleteTask(task.id)} + sx={{ + color: 'success.main', + '&:hover': { background: 'rgba(76, 175, 80, 0.1)' } + }} + > + + + + + handleSkipTask(task.id)} + sx={{ + color: 'warning.main', + '&:hover': { background: 'rgba(255, 152, 0, 0.1)' } + }} + > + + + + + )} + {task.status === 'completed' && ( + + )} + {task.status === 'skipped' && ( + + )} + + + + + ))} + + + + )} + + {/* Help Section */} + {!hasWorkflow && ( + + Getting Started + Generate today's workflow to see your personalized marketing tasks. The system will guide you through each task with clear instructions and navigation controls. + + )} + + + ); +}; + +export default WorkflowDemo; \ No newline at end of file diff --git a/frontend/src/components/MainDashboard/components/WorkflowProgressBar.tsx b/frontend/src/components/MainDashboard/components/WorkflowProgressBar.tsx new file mode 100644 index 00000000..cfc344a2 --- /dev/null +++ b/frontend/src/components/MainDashboard/components/WorkflowProgressBar.tsx @@ -0,0 +1,251 @@ +import React from 'react'; +import { + Box, + Typography, + LinearProgress, + Chip, + IconButton, + Tooltip, + useTheme +} from '@mui/material'; +import { motion } from 'framer-motion'; +import { + PlayArrow, + Pause, + CheckCircle, + Schedule, + TrendingUp +} from '@mui/icons-material'; +import { useWorkflowStore } from '../../../stores/workflowStore'; + +interface WorkflowProgressBarProps { + onStartWorkflow?: () => void; + onPauseWorkflow?: () => void; + onResumeWorkflow?: () => void; + showControls?: boolean; + compact?: boolean; +} + +const WorkflowProgressBar: React.FC = ({ + onStartWorkflow, + onPauseWorkflow, + onResumeWorkflow, + showControls = true, + compact = false +}) => { + const theme = useTheme(); + const { + currentWorkflow, + workflowProgress, + navigationState, + isLoading, + startWorkflow, + isWorkflowComplete, + getCompletionPercentage, + generateDailyWorkflow + } = useWorkflowStore(); + + const completionPercentage = getCompletionPercentage(); + const isComplete = isWorkflowComplete(); + const currentTask = navigationState?.currentTask; + + // Always show the progress bar, even if no workflow exists yet + + const handleStartWorkflow = async () => { + try { + if (currentWorkflow) { + await startWorkflow(currentWorkflow.id); + onStartWorkflow?.(); + } else { + // Generate a new workflow if none exists + await generateDailyWorkflow('demo-user'); + onStartWorkflow?.(); + } + } catch (error) { + console.error('Failed to start workflow:', error); + } + }; + + const getStatusColor = () => { + if (isComplete) return theme.palette.success.main; + if (currentWorkflow?.workflowStatus === 'in_progress') return theme.palette.primary.main; + return theme.palette.grey[500]; + }; + + const getStatusText = () => { + if (isComplete) return 'Workflow Complete! 🎉'; + if (currentWorkflow?.workflowStatus === 'in_progress') return 'In Progress'; + if (!currentWorkflow) return 'No Workflow Generated'; + return 'Ready to Start'; + }; + + return ( + + + {/* Header */} + + + + {isComplete ? : + currentWorkflow?.workflowStatus === 'in_progress' ? : + } + Today's Marketing Workflow + + + + + + {/* Controls */} + {showControls && ( + + {(currentWorkflow?.workflowStatus === 'not_started' || !currentWorkflow) && ( + + + + + + )} + + {currentWorkflow?.workflowStatus === 'in_progress' && ( + + + + + + )} + + )} + + + {/* Progress Bar */} + + + + Progress + + + {workflowProgress?.completedTasks || 0} of {workflowProgress?.totalTasks || 0} tasks + + + + + + + {currentWorkflow ? `${completionPercentage}% complete` : 'No workflow active'} + + + + {/* Current Task Info */} + {currentTask && !isComplete && ( + + + Current Task: + + + {currentTask.title} + + + {currentTask.description} + + + )} + + {/* Time Information */} + {workflowProgress && ( + + + Time Spent: {workflowProgress.actualTimeSpent} min + + + Est. Remaining: {workflowProgress.estimatedTimeRemaining} min + + + )} + + + ); +}; + +export default WorkflowProgressBar; diff --git a/frontend/src/components/shared/DashboardHeader.tsx b/frontend/src/components/shared/DashboardHeader.tsx index 15707327..ae214698 100644 --- a/frontend/src/components/shared/DashboardHeader.tsx +++ b/frontend/src/components/shared/DashboardHeader.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Typography, Chip } from '@mui/material'; +import { Box, Typography, Chip, Button, CircularProgress } from '@mui/material'; +import { PlayArrow, Pause, Stop } from '@mui/icons-material'; import { ShimmerHeader } from './styled'; import { DashboardHeaderProps } from './types'; @@ -7,32 +8,226 @@ const DashboardHeader: React.FC = ({ title, subtitle, statusChips = [], - rightContent + rightContent, + customIcon, + workflowControls }) => { return ( - - - - - {title} - - - {subtitle} - + + + + {customIcon && ( + + )} + + + + {title} + + {subtitle && ( + + {subtitle} + + )} + + + {/* Workflow Controls */} + {workflowControls && ( + + {/* Workflow Control Buttons */} + {!workflowControls.isWorkflowActive ? ( + /* Start Button with Badge and Lightning Glow */ + + + + {`${workflowControls.completedTasks}/${workflowControls.totalTasks}`} + + + ) : ( + /* In-Progress/Completed Controls with Enhanced Styling */ + + {/* In-Progress/Completed Status with Lightning Glow */} + + + + {`${workflowControls.completedTasks}/${workflowControls.totalTasks}`} + + + + )} + + )} + {statusChips.length > 0 && ( diff --git a/frontend/src/components/shared/SearchFilter.tsx b/frontend/src/components/shared/SearchFilter.tsx index 6d65298b..c447d534 100644 --- a/frontend/src/components/shared/SearchFilter.tsx +++ b/frontend/src/components/shared/SearchFilter.tsx @@ -26,6 +26,15 @@ const SearchFilter: React.FC = ({ toolCategories, theme }) => { + // Helper function to get tool count from a category + const getToolCount = (category: any): number => { + if ('tools' in category) { + return category.tools.length; + } else if ('subCategories' in category) { + return Object.values(category.subCategories).reduce((total: number, subCat: any) => total + subCat.tools.length, 0); + } + return 0; + }; return ( @@ -74,13 +83,14 @@ const SearchFilter: React.FC = ({ - {/* Enhanced Category Filter */} + {/* Enhanced Category Filter with Tool Count Badges */} onCategoryChange(null)} active={selectedCategory === null} theme={theme} + toolCount={Object.values(toolCategories).reduce((total, category) => total + getToolCount(category), 0)} /> {Object.keys(toolCategories).map((category) => ( = ({ onClick={() => onCategoryChange(category)} active={selectedCategory === category} theme={theme} + toolCount={getToolCount(toolCategories[category])} /> ))} diff --git a/frontend/src/components/shared/styled.ts b/frontend/src/components/shared/styled.ts index f6303570..e9e68263 100644 --- a/frontend/src/components/shared/styled.ts +++ b/frontend/src/components/shared/styled.ts @@ -96,15 +96,24 @@ export const SearchContainer = styled(Box)(({ theme }) => ({ })); export const CategoryChip = styled(Chip, { - shouldForwardProp: (prop) => prop !== 'active', -})<{ active?: boolean }>(({ theme, active }) => ({ - background: active ? 'rgba(255, 255, 255, 0.25)' : 'rgba(255, 255, 255, 0.1)', + shouldForwardProp: (prop) => prop !== 'active' && prop !== 'toolCount', +})<{ active?: boolean; toolCount?: number }>(({ theme, active, toolCount }) => ({ + background: active + ? 'linear-gradient(135deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.2) 100%)' + : 'rgba(255, 255, 255, 0.1)', color: 'white', - fontWeight: 600, + fontWeight: active ? 700 : 600, fontSize: '0.9rem', padding: theme.spacing(1, 2), - border: `1px solid ${active ? 'rgba(255, 255, 255, 0.4)' : 'rgba(255, 255, 255, 0.2)'}`, + border: active + ? '2px solid rgba(255, 255, 255, 0.6)' + : '1px solid rgba(255, 255, 255, 0.2)', + boxShadow: active + ? '0 6px 20px rgba(255, 255, 255, 0.2), 0 0 0 1px rgba(255,255,255,0.1)' + : 'none', + transform: active ? 'translateY(-2px) scale(1.05)' : 'none', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + position: 'relative', '&:hover': { background: 'rgba(255, 255, 255, 0.25)', transform: 'translateY(-2px)', @@ -112,7 +121,29 @@ export const CategoryChip = styled(Chip, { }, '& .MuiChip-label': { padding: theme.spacing(0.5, 1), + display: 'flex', + alignItems: 'center', + gap: theme.spacing(0.5), }, + // Tool count badge + ...(toolCount && { + '&::after': { + content: `"${toolCount}"`, + position: 'absolute', + top: -6, + right: -6, + backgroundColor: active ? '#4caf50' : 'rgba(255, 255, 255, 0.8)', + color: active ? 'white' : 'rgba(0, 0, 0, 0.8)', + borderRadius: '10px', + padding: '2px 6px', + fontSize: '0.7rem', + fontWeight: 700, + minWidth: '18px', + textAlign: 'center', + boxShadow: '0 2px 6px rgba(0,0,0,0.2)', + border: active ? '1px solid rgba(255,255,255,0.3)' : '1px solid rgba(0,0,0,0.1)', + }, + }), })); export const EnhancedGlassCard = styled(GlassCard)(({ theme }) => ({ diff --git a/frontend/src/components/shared/types.ts b/frontend/src/components/shared/types.ts index 457ee86f..e7fc6969 100644 --- a/frontend/src/components/shared/types.ts +++ b/frontend/src/components/shared/types.ts @@ -77,13 +77,24 @@ export interface SearchFilterProps { export interface DashboardHeaderProps { title: string; - subtitle: string; + subtitle?: string; statusChips?: Array<{ label: string; color: string; icon: React.ReactElement; }>; rightContent?: React.ReactNode; + customIcon?: string; + workflowControls?: { + onStartWorkflow: () => void; + onPauseWorkflow?: () => void; + onStopWorkflow?: () => void; + onResumePlanModal?: () => void; + isWorkflowActive: boolean; + completedTasks: number; + totalTasks: number; + isLoading: boolean; + }; } export interface LoadingSkeletonProps { diff --git a/frontend/src/data/toolCategories.ts b/frontend/src/data/toolCategories.ts index 17cb3fd0..813b7d48 100644 --- a/frontend/src/data/toolCategories.ts +++ b/frontend/src/data/toolCategories.ts @@ -2,7 +2,6 @@ import React from 'react'; import { Article as ArticleIcon, Search as SearchIcon, - TrendingUp as TrendingUpIcon, Campaign as CampaignIcon, Analytics as AnalyticsIcon, Psychology as PsychologyIcon, diff --git a/frontend/src/services/TaskCompletionVerifier.ts b/frontend/src/services/TaskCompletionVerifier.ts new file mode 100644 index 00000000..0ff64c15 --- /dev/null +++ b/frontend/src/services/TaskCompletionVerifier.ts @@ -0,0 +1,426 @@ +import { + TodayTask +} from '../types/workflow'; + +interface VerificationResult { + isCompleted: boolean; + confidence: number; // 0-1 scale + evidence: string[]; + warnings: string[]; + suggestions: string[]; +} + +interface VerificationRule { + id: string; + name: string; + description: string; + pillarId: string; + actionType: string; + verifier: (task: TodayTask, context?: any) => Promise; + weight: number; // Importance weight for confidence calculation +} + +interface VerificationContext { + userId: string; + timestamp: Date; + platformData?: Record; + userActivity?: Record; +} + +class TaskCompletionVerifier { + private verificationRules: Map = new Map(); + private verificationHistory: Map = new Map(); + + constructor() { + this.initializeDefaultRules(); + } + + /** + * Verify if a task has been completed + */ + async verifyTaskCompletion( + task: TodayTask, + context?: VerificationContext + ): Promise { + try { + const rule = this.verificationRules.get(`${task.pillarId}-${task.actionType}`); + + if (!rule) { + // Fallback to basic verification + return this.basicVerification(task, context); + } + + const result = await rule.verifier(task, context); + + // Store verification history + this.storeVerificationResult(task.id, result); + + return result; + } catch (error) { + console.error(`Verification failed for task ${task.id}:`, error); + + return { + isCompleted: false, + confidence: 0, + evidence: [], + warnings: [`Verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`], + suggestions: ['Try completing the task again or contact support'] + }; + } + } + + /** + * Verify multiple tasks at once + */ + async verifyMultipleTasks( + tasks: TodayTask[], + context?: VerificationContext + ): Promise> { + const results = new Map(); + + // Verify tasks in parallel for better performance + const verificationPromises = tasks.map(async (task) => { + const result = await this.verifyTaskCompletion(task, context); + results.set(task.id, result); + }); + + await Promise.all(verificationPromises); + return results; + } + + /** + * Get verification history for a task + */ + getVerificationHistory(taskId: string): VerificationResult[] { + return this.verificationHistory.get(taskId) || []; + } + + /** + * Add custom verification rule + */ + addVerificationRule(rule: VerificationRule): void { + const key = `${rule.pillarId}-${rule.actionType}`; + this.verificationRules.set(key, rule); + } + + /** + * Remove verification rule + */ + removeVerificationRule(pillarId: string, actionType: string): void { + const key = `${pillarId}-${actionType}`; + this.verificationRules.delete(key); + } + + /** + * Get all verification rules + */ + getVerificationRules(): VerificationRule[] { + return Array.from(this.verificationRules.values()); + } + + /** + * Initialize default verification rules + */ + private initializeDefaultRules(): void { + // Plan pillar rules + this.addVerificationRule({ + id: 'plan-navigate', + name: 'Content Planning Navigation', + description: 'Verify user navigated to content planning dashboard', + pillarId: 'plan', + actionType: 'navigate', + weight: 0.8, + verifier: async (task, context) => { + return this.verifyNavigation(task, context); + } + }); + + // Generate pillar rules + this.addVerificationRule({ + id: 'generate-navigate', + name: 'Content Generation Navigation', + description: 'Verify user navigated to content generation tools', + pillarId: 'generate', + actionType: 'navigate', + weight: 0.8, + verifier: async (task, context) => { + return this.verifyNavigation(task, context); + } + }); + + // Publish pillar rules + this.addVerificationRule({ + id: 'publish-navigate', + name: 'Content Publishing Navigation', + description: 'Verify user navigated to publishing tools', + pillarId: 'publish', + actionType: 'navigate', + weight: 0.8, + verifier: async (task, context) => { + return this.verifyNavigation(task, context); + } + }); + + // Analyze pillar rules + this.addVerificationRule({ + id: 'analyze-navigate', + name: 'Analytics Navigation', + description: 'Verify user navigated to analytics dashboard', + pillarId: 'analyze', + actionType: 'navigate', + weight: 0.8, + verifier: async (task, context) => { + return this.verifyNavigation(task, context); + } + }); + + // Engage pillar rules + this.addVerificationRule({ + id: 'engage-navigate', + name: 'Engagement Navigation', + description: 'Verify user navigated to engagement tools', + pillarId: 'engage', + actionType: 'navigate', + weight: 0.8, + verifier: async (task, context) => { + return this.verifyNavigation(task, context); + } + }); + + // Remarket pillar rules + this.addVerificationRule({ + id: 'remarket-navigate', + name: 'Remarketing Navigation', + description: 'Verify user navigated to remarketing tools', + pillarId: 'remarket', + actionType: 'navigate', + weight: 0.8, + verifier: async (task, context) => { + return this.verifyNavigation(task, context); + } + }); + } + + /** + * Verify navigation-based tasks + */ + private async verifyNavigation( + task: TodayTask, + context?: VerificationContext + ): Promise { + const evidence: string[] = []; + const warnings: string[] = []; + const suggestions: string[] = []; + let confidence = 0; + + try { + // Check if user is currently on the target page + if (typeof window !== 'undefined' && task.actionUrl) { + const currentPath = window.location.pathname; + const targetPath = task.actionUrl; + + if (currentPath === targetPath) { + evidence.push(`User is currently on target page: ${targetPath}`); + confidence += 0.4; + } else { + warnings.push(`User is not on target page. Current: ${currentPath}, Expected: ${targetPath}`); + suggestions.push('Navigate to the correct page to complete this task'); + } + } + + // Check user activity (if available) + if (context?.userActivity) { + const activity = context.userActivity; + const taskStartTime = task.startedAt?.getTime() || 0; + const recentActivity = Object.entries(activity) + .filter(([_, timestamp]) => typeof timestamp === 'number' && timestamp > taskStartTime); + + if (recentActivity.length > 0) { + evidence.push(`User activity detected after task start: ${recentActivity.length} actions`); + confidence += 0.3; + } else { + warnings.push('No user activity detected after task start'); + } + } + + // Check platform data (if available) + if (context?.platformData) { + const platformData = context.platformData; + if (platformData.lastActivity && platformData.lastActivity > (task.startedAt?.getTime() || 0)) { + evidence.push('Platform activity detected after task start'); + confidence += 0.3; + } + } + + // Time-based verification + if (task.startedAt && task.completedAt) { + const timeSpent = task.completedAt.getTime() - task.startedAt.getTime(); + const estimatedTime = task.estimatedTime * 60 * 1000; // Convert to milliseconds + + if (timeSpent >= estimatedTime * 0.5) { // At least 50% of estimated time + evidence.push(`Task took ${Math.round(timeSpent / 60000)} minutes (estimated: ${task.estimatedTime} minutes)`); + confidence += 0.2; + } else { + warnings.push(`Task completed too quickly (${Math.round(timeSpent / 60000)} minutes vs ${task.estimatedTime} estimated)`); + } + } + + // Cap confidence at 1.0 + confidence = Math.min(confidence, 1.0); + + return { + isCompleted: confidence >= 0.6, // Threshold for completion + confidence, + evidence, + warnings, + suggestions + }; + + } catch (error) { + return { + isCompleted: false, + confidence: 0, + evidence: [], + warnings: [`Navigation verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`], + suggestions: ['Try navigating to the target page again'] + }; + } + } + + /** + * Basic verification fallback + */ + private async basicVerification( + task: TodayTask, + context?: VerificationContext + ): Promise { + const evidence: string[] = []; + const warnings: string[] = []; + const suggestions: string[] = []; + let confidence = 0; + + // Check if task has completion timestamp + if (task.completedAt) { + evidence.push('Task has completion timestamp'); + confidence += 0.5; + } else { + warnings.push('No completion timestamp found'); + } + + // Check if task was started + if (task.startedAt) { + evidence.push('Task was started'); + confidence += 0.3; + } else { + warnings.push('No start timestamp found'); + } + + // Check time spent + if (task.startedAt && task.completedAt) { + const timeSpent = task.completedAt.getTime() - task.startedAt.getTime(); + if (timeSpent > 0) { + evidence.push(`Task took ${Math.round(timeSpent / 60000)} minutes`); + confidence += 0.2; + } + } + + return { + isCompleted: confidence >= 0.5, + confidence, + evidence, + warnings, + suggestions: suggestions.length > 0 ? suggestions : ['Complete the task to verify completion'] + }; + } + + /** + * Store verification result in history + */ + private storeVerificationResult(taskId: string, result: VerificationResult): void { + const history = this.verificationHistory.get(taskId) || []; + history.push(result); + + // Keep only last 10 verification results + if (history.length > 10) { + history.shift(); + } + + this.verificationHistory.set(taskId, history); + } + + /** + * Get verification statistics + */ + getVerificationStats(): { + totalVerifications: number; + averageConfidence: number; + completionRate: number; + mostCommonWarnings: string[]; + } { + const allResults = Array.from(this.verificationHistory.values()).flat(); + + if (allResults.length === 0) { + return { + totalVerifications: 0, + averageConfidence: 0, + completionRate: 0, + mostCommonWarnings: [] + }; + } + + const totalVerifications = allResults.length; + const averageConfidence = allResults.reduce((sum, result) => sum + result.confidence, 0) / totalVerifications; + const completionRate = allResults.filter(result => result.isCompleted).length / totalVerifications; + + // Count warning frequency + const warningCounts = new Map(); + allResults.forEach(result => { + result.warnings.forEach(warning => { + warningCounts.set(warning, (warningCounts.get(warning) || 0) + 1); + }); + }); + + const mostCommonWarnings = Array.from(warningCounts.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) + .map(([warning]) => warning); + + return { + totalVerifications, + averageConfidence, + completionRate, + mostCommonWarnings + }; + } + + /** + * Clear verification history + */ + clearVerificationHistory(): void { + this.verificationHistory.clear(); + } + + /** + * Export verification data + */ + exportVerificationData(): { + rules: VerificationRule[]; + history: Record; + stats: { + totalVerifications: number; + averageConfidence: number; + completionRate: number; + mostCommonWarnings: string[]; + }; + } { + return { + rules: this.getVerificationRules(), + history: Object.fromEntries(this.verificationHistory), + stats: this.getVerificationStats() + }; + } +} + +// Export singleton instance +export const taskCompletionVerifier = new TaskCompletionVerifier(); +export default TaskCompletionVerifier; diff --git a/frontend/src/services/TaskDependencyManager.ts b/frontend/src/services/TaskDependencyManager.ts new file mode 100644 index 00000000..4b233eec --- /dev/null +++ b/frontend/src/services/TaskDependencyManager.ts @@ -0,0 +1,433 @@ +import { + TodayTask, + DailyWorkflow, + WorkflowError +} from '../types/workflow'; + +interface DependencyGraph { + [taskId: string]: { + dependencies: string[]; + dependents: string[]; + status: 'ready' | 'blocked' | 'completed' | 'skipped'; + }; +} + +interface DependencyValidationResult { + isValid: boolean; + errors: string[]; + warnings: string[]; + readyTasks: string[]; + blockedTasks: string[]; +} + +class TaskDependencyManager { + private dependencyGraph: DependencyGraph = {}; + + /** + * Build dependency graph from workflow tasks + */ + buildDependencyGraph(workflow: DailyWorkflow): DependencyGraph { + this.dependencyGraph = {}; + + // Initialize all tasks in the graph + workflow.tasks.forEach(task => { + this.dependencyGraph[task.id] = { + dependencies: task.dependencies || [], + dependents: [], + status: this.getTaskStatus(task, workflow) + }; + }); + + // Build dependent relationships + Object.keys(this.dependencyGraph).forEach(taskId => { + const task = this.dependencyGraph[taskId]; + task.dependencies.forEach(depId => { + if (this.dependencyGraph[depId]) { + this.dependencyGraph[depId].dependents.push(taskId); + } + }); + }); + + return this.dependencyGraph; + } + + /** + * Validate dependency graph for issues + */ + validateDependencyGraph(workflow: DailyWorkflow): DependencyValidationResult { + const result: DependencyValidationResult = { + isValid: true, + errors: [], + warnings: [], + readyTasks: [], + blockedTasks: [] + }; + + this.buildDependencyGraph(workflow); + + // Check for circular dependencies + const circularDeps = this.detectCircularDependencies(); + if (circularDeps.length > 0) { + result.isValid = false; + result.errors.push(`Circular dependencies detected: ${circularDeps.join(', ')}`); + } + + // Check for missing dependencies + const missingDeps = this.detectMissingDependencies(workflow); + if (missingDeps.length > 0) { + result.isValid = false; + result.errors.push(`Missing dependencies: ${missingDeps.join(', ')}`); + } + + // Check for orphaned tasks (tasks with no dependencies or dependents) + const orphanedTasks = this.detectOrphanedTasks(); + if (orphanedTasks.length > 0) { + result.warnings.push(`Orphaned tasks detected: ${orphanedTasks.join(', ')}`); + } + + // Categorize tasks by readiness + Object.keys(this.dependencyGraph).forEach(taskId => { + const task = this.dependencyGraph[taskId]; + if (task.status === 'ready') { + result.readyTasks.push(taskId); + } else if (task.status === 'blocked') { + result.blockedTasks.push(taskId); + } + }); + + return result; + } + + /** + * Get tasks that are ready to be executed + */ + getReadyTasks(workflow: DailyWorkflow): TodayTask[] { + this.buildDependencyGraph(workflow); + + return workflow.tasks.filter(task => { + const graphTask = this.dependencyGraph[task.id]; + return graphTask && graphTask.status === 'ready' && task.status === 'pending'; + }); + } + + /** + * Get tasks that are blocked by dependencies + */ + getBlockedTasks(workflow: DailyWorkflow): TodayTask[] { + this.buildDependencyGraph(workflow); + + return workflow.tasks.filter(task => { + const graphTask = this.dependencyGraph[task.id]; + return graphTask && graphTask.status === 'blocked'; + }); + } + + /** + * Get tasks that depend on a specific task + */ + getDependentTasks(taskId: string): string[] { + const task = this.dependencyGraph[taskId]; + return task ? [...task.dependents] : []; + } + + /** + * Get tasks that a specific task depends on + */ + getDependencyTasks(taskId: string): string[] { + const task = this.dependencyGraph[taskId]; + return task ? [...task.dependencies] : []; + } + + /** + * Check if a task can be executed (all dependencies satisfied) + */ + canExecuteTask(taskId: string, workflow: DailyWorkflow): boolean { + this.buildDependencyGraph(workflow); + + const task = this.dependencyGraph[taskId]; + if (!task) { + return false; + } + + return task.status === 'ready'; + } + + /** + * Get the optimal execution order for tasks + */ + getOptimalExecutionOrder(workflow: DailyWorkflow): TodayTask[] { + this.buildDependencyGraph(workflow); + + const visited = new Set(); + const visiting = new Set(); + const executionOrder: TodayTask[] = []; + + const visit = (taskId: string) => { + if (visiting.has(taskId)) { + throw new WorkflowError({ + code: 'CIRCULAR_DEPENDENCY', + message: `Circular dependency detected involving task ${taskId}`, + timestamp: new Date(), + recoverable: false + }); + } + + if (visited.has(taskId)) { + return; + } + + visiting.add(taskId); + + const task = this.dependencyGraph[taskId]; + if (task) { + // Visit dependencies first + task.dependencies.forEach(depId => { + visit(depId); + }); + } + + visiting.delete(taskId); + visited.add(taskId); + + // Add task to execution order + const workflowTask = workflow.tasks.find(t => t.id === taskId); + if (workflowTask) { + executionOrder.push(workflowTask); + } + }; + + // Visit all tasks + workflow.tasks.forEach(task => { + if (!visited.has(task.id)) { + visit(task.id); + } + }); + + return executionOrder; + } + + /** + * Update task status in dependency graph + */ + updateTaskStatus(taskId: string, status: 'completed' | 'skipped' | 'in_progress'): void { + if (this.dependencyGraph[taskId]) { + // Update status of the task + this.dependencyGraph[taskId].status = status === 'in_progress' ? 'ready' : status; + + // Update status of dependent tasks + this.updateDependentTasksStatus(taskId); + } + } + + /** + * Get dependency chain for a task (all tasks that must be completed first) + */ + getDependencyChain(taskId: string): string[] { + const chain: string[] = []; + const visited = new Set(); + + const buildChain = (currentTaskId: string) => { + if (visited.has(currentTaskId)) { + return; + } + + visited.add(currentTaskId); + const task = this.dependencyGraph[currentTaskId]; + + if (task) { + task.dependencies.forEach(depId => { + buildChain(depId); + if (!chain.includes(depId)) { + chain.push(depId); + } + }); + } + }; + + buildChain(taskId); + return chain; + } + + /** + * Get impact of completing a task (what tasks become available) + */ + getCompletionImpact(taskId: string): string[] { + const impact: string[] = []; + + const task = this.dependencyGraph[taskId]; + if (task) { + task.dependents.forEach(dependentId => { + const dependent = this.dependencyGraph[dependentId]; + if (dependent && dependent.status === 'blocked') { + // Check if all dependencies are now satisfied + const allDepsSatisfied = dependent.dependencies.every(depId => { + const depTask = this.dependencyGraph[depId]; + return depTask && (depTask.status === 'completed' || depTask.status === 'skipped'); + }); + + if (allDepsSatisfied) { + impact.push(dependentId); + } + } + }); + } + + return impact; + } + + /** + * Detect circular dependencies in the graph + */ + private detectCircularDependencies(): string[] { + const visited = new Set(); + const visiting = new Set(); + const circular: string[] = []; + + const visit = (taskId: string, path: string[] = []) => { + if (visiting.has(taskId)) { + const cycleStart = path.indexOf(taskId); + if (cycleStart !== -1) { + circular.push(...path.slice(cycleStart), taskId); + } + return; + } + + if (visited.has(taskId)) { + return; + } + + visiting.add(taskId); + const task = this.dependencyGraph[taskId]; + + if (task) { + task.dependencies.forEach(depId => { + visit(depId, [...path, taskId]); + }); + } + + visiting.delete(taskId); + visited.add(taskId); + }; + + Object.keys(this.dependencyGraph).forEach(taskId => { + if (!visited.has(taskId)) { + visit(taskId); + } + }); + + return [...new Set(circular)]; + } + + /** + * Detect missing dependencies + */ + private detectMissingDependencies(workflow: DailyWorkflow): string[] { + const missing: string[] = []; + const taskIds = new Set(workflow.tasks.map(t => t.id)); + + Object.keys(this.dependencyGraph).forEach(taskId => { + const task = this.dependencyGraph[taskId]; + task.dependencies.forEach(depId => { + if (!taskIds.has(depId)) { + missing.push(`${taskId} -> ${depId}`); + } + }); + }); + + return missing; + } + + /** + * Detect orphaned tasks + */ + private detectOrphanedTasks(): string[] { + const orphaned: string[] = []; + + Object.keys(this.dependencyGraph).forEach(taskId => { + const task = this.dependencyGraph[taskId]; + if (task.dependencies.length === 0 && task.dependents.length === 0) { + orphaned.push(taskId); + } + }); + + return orphaned; + } + + /** + * Update dependent tasks status when a dependency is completed + */ + private updateDependentTasksStatus(completedTaskId: string): void { + const task = this.dependencyGraph[completedTaskId]; + if (!task) { + return; + } + + task.dependents.forEach(dependentId => { + const dependent = this.dependencyGraph[dependentId]; + if (dependent && dependent.status === 'blocked') { + // Check if all dependencies are now satisfied + const allDepsSatisfied = dependent.dependencies.every(depId => { + const depTask = this.dependencyGraph[depId]; + return depTask && (depTask.status === 'completed' || depTask.status === 'skipped'); + }); + + if (allDepsSatisfied) { + dependent.status = 'ready'; + } + } + }); + } + + /** + * Get task status based on dependencies + */ + private getTaskStatus(task: TodayTask, workflow: DailyWorkflow): 'ready' | 'blocked' | 'completed' | 'skipped' { + if (task.status === 'completed' || task.status === 'skipped') { + return task.status; + } + + if (!task.dependencies || task.dependencies.length === 0) { + return 'ready'; + } + + // Check if all dependencies are satisfied + const allDepsSatisfied = task.dependencies.every(depId => { + const depTask = workflow.tasks.find(t => t.id === depId); + return depTask && (depTask.status === 'completed' || depTask.status === 'skipped'); + }); + + return allDepsSatisfied ? 'ready' : 'blocked'; + } + + /** + * Get dependency graph visualization data + */ + getDependencyGraphData(): { + nodes: Array<{ id: string; label: string; status: string }>; + edges: Array<{ from: string; to: string; type: string }>; + } { + const nodes = Object.keys(this.dependencyGraph).map(taskId => ({ + id: taskId, + label: taskId, + status: this.dependencyGraph[taskId].status + })); + + const edges: Array<{ from: string; to: string; type: string }> = []; + Object.keys(this.dependencyGraph).forEach(taskId => { + const task = this.dependencyGraph[taskId]; + task.dependencies.forEach(depId => { + edges.push({ + from: depId, + to: taskId, + type: 'dependency' + }); + }); + }); + + return { nodes, edges }; + } +} + +// Export singleton instance +export const taskDependencyManager = new TaskDependencyManager(); +export default TaskDependencyManager; diff --git a/frontend/src/services/TaskNavigationService.ts b/frontend/src/services/TaskNavigationService.ts new file mode 100644 index 00000000..4eb87520 --- /dev/null +++ b/frontend/src/services/TaskNavigationService.ts @@ -0,0 +1,469 @@ +import { + TodayTask, + DailyWorkflow, + NavigationState, + WorkflowError +} from '../types/workflow'; + +interface NavigationConfig { + autoNavigate: boolean; + delayBeforeNavigation: number; // milliseconds + showNavigationConfirmation: boolean; + enableBackNavigation: boolean; + persistNavigationState: boolean; +} + +interface NavigationEvent { + type: 'task_started' | 'task_completed' | 'task_skipped' | 'navigation_requested'; + taskId: string; + workflowId: string; + timestamp: Date; + metadata?: Record; +} + +class TaskNavigationService { + private config: NavigationConfig; + private navigationHistory: NavigationEvent[] = []; + private currentNavigationState: NavigationState | null = null; + private navigationListeners: Array<(event: NavigationEvent) => void> = []; + + constructor(config: NavigationConfig = { + autoNavigate: true, + delayBeforeNavigation: 2000, + showNavigationConfirmation: false, + enableBackNavigation: true, + persistNavigationState: true + }) { + this.config = config; + this.loadNavigationHistory(); + } + + /** + * Navigate to a specific task + */ + async navigateToTask( + task: TodayTask, + workflow: DailyWorkflow, + options: { + skipConfirmation?: boolean; + trackNavigation?: boolean; + } = {} + ): Promise { + try { + // Validate task and workflow + if (!this.validateTaskForNavigation(task, workflow)) { + throw new WorkflowError({ + code: 'INVALID_NAVIGATION_TARGET', + message: `Cannot navigate to task ${task.id}`, + timestamp: new Date(), + recoverable: true, + suggestedAction: 'Check task dependencies and status' + }); + } + + // Show confirmation if required + if (this.config.showNavigationConfirmation && !options.skipConfirmation) { + const confirmed = await this.showNavigationConfirmation(task); + if (!confirmed) { + return false; + } + } + + // Execute navigation based on action type + const navigationSuccess = await this.executeNavigation(task); + + if (navigationSuccess) { + // Update navigation state + this.updateNavigationState(task, workflow); + + // Track navigation event + if (options.trackNavigation !== false) { + this.trackNavigationEvent({ + type: 'navigation_requested', + taskId: task.id, + workflowId: workflow.id, + timestamp: new Date(), + metadata: { actionType: task.actionType, actionUrl: task.actionUrl } + }); + } + + // Auto-navigate to next task if enabled + if (this.config.autoNavigate && task.status === 'completed') { + setTimeout(() => { + this.autoNavigateToNextTask(workflow); + }, this.config.delayBeforeNavigation); + } + } + + return navigationSuccess; + } catch (error) { + console.error('Navigation failed:', error); + throw error; + } + } + + /** + * Auto-navigate to the next available task + */ + async autoNavigateToNextTask(workflow: DailyWorkflow): Promise { + try { + const nextTask = this.getNextAvailableTask(workflow); + + if (nextTask) { + await this.navigateToTask(nextTask, workflow, { + skipConfirmation: true, + trackNavigation: true + }); + return nextTask; + } + + return null; + } catch (error) { + console.error('Auto-navigation failed:', error); + return null; + } + } + + /** + * Navigate back to previous task + */ + async navigateBack(workflow: DailyWorkflow): Promise { + if (!this.config.enableBackNavigation) { + throw new WorkflowError({ + code: 'BACK_NAVIGATION_DISABLED', + message: 'Back navigation is disabled', + timestamp: new Date(), + recoverable: false + }); + } + + try { + const previousTask = this.getPreviousTask(workflow); + + if (previousTask) { + await this.navigateToTask(previousTask, workflow, { + skipConfirmation: true, + trackNavigation: true + }); + return previousTask; + } + + return null; + } catch (error) { + console.error('Back navigation failed:', error); + throw error; + } + } + + /** + * Get the next available task in the workflow + */ + getNextAvailableTask(workflow: DailyWorkflow): TodayTask | null { + const currentIndex = workflow.currentTaskIndex; + const remainingTasks = workflow.tasks.slice(currentIndex + 1); + + // Find next task that's not completed and has dependencies satisfied + for (const task of remainingTasks) { + if (task.status === 'pending' && this.areDependenciesSatisfied(task, workflow)) { + return task; + } + } + + return null; + } + + /** + * Get the previous task in the workflow + */ + getPreviousTask(workflow: DailyWorkflow): TodayTask | null { + const currentIndex = workflow.currentTaskIndex; + + if (currentIndex > 0) { + return workflow.tasks[currentIndex - 1]; + } + + return null; + } + + /** + * Check if task dependencies are satisfied + */ + areDependenciesSatisfied(task: TodayTask, workflow: DailyWorkflow): boolean { + if (!task.dependencies || task.dependencies.length === 0) { + return true; + } + + return task.dependencies.every(depId => { + const depTask = workflow.tasks.find(t => t.id === depId); + return depTask && depTask.status === 'completed'; + }); + } + + /** + * Execute the actual navigation based on task action type + */ + private async executeNavigation(task: TodayTask): Promise { + try { + switch (task.actionType) { + case 'navigate': + return await this.navigateToInternalPage(task); + case 'modal': + return await this.openModal(task); + case 'external': + return await this.navigateToExternalUrl(task); + default: + throw new WorkflowError({ + code: 'UNKNOWN_ACTION_TYPE', + message: `Unknown action type: ${task.actionType}`, + timestamp: new Date(), + recoverable: true, + suggestedAction: 'Check task configuration' + }); + } + } catch (error) { + console.error(`Navigation execution failed for task ${task.id}:`, error); + return false; + } + } + + /** + * Navigate to internal ALwrity page + */ + private async navigateToInternalPage(task: TodayTask): Promise { + if (!task.actionUrl) { + throw new WorkflowError({ + code: 'MISSING_ACTION_URL', + message: `Task ${task.id} is missing action URL`, + timestamp: new Date(), + recoverable: true, + suggestedAction: 'Configure action URL for the task' + }); + } + + try { + // Use React Router navigation + if (typeof window !== 'undefined' && window.history) { + window.history.pushState(null, '', task.actionUrl); + + // Dispatch custom event for React Router to handle + window.dispatchEvent(new PopStateEvent('popstate')); + + return true; + } + + return false; + } catch (error) { + console.error('Internal navigation failed:', error); + return false; + } + } + + /** + * Open modal for task + */ + private async openModal(task: TodayTask): Promise { + try { + // Dispatch custom event to open modal + const modalEvent = new CustomEvent('openTaskModal', { + detail: { task } + }); + + if (typeof window !== 'undefined') { + window.dispatchEvent(modalEvent); + return true; + } + + return false; + } catch (error) { + console.error('Modal opening failed:', error); + return false; + } + } + + /** + * Navigate to external URL + */ + private async navigateToExternalUrl(task: TodayTask): Promise { + if (!task.actionUrl) { + throw new WorkflowError({ + code: 'MISSING_ACTION_URL', + message: `Task ${task.id} is missing external URL`, + timestamp: new Date(), + recoverable: true, + suggestedAction: 'Configure external URL for the task' + }); + } + + try { + if (typeof window !== 'undefined') { + window.open(task.actionUrl, '_blank', 'noopener,noreferrer'); + return true; + } + + return false; + } catch (error) { + console.error('External navigation failed:', error); + return false; + } + } + + /** + * Validate if task can be navigated to + */ + private validateTaskForNavigation(task: TodayTask, workflow: DailyWorkflow): boolean { + // Check if task exists in workflow + const workflowTask = workflow.tasks.find(t => t.id === task.id); + if (!workflowTask) { + return false; + } + + // Check if task is enabled + if (!task.enabled) { + return false; + } + + // Check dependencies + if (!this.areDependenciesSatisfied(task, workflow)) { + return false; + } + + return true; + } + + /** + * Show navigation confirmation dialog + */ + private async showNavigationConfirmation(task: TodayTask): Promise { + return new Promise((resolve) => { + // In a real implementation, this would show a confirmation dialog + // For now, we'll use a simple confirm dialog + const confirmed = window.confirm( + `Navigate to: ${task.title}\n\n${task.description}\n\nContinue?` + ); + resolve(confirmed); + }); + } + + /** + * Update navigation state + */ + private updateNavigationState(task: TodayTask, workflow: DailyWorkflow): void { + const currentIndex = workflow.tasks.findIndex(t => t.id === task.id); + const previousTask = currentIndex > 0 ? workflow.tasks[currentIndex - 1] : null; + const nextTask = currentIndex < workflow.tasks.length - 1 ? workflow.tasks[currentIndex + 1] : null; + + this.currentNavigationState = { + currentTask: task, + previousTask, + nextTask, + canGoBack: currentIndex > 0, + canGoForward: currentIndex < workflow.tasks.length - 1 + }; + } + + /** + * Track navigation event + */ + private trackNavigationEvent(event: NavigationEvent): void { + this.navigationHistory.push(event); + + // Notify listeners + this.navigationListeners.forEach(listener => { + try { + listener(event); + } catch (error) { + console.error('Navigation listener error:', error); + } + }); + + // Persist navigation history + if (this.config.persistNavigationState) { + this.persistNavigationHistory(); + } + } + + /** + * Add navigation event listener + */ + addNavigationListener(listener: (event: NavigationEvent) => void): void { + this.navigationListeners.push(listener); + } + + /** + * Remove navigation event listener + */ + removeNavigationListener(listener: (event: NavigationEvent) => void): void { + const index = this.navigationListeners.indexOf(listener); + if (index > -1) { + this.navigationListeners.splice(index, 1); + } + } + + /** + * Get current navigation state + */ + getCurrentNavigationState(): NavigationState | null { + return this.currentNavigationState; + } + + /** + * Get navigation history + */ + getNavigationHistory(): NavigationEvent[] { + return [...this.navigationHistory]; + } + + /** + * Clear navigation history + */ + clearNavigationHistory(): void { + this.navigationHistory = []; + this.persistNavigationHistory(); + } + + /** + * Persist navigation history to localStorage + */ + private persistNavigationHistory(): void { + try { + localStorage.setItem('task-navigation-history', JSON.stringify(this.navigationHistory)); + } catch (error) { + console.warn('Failed to persist navigation history:', error); + } + } + + /** + * Load navigation history from localStorage + */ + private loadNavigationHistory(): void { + try { + const stored = localStorage.getItem('task-navigation-history'); + if (stored) { + this.navigationHistory = JSON.parse(stored).map((event: any) => ({ + ...event, + timestamp: new Date(event.timestamp) + })); + } + } catch (error) { + console.warn('Failed to load navigation history:', error); + } + } + + /** + * Update navigation configuration + */ + updateConfig(newConfig: Partial): void { + this.config = { ...this.config, ...newConfig }; + } + + /** + * Get current configuration + */ + getConfig(): NavigationConfig { + return { ...this.config }; + } +} + +// Export singleton instance +export const taskNavigationService = new TaskNavigationService(); +export default TaskNavigationService; diff --git a/frontend/src/services/TaskWorkflowOrchestrator.ts b/frontend/src/services/TaskWorkflowOrchestrator.ts new file mode 100644 index 00000000..e4b4d69f --- /dev/null +++ b/frontend/src/services/TaskWorkflowOrchestrator.ts @@ -0,0 +1,611 @@ +import { + TodayTask, + DailyWorkflow, + WorkflowProgress, + TaskCompletionData, + TaskGenerationContext, + WorkflowOrchestratorConfig, + NavigationState, + WorkflowError +} from '../types/workflow'; +import { taskNavigationService } from './TaskNavigationService'; +import { taskDependencyManager } from './TaskDependencyManager'; +import { taskCompletionVerifier } from './TaskCompletionVerifier'; + +class TaskWorkflowOrchestrator { + private workflows: Map = new Map(); + private config: WorkflowOrchestratorConfig; + + constructor(config: WorkflowOrchestratorConfig = { + autoNavigate: true, + showProgress: true, + enableNotifications: true, + persistProgress: true, + allowTaskSkipping: true + }) { + this.config = config; + this.loadPersistedWorkflows(); + } + + /** + * Generate a new daily workflow for a user + */ + async generateDailyWorkflow( + userId: string, + date: string = new Date().toISOString().split('T')[0], + context?: TaskGenerationContext + ): Promise { + try { + // Check if workflow already exists for this date + const existingWorkflow = this.getWorkflow(userId, date); + if (existingWorkflow) { + return existingWorkflow; + } + + // Generate tasks based on context or default configuration + const tasks = await this.generateTasksForDate(userId, date, context); + + // Create new workflow + const workflow: DailyWorkflow = { + id: `${userId}-${date}`, + date, + userId, + tasks, + currentTaskIndex: 0, + completedTasks: 0, + totalTasks: tasks.length, + workflowStatus: 'not_started', + totalEstimatedTime: tasks.reduce((sum, task) => sum + task.estimatedTime, 0), + actualTimeSpent: 0 + }; + + // Save workflow + this.workflows.set(workflow.id, workflow); + this.persistWorkflow(workflow); + + return workflow; + } catch (error) { + throw new WorkflowError({ + code: 'WORKFLOW_GENERATION_FAILED', + message: `Failed to generate workflow for user ${userId} on ${date}`, + timestamp: new Date(), + recoverable: true, + suggestedAction: 'Retry workflow generation' + }); + } + } + + /** + * Get workflow for a specific user and date + */ + getWorkflow(userId: string, date: string): DailyWorkflow | null { + const workflowId = `${userId}-${date}`; + return this.workflows.get(workflowId) || null; + } + + /** + * Start a workflow + */ + async startWorkflow(workflowId: string): Promise { + const workflow = this.workflows.get(workflowId); + if (!workflow) { + throw new WorkflowError({ + code: 'WORKFLOW_NOT_FOUND', + message: `Workflow ${workflowId} not found`, + timestamp: new Date(), + recoverable: false + }); + } + + workflow.workflowStatus = 'in_progress'; + workflow.startedAt = new Date(); + + // Mark first task as in progress + if (workflow.tasks.length > 0) { + workflow.tasks[0].status = 'in_progress'; + workflow.tasks[0].startedAt = new Date(); + } + + this.persistWorkflow(workflow); + return workflow; + } + + /** + * Complete a specific task + */ + async completeTask( + workflowId: string, + taskId: string, + completionData?: Partial + ): Promise { + const workflow = this.workflows.get(workflowId); + if (!workflow) { + throw new WorkflowError({ + code: 'WORKFLOW_NOT_FOUND', + message: `Workflow ${workflowId} not found`, + timestamp: new Date(), + recoverable: false + }); + } + + const task = workflow.tasks.find(t => t.id === taskId); + if (!task) { + throw new WorkflowError({ + code: 'TASK_NOT_FOUND', + message: `Task ${taskId} not found in workflow ${workflowId}`, + timestamp: new Date(), + recoverable: false + }); + } + + // Verify task completion + await taskCompletionVerifier.verifyTaskCompletion(task, { + userId: workflow.userId, + timestamp: new Date() + }); + + // Mark task as completed + task.status = 'completed'; + task.completedAt = new Date(); + + // Calculate time spent + if (task.startedAt) { + const timeSpent = Math.round((task.completedAt.getTime() - task.startedAt.getTime()) / (1000 * 60)); + workflow.actualTimeSpent += timeSpent; + } + + // Update dependency manager + taskDependencyManager.updateTaskStatus(taskId, 'completed'); + + // Update workflow progress + workflow.completedTasks++; + + // Check if workflow is complete + if (workflow.completedTasks === workflow.totalTasks) { + workflow.workflowStatus = 'completed'; + workflow.completedAt = new Date(); + } + + // Auto-navigate to next task if enabled + if (this.config.autoNavigate) { + const nextTask = taskDependencyManager.getReadyTasks(workflow)[0]; + if (nextTask) { + setTimeout(async () => { + try { + await taskNavigationService.navigateToTask(nextTask, workflow); + } catch (error) { + console.warn('Auto-navigation failed:', error); + } + }, 2000); // 2 second delay + } + } + + this.persistWorkflow(workflow); + return this.getWorkflowProgress(workflowId); + } + + /** + * Skip a task + */ + async skipTask(workflowId: string, taskId: string): Promise { + const workflow = this.workflows.get(workflowId); + if (!workflow) { + throw new WorkflowError({ + code: 'WORKFLOW_NOT_FOUND', + message: `Workflow ${workflowId} not found`, + timestamp: new Date(), + recoverable: false + }); + } + + const task = workflow.tasks.find(t => t.id === taskId); + if (!task) { + throw new WorkflowError({ + code: 'TASK_NOT_FOUND', + message: `Task ${taskId} not found in workflow ${workflowId}`, + timestamp: new Date(), + recoverable: false + }); + } + + task.status = 'skipped'; + workflow.completedTasks++; + + this.persistWorkflow(workflow); + return this.getWorkflowProgress(workflowId); + } + + /** + * Get current workflow progress + */ + getWorkflowProgress(workflowId: string): WorkflowProgress { + const workflow = this.workflows.get(workflowId); + if (!workflow) { + throw new WorkflowError({ + code: 'WORKFLOW_NOT_FOUND', + message: `Workflow ${workflowId} not found`, + timestamp: new Date(), + recoverable: false + }); + } + + const currentTask = workflow.tasks[workflow.currentTaskIndex]; + const nextTask = workflow.tasks[workflow.currentTaskIndex + 1]; + const remainingTasks = workflow.tasks.slice(workflow.currentTaskIndex + 1); + const estimatedTimeRemaining = remainingTasks.reduce((sum, task) => sum + task.estimatedTime, 0); + + return { + completedTasks: workflow.completedTasks, + totalTasks: workflow.totalTasks, + completionPercentage: Math.round((workflow.completedTasks / workflow.totalTasks) * 100), + currentTask, + nextTask, + estimatedTimeRemaining, + actualTimeSpent: workflow.actualTimeSpent + }; + } + + /** + * Get navigation state for current workflow + */ + getNavigationState(workflowId: string): NavigationState { + const workflow = this.workflows.get(workflowId); + if (!workflow) { + throw new WorkflowError({ + code: 'WORKFLOW_NOT_FOUND', + message: `Workflow ${workflowId} not found`, + timestamp: new Date(), + recoverable: false + }); + } + + const currentTask = workflow.tasks[workflow.currentTaskIndex]; + const previousTask = workflow.currentTaskIndex > 0 ? workflow.tasks[workflow.currentTaskIndex - 1] : null; + const nextTask = workflow.currentTaskIndex < workflow.tasks.length - 1 ? workflow.tasks[workflow.currentTaskIndex + 1] : null; + + return { + currentTask, + previousTask, + nextTask, + canGoBack: workflow.currentTaskIndex > 0, + canGoForward: workflow.currentTaskIndex < workflow.tasks.length - 1 + }; + } + + /** + * Move to next task in workflow + */ + async moveToNextTask(workflowId: string): Promise { + const workflow = this.workflows.get(workflowId); + if (!workflow) { + throw new WorkflowError({ + code: 'WORKFLOW_NOT_FOUND', + message: `Workflow ${workflowId} not found`, + timestamp: new Date(), + recoverable: false + }); + } + + if (workflow.currentTaskIndex < workflow.tasks.length - 1) { + workflow.currentTaskIndex++; + const nextTask = workflow.tasks[workflow.currentTaskIndex]; + + // Mark next task as in progress + if (nextTask.status === 'pending') { + nextTask.status = 'in_progress'; + nextTask.startedAt = new Date(); + } + + this.persistWorkflow(workflow); + return nextTask; + } + + return null; + } + + /** + * Generate tasks for a specific date (enhanced with dependency management) + */ + private async generateTasksForDate( + userId: string, + date: string, + context?: TaskGenerationContext + ): Promise { + // This is a placeholder implementation + // In Phase 3, this will be replaced with AI-powered task generation + + const defaultTasks: TodayTask[] = [ + { + id: `${userId}-${date}-plan-1`, + pillarId: 'plan', + title: 'Review content strategy', + description: 'Check and update your content strategy for the week', + status: 'pending', + priority: 'high', + estimatedTime: 15, + actionType: 'navigate', + actionUrl: '/content-planning-dashboard', + enabled: true, + icon: 'Business', + color: '#4CAF50' + }, + { + id: `${userId}-${date}-plan-2`, + pillarId: 'plan', + title: 'Update content calendar', + description: 'Review and update your content calendar', + status: 'pending', + priority: 'medium', + estimatedTime: 10, + dependencies: [`${userId}-${date}-plan-1`], + actionType: 'navigate', + actionUrl: '/content-planning-dashboard', + enabled: true, + icon: 'CalendarMonth', + color: '#4CAF50' + }, + { + id: `${userId}-${date}-generate-1`, + pillarId: 'generate', + title: 'Create social media content', + description: 'Generate content for your social media platforms', + status: 'pending', + priority: 'high', + estimatedTime: 30, + dependencies: [`${userId}-${date}-plan-1`, `${userId}-${date}-plan-2`], + actionType: 'navigate', + actionUrl: '/facebook-writer', + enabled: true, + icon: 'AutoAwesome', + color: '#2196F3' + }, + { + id: `${userId}-${date}-generate-2`, + pillarId: 'generate', + title: 'Create blog content', + description: 'Write blog posts for your website', + status: 'pending', + priority: 'medium', + estimatedTime: 45, + dependencies: [`${userId}-${date}-plan-1`], + actionType: 'navigate', + actionUrl: '/blog-writer', + enabled: true, + icon: 'Article', + color: '#2196F3' + }, + { + id: `${userId}-${date}-publish-1`, + pillarId: 'publish', + title: 'Publish social media content', + description: 'Publish your created content to social media', + status: 'pending', + priority: 'medium', + estimatedTime: 10, + dependencies: [`${userId}-${date}-generate-1`], + actionType: 'navigate', + actionUrl: '/facebook-writer', + enabled: true, + icon: 'Publish', + color: '#FF9800' + }, + { + id: `${userId}-${date}-publish-2`, + pillarId: 'publish', + title: 'Publish blog content', + description: 'Publish blog posts to your website', + status: 'pending', + priority: 'medium', + estimatedTime: 15, + dependencies: [`${userId}-${date}-generate-2`], + actionType: 'navigate', + actionUrl: '/blog-writer', + enabled: true, + icon: 'Publish', + color: '#FF9800' + }, + { + id: `${userId}-${date}-analyze-1`, + pillarId: 'analyze', + title: 'Review content performance', + description: 'Analyze performance of published content', + status: 'pending', + priority: 'low', + estimatedTime: 20, + dependencies: [`${userId}-${date}-publish-1`, `${userId}-${date}-publish-2`], + actionType: 'navigate', + actionUrl: '/analytics-dashboard', + enabled: true, + icon: 'Analytics', + color: '#9C27B0' + }, + { + id: `${userId}-${date}-engage-1`, + pillarId: 'engage', + title: 'Respond to comments', + description: 'Engage with comments on your content', + status: 'pending', + priority: 'low', + estimatedTime: 15, + dependencies: [`${userId}-${date}-publish-1`], + actionType: 'navigate', + actionUrl: '/engagement-dashboard', + enabled: true, + icon: 'ChatBubbleOutline', + color: '#E91E63' + }, + // Engage pillar tasks + { + id: `${userId}-${date}-engage-1`, + pillarId: 'engage', + title: 'Reply to blog comment', + description: 'Respond to comments on your latest blog post', + status: 'pending', + priority: 'high', + estimatedTime: 10, + dependencies: [`${userId}-${date}-analyze-1`], + actionType: 'navigate', + actionUrl: '/engagement-dashboard', + enabled: true, + icon: 'Comment', + color: '#E91E63' + }, + { + id: `${userId}-${date}-engage-2`, + pillarId: 'engage', + title: 'Respond to Twitter mention', + description: 'Reply to Twitter mentions and engage with followers', + status: 'pending', + priority: 'medium', + estimatedTime: 5, + dependencies: [`${userId}-${date}-engage-1`], + actionType: 'navigate', + actionUrl: '/engagement-dashboard', + enabled: true, + icon: 'Twitter', + color: '#1DA1F2' + }, + // Remarket pillar tasks + { + id: `${userId}-${date}-remarket-1`, + pillarId: 'remarket', + title: 'Launch Retargeting Campaign', + description: 'Create and launch targeted remarketing campaigns', + status: 'pending', + priority: 'high', + estimatedTime: 35, + dependencies: [`${userId}-${date}-engage-2`], + actionType: 'navigate', + actionUrl: '/remarketing-dashboard', + enabled: true, + icon: 'Psychology', + color: '#00695C' + }, + { + id: `${userId}-${date}-remarket-2`, + pillarId: 'remarket', + title: 'Lead Nurturing Sequence', + description: 'Set up automated lead nurturing workflows', + status: 'pending', + priority: 'medium', + estimatedTime: 30, + dependencies: [`${userId}-${date}-remarket-1`], + actionType: 'navigate', + actionUrl: '/lead-nurturing', + enabled: true, + icon: 'Refresh', + color: '#4CAF50' + } + ]; + + // Validate dependencies and get optimal execution order + const tempWorkflow: DailyWorkflow = { + id: `${userId}-${date}`, + date, + userId, + tasks: defaultTasks, + currentTaskIndex: 0, + completedTasks: 0, + totalTasks: defaultTasks.length, + workflowStatus: 'not_started', + totalEstimatedTime: defaultTasks.reduce((sum, task) => sum + task.estimatedTime, 0), + actualTimeSpent: 0 + }; + + // Validate dependency graph + const validation = taskDependencyManager.validateDependencyGraph(tempWorkflow); + if (!validation.isValid) { + console.warn('Dependency validation failed:', validation.errors); + // Return tasks without dependencies if validation fails + return defaultTasks.map(task => ({ ...task, dependencies: [] })); + } + + // Get optimal execution order + const orderedTasks = taskDependencyManager.getOptimalExecutionOrder(tempWorkflow); + + return orderedTasks; + } + + /** + * Persist workflow to localStorage + */ + private persistWorkflow(workflow: DailyWorkflow): void { + if (this.config.persistProgress) { + try { + localStorage.setItem(`workflow-${workflow.id}`, JSON.stringify(workflow)); + } catch (error) { + console.warn('Failed to persist workflow:', error); + } + } + } + + /** + * Load persisted workflows from localStorage + */ + private loadPersistedWorkflows(): void { + if (this.config.persistProgress) { + try { + const keys = Object.keys(localStorage).filter(key => key.startsWith('workflow-')); + keys.forEach(key => { + const workflowData = localStorage.getItem(key); + if (workflowData) { + try { + const workflow = JSON.parse(workflowData) as DailyWorkflow; + + // Ensure workflow has required properties + if (!workflow.id || !workflow.date || !workflow.userId) { + console.warn(`Invalid workflow data for key ${key}, skipping`); + return; + } + + // Ensure tasks array exists and is valid + if (!workflow.tasks || !Array.isArray(workflow.tasks)) { + console.warn(`Invalid tasks array for workflow ${workflow.id}, initializing empty array`); + workflow.tasks = []; + } + + // Convert date strings back to Date objects + if (workflow.startedAt) workflow.startedAt = new Date(workflow.startedAt); + if (workflow.completedAt) workflow.completedAt = new Date(workflow.completedAt); + + // Process tasks with null checks + workflow.tasks.forEach(task => { + if (task && typeof task === 'object') { + if (task.startedAt) task.startedAt = new Date(task.startedAt); + if (task.completedAt) task.completedAt = new Date(task.completedAt); + } + }); + + this.workflows.set(workflow.id, workflow); + } catch (parseError) { + console.warn(`Failed to parse workflow data for key ${key}:`, parseError); + // Remove corrupted data + localStorage.removeItem(key); + } + } + }); + } catch (error) { + console.warn('Failed to load persisted workflows:', error); + } + } + } + + /** + * Clear completed workflows (cleanup) + */ + clearCompletedWorkflows(): void { + const completedWorkflows = Array.from(this.workflows.values()) + .filter(workflow => workflow.workflowStatus === 'completed'); + + completedWorkflows.forEach(workflow => { + this.workflows.delete(workflow.id); + if (this.config.persistProgress) { + localStorage.removeItem(`workflow-${workflow.id}`); + } + }); + } +} + +// Export singleton instance +export const taskWorkflowOrchestrator = new TaskWorkflowOrchestrator(); +export default TaskWorkflowOrchestrator; diff --git a/frontend/src/stores/workflowStore.ts b/frontend/src/stores/workflowStore.ts new file mode 100644 index 00000000..0a003140 --- /dev/null +++ b/frontend/src/stores/workflowStore.ts @@ -0,0 +1,366 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { + TodayTask, + DailyWorkflow, + WorkflowProgress, + UserWorkflowPreferences, + NavigationState, + WorkflowError +} from '../types/workflow'; +import { taskWorkflowOrchestrator } from '../services/TaskWorkflowOrchestrator'; + +interface WorkflowState { + // Current workflow state + currentWorkflow: DailyWorkflow | null; + workflowProgress: WorkflowProgress | null; + navigationState: NavigationState | null; + + // User preferences + userPreferences: UserWorkflowPreferences | null; + + // UI state + isWorkflowModalOpen: boolean; + isLoading: boolean; + error: WorkflowError | null; + + // Actions + generateDailyWorkflow: (userId: string, date?: string) => Promise; + startWorkflow: (workflowId: string) => Promise; + pauseWorkflow: (workflowId: string) => Promise; + stopWorkflow: (workflowId: string) => Promise; + completeTask: (taskId: string, completionData?: any) => Promise; + skipTask: (taskId: string) => Promise; + moveToNextTask: () => Promise; + moveToPreviousTask: () => Promise; + + // UI actions + openWorkflowModal: () => void; + closeWorkflowModal: () => void; + setError: (error: WorkflowError | null) => void; + clearError: () => void; + + // Preferences + updateUserPreferences: (preferences: Partial) => void; + + // Utility actions + refreshWorkflowProgress: () => void; + getCurrentTask: () => TodayTask | null; + getNextTask: () => TodayTask | null; + isWorkflowComplete: () => boolean; + getCompletionPercentage: () => number; +} + +export const useWorkflowStore = create()( + persist( + (set, get) => ({ + // Initial state + currentWorkflow: null, + workflowProgress: null, + navigationState: null, + userPreferences: null, + isWorkflowModalOpen: false, + isLoading: false, + error: null, + + // Generate daily workflow + generateDailyWorkflow: async (userId: string, date?: string) => { + set({ isLoading: true, error: null }); + + try { + const workflow = await taskWorkflowOrchestrator.generateDailyWorkflow(userId, date); + const progress = taskWorkflowOrchestrator.getWorkflowProgress(workflow.id); + const navigation = taskWorkflowOrchestrator.getNavigationState(workflow.id); + + set({ + currentWorkflow: workflow, + workflowProgress: progress, + navigationState: navigation, + isLoading: false + }); + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Start workflow + startWorkflow: async (workflowId: string) => { + set({ isLoading: true, error: null }); + + try { + const workflow = await taskWorkflowOrchestrator.startWorkflow(workflowId); + const progress = taskWorkflowOrchestrator.getWorkflowProgress(workflow.id); + const navigation = taskWorkflowOrchestrator.getNavigationState(workflow.id); + + set({ + currentWorkflow: workflow, + workflowProgress: progress, + navigationState: navigation, + isLoading: false + }); + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Pause workflow + pauseWorkflow: async (workflowId: string) => { + set({ isLoading: true, error: null }); + + try { + // For now, we'll just update the workflow status to paused + // In a real implementation, this would call the orchestrator + const currentWorkflow = get().currentWorkflow; + if (currentWorkflow && currentWorkflow.id === workflowId) { + const pausedWorkflow = { + ...currentWorkflow, + workflowStatus: 'paused' as const, + pausedAt: new Date() + }; + + set({ + currentWorkflow: pausedWorkflow, + isLoading: false + }); + } + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Stop workflow + stopWorkflow: async (workflowId: string) => { + set({ isLoading: true, error: null }); + + try { + // For now, we'll just update the workflow status to stopped + // In a real implementation, this would call the orchestrator + const currentWorkflow = get().currentWorkflow; + if (currentWorkflow && currentWorkflow.id === workflowId) { + const stoppedWorkflow = { + ...currentWorkflow, + workflowStatus: 'stopped' as const, + completedAt: new Date() + }; + + set({ + currentWorkflow: stoppedWorkflow, + isLoading: false + }); + } + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Complete task + completeTask: async (taskId: string, completionData?: any) => { + const { currentWorkflow } = get(); + if (!currentWorkflow) return; + + set({ isLoading: true, error: null }); + + try { + const progress = await taskWorkflowOrchestrator.completeTask( + currentWorkflow.id, + taskId, + completionData + ); + const navigation = taskWorkflowOrchestrator.getNavigationState(currentWorkflow.id); + + // Update current workflow + const updatedWorkflow = taskWorkflowOrchestrator.getWorkflow( + currentWorkflow.userId, + currentWorkflow.date + ); + + set({ + currentWorkflow: updatedWorkflow, + workflowProgress: progress, + navigationState: navigation, + isLoading: false + }); + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Skip task + skipTask: async (taskId: string) => { + const { currentWorkflow } = get(); + if (!currentWorkflow) return; + + set({ isLoading: true, error: null }); + + try { + const progress = await taskWorkflowOrchestrator.skipTask( + currentWorkflow.id, + taskId + ); + const navigation = taskWorkflowOrchestrator.getNavigationState(currentWorkflow.id); + + // Update current workflow + const updatedWorkflow = taskWorkflowOrchestrator.getWorkflow( + currentWorkflow.userId, + currentWorkflow.date + ); + + set({ + currentWorkflow: updatedWorkflow, + workflowProgress: progress, + navigationState: navigation, + isLoading: false + }); + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Move to next task + moveToNextTask: async () => { + const { currentWorkflow } = get(); + if (!currentWorkflow) return; + + set({ isLoading: true, error: null }); + + try { + await taskWorkflowOrchestrator.moveToNextTask(currentWorkflow.id); + const progress = taskWorkflowOrchestrator.getWorkflowProgress(currentWorkflow.id); + const navigation = taskWorkflowOrchestrator.getNavigationState(currentWorkflow.id); + + // Update current workflow + const updatedWorkflow = taskWorkflowOrchestrator.getWorkflow( + currentWorkflow.userId, + currentWorkflow.date + ); + + set({ + currentWorkflow: updatedWorkflow, + workflowProgress: progress, + navigationState: navigation, + isLoading: false + }); + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // Move to previous task + moveToPreviousTask: async () => { + const { currentWorkflow } = get(); + if (!currentWorkflow) return; + + set({ isLoading: true, error: null }); + + try { + // This would need to be implemented in the orchestrator + // For now, we'll just refresh the navigation state + const navigation = taskWorkflowOrchestrator.getNavigationState(currentWorkflow.id); + + set({ + navigationState: navigation, + isLoading: false + }); + } catch (error) { + const workflowError = error as WorkflowError; + set({ + error: workflowError, + isLoading: false + }); + } + }, + + // UI actions + openWorkflowModal: () => set({ isWorkflowModalOpen: true }), + closeWorkflowModal: () => set({ isWorkflowModalOpen: false }), + setError: (error: WorkflowError | null) => set({ error }), + clearError: () => set({ error: null }), + + // Update user preferences + updateUserPreferences: (preferences: Partial) => { + const { userPreferences } = get(); + set({ + userPreferences: { + ...userPreferences, + ...preferences + } as UserWorkflowPreferences + }); + }, + + // Utility actions + refreshWorkflowProgress: () => { + const { currentWorkflow } = get(); + if (!currentWorkflow) return; + + try { + const progress = taskWorkflowOrchestrator.getWorkflowProgress(currentWorkflow.id); + const navigation = taskWorkflowOrchestrator.getNavigationState(currentWorkflow.id); + + set({ + workflowProgress: progress, + navigationState: navigation + }); + } catch (error) { + console.warn('Failed to refresh workflow progress:', error); + } + }, + + getCurrentTask: () => { + const { navigationState } = get(); + return navigationState?.currentTask || null; + }, + + getNextTask: () => { + const { navigationState } = get(); + return navigationState?.nextTask || null; + }, + + isWorkflowComplete: () => { + const { workflowProgress } = get(); + return workflowProgress ? workflowProgress.completedTasks === workflowProgress.totalTasks : false; + }, + + getCompletionPercentage: () => { + const { workflowProgress } = get(); + return workflowProgress?.completionPercentage || 0; + } + }), + { + name: 'workflow-store', + partialize: (state) => ({ + userPreferences: state.userPreferences, + currentWorkflow: state.currentWorkflow + }) + } + ) +); + +export default useWorkflowStore; diff --git a/frontend/src/types/workflow.ts b/frontend/src/types/workflow.ts new file mode 100644 index 00000000..be46c9ff --- /dev/null +++ b/frontend/src/types/workflow.ts @@ -0,0 +1,168 @@ +// Core workflow and task type definitions +import React from 'react'; + +export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'skipped'; +export type TaskPriority = 'high' | 'medium' | 'low'; +export type ActionType = 'navigate' | 'modal' | 'external'; +export type WorkflowStatus = 'not_started' | 'in_progress' | 'completed' | 'paused' | 'stopped'; + +export interface TodayTask { + id: string; + pillarId: string; + title: string; + description: string; + status: TaskStatus; + priority: TaskPriority; + estimatedTime: number; // in minutes + dependencies?: string[]; // task IDs that must be completed first + actionUrl?: string; + actionType: ActionType; + completedAt?: Date; + startedAt?: Date; + metadata?: Record; + icon?: string | React.ComponentType; // icon name or component reference + color?: string; + enabled: boolean; + action?: () => void; +} + +export interface DailyWorkflow { + id: string; + date: string; // YYYY-MM-DD format + userId: string; + tasks: TodayTask[]; + currentTaskIndex: number; + completedTasks: number; + totalTasks: number; + workflowStatus: WorkflowStatus; + startedAt?: Date; + completedAt?: Date; + totalEstimatedTime: number; // in minutes + actualTimeSpent: number; // in minutes +} + +export interface WorkflowProgress { + completedTasks: number; + totalTasks: number; + completionPercentage: number; + currentTask?: TodayTask; + nextTask?: TodayTask; + estimatedTimeRemaining: number; // in minutes + actualTimeSpent: number; // in minutes +} + +export interface TaskCompletionData { + taskId: string; + completedAt: Date; + timeSpent: number; // in minutes + userNotes?: string; + metadata?: Record; +} + +export interface WorkflowAnalytics { + dailyCompletionRate: number; + averageTaskTime: number; + mostCompletedPillar: string; + completionStreak: number; + totalTasksCompleted: number; + lastWorkflowDate?: string; +} + +// Pillar-specific task generation interfaces +export interface PillarTaskConfig { + pillarId: string; + enabled: boolean; + taskCount: number; + priority: TaskPriority; + dependencies: string[]; + customTasks?: TodayTask[]; +} + +export interface UserWorkflowPreferences { + userId: string; + preferredTaskOrder: string[]; // pillar IDs in preferred order + dailyTaskLimit: number; + estimatedTimeLimit: number; // in minutes + skipWeekends: boolean; + notificationSettings: { + taskReminders: boolean; + completionCelebrations: boolean; + progressUpdates: boolean; + }; +} + +// Workflow orchestration interfaces +export interface WorkflowOrchestratorConfig { + autoNavigate: boolean; + showProgress: boolean; + enableNotifications: boolean; + persistProgress: boolean; + allowTaskSkipping: boolean; +} + +export interface TaskGenerationContext { + userId: string; + date: string; + userPreferences: UserWorkflowPreferences; + existingTasks: TodayTask[]; + platformData?: Record; // data from connected platforms +} + +// Navigation and action interfaces +export interface TaskAction { + type: ActionType; + url?: string; + modalId?: string; + externalUrl?: string; + params?: Record; +} + +export interface NavigationState { + currentTask: TodayTask | null; + previousTask: TodayTask | null; + nextTask: TodayTask | null; + canGoBack: boolean; + canGoForward: boolean; +} + +// Error handling interfaces +export interface WorkflowError { + code: string; + message: string; + taskId?: string; + timestamp: Date; + recoverable: boolean; + suggestedAction?: string; +} + +// WorkflowError class for throwing errors +export class WorkflowError extends Error { + code: string; + taskId?: string; + timestamp: Date; + recoverable: boolean; + suggestedAction?: string; + + constructor(error: { + code: string; + message: string; + taskId?: string; + timestamp: Date; + recoverable: boolean; + suggestedAction?: string; + }) { + super(error.message); + this.name = 'WorkflowError'; + this.code = error.code; + this.taskId = error.taskId; + this.timestamp = error.timestamp; + this.recoverable = error.recoverable; + this.suggestedAction = error.suggestedAction; + } +} + +export interface WorkflowErrorHandler { + handleError: (error: WorkflowError) => Promise; + recoverFromError: (error: WorkflowError) => Promise; + logError: (error: WorkflowError) => Promise; +}