Alwrity today's tasks workflow implementation plan.

This commit is contained in:
ajaysi
2025-09-06 15:28:05 +05:30
parent f82ada0361
commit ae42720c2a
25 changed files with 7836 additions and 46 deletions

View File

@@ -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.*

View File

@@ -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 (
<Tooltip
title={
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
{chip.label}
</Typography>
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
>
<Typography variant="caption" sx={{ color: 'white' }}>
{chip.bubbles[currentIndex]}
</Typography>
</motion.div>
</AnimatePresence>
</Box>
}
arrow
placement="top"
>
<Box
sx={{
position: 'relative',
cursor: 'pointer',
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
'&:hover': {
transform: 'translateY(-4px) scale(1.05)',
'& .chip-glow': {
opacity: 1,
transform: 'scale(1.2)'
},
'& .chip-shadow': {
opacity: 0.6,
transform: 'translateY(8px) scale(1.1)'
}
}
}}
onClick={handleClick}
>
{/* Glow Effect */}
<Box
className="chip-glow"
sx={{
position: 'absolute',
top: -8,
left: -8,
right: -8,
bottom: -8,
background: chip.gradient || chip.color,
borderRadius: '20px',
opacity: 0,
transition: 'all 0.4s ease',
filter: 'blur(12px)',
zIndex: -2
}}
/>
{/* Shadow Effect */}
<Box
className="chip-shadow"
sx={{
position: 'absolute',
top: 4,
left: 2,
right: -2,
bottom: -4,
background: 'rgba(0,0,0,0.3)',
borderRadius: '16px',
opacity: 0.3,
transition: 'all 0.4s ease',
filter: 'blur(8px)',
zIndex: -1
}}
/>
{/* Main Chip */}
<Chip
icon={<IconComponent sx={{ fontSize: 14 }} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ fontWeight: 600, fontSize: '0.7rem' }}>
{chip.label}
</Typography>
{chip.value && (
<Box
sx={{
backgroundColor: 'rgba(255,255,255,0.9)',
color: chip.color,
borderRadius: '50%',
width: 16,
height: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.6rem',
fontWeight: 700,
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
}}
>
{chip.value}
</Box>
)}
</Box>
}
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)`
}
}}
/>
</Box>
</Tooltip>
);
};
// 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 (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: index * 0.1 }}
whileHover={{ y: -5, scale: 1.02 }}
>
<Paper
elevation={8}
sx={{
height: isHovered ? 280 : 120, // Dynamic height based on hover state
background: pillar.gradient,
color: 'white',
cursor: 'pointer',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
position: 'relative',
overflow: 'hidden',
// Large tick when pillar tasks complete (uses live store counts)
'&::after': {
content: doneForPillar > 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)}
>
<CardContent sx={{ p: 2, height: '100%', display: 'flex', flexDirection: 'column' }}>
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5, position: 'relative' }}>
<Box
sx={{
p: 0.8,
borderRadius: '50%',
backgroundColor: 'rgba(255,255,255,0.2)',
mr: 1.2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<IconComponent sx={{ fontSize: 18, color: 'white' }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1rem' }}>
{pillar.title}
</Typography>
{/* Pillar task count badge */}
<Box sx={{ ml: 1, position: 'relative' }}>
<Box
sx={{
backgroundColor: 'rgba(255,255,255,0.9)',
color: pillar.color,
borderRadius: '12px',
px: 0.75,
py: 0.1,
fontSize: '0.65rem',
fontWeight: 800,
boxShadow: '0 2px 6px rgba(0,0,0,0.2)'
}}
>
{totalForPillar}
</Box>
</Box>
{/* More Options Indicator */}
{!isHovered && (
<motion.div
animate={{ opacity: [0.5, 1, 0.5] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
style={{ marginLeft: 'auto' }}
>
<Typography variant="caption" sx={{ fontSize: '0.6rem', opacity: 0.7 }}>
</Typography>
</motion.div>
)}
</Box>
{/* Chips Layout with Progressive Disclosure */}
{pillar.id === 'generate' ? (
<GeneratePillarChips index={index} isHovered={isHovered} />
) : pillar.id === 'publish' ? (
<PublishPillarChips isHovered={isHovered} pillarColor={pillar.color} />
) : pillar.id === 'analyze' ? (
<AnalyzePillarChips isHovered={isHovered} pillarColor={pillar.color} />
) : pillar.id === 'engage' ? (
<EngagePillarChips isHovered={isHovered} pillarColor={pillar.color} />
) : (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 1,
flexGrow: 1,
justifyContent: isHovered ? 'center' : 'flex-start'
}}
>
{/* Today Chip - Always Visible */}
<EnhancedTodayChip
pillarId={pillar.id}
pillarTitle={pillar.title}
pillarColor={pillar.color}
tasks={pillar.todayTasks}
delay={index * 5}
/>
{/* Additional Chips - Progressive Disclosure */}
<AnimatePresence>
{isHovered && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
style={{ overflow: 'hidden' }}
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 1 }}>
{pillar.id === 'plan' ? (
<>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<ChipWithTooltip chip={pillar.chips.onboarding} delay={index * 5 + 1} onOnboardingClick={onOnboardingClick} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<ChipWithTooltip chip={pillar.chips.strategy} delay={index * 5 + 2} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<ChipWithTooltip chip={pillar.chips.calendar} delay={index * 5 + 3} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<ChipWithTooltip chip={pillar.chips.review} delay={index * 5 + 4} />
</motion.div>
</>
) : pillar.id === 'remarket' ? (
<>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<ChipWithTooltip chip={pillar.chips.good} delay={index * 5 + 1} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<ChipWithTooltip chip={pillar.chips.bad} delay={index * 5 + 2} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<ChipWithTooltip chip={pillar.chips.ugly} delay={index * 5 + 3} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<ChipWithTooltip chip={pillar.chips.review} delay={index * 5 + 4} />
</motion.div>
</>
) : null}
</Box>
</motion.div>
)}
</AnimatePresence>
</Box>
)}
</CardContent>
</Paper>
</motion.div>
);
};
// 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 (
<>
<Box
sx={{
py: 3,
background: 'linear-gradient(135deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.02) 100%)',
backdropFilter: 'blur(8px)',
borderRadius: 2,
mb: 4
}}
>
<Container maxWidth="xl">
{/* Pillars Grid */}
<Box
sx={{
display: 'grid',
gridTemplateColumns: {
xs: 'repeat(2, 1fr)',
sm: 'repeat(3, 1fr)',
md: 'repeat(6, 1fr)'
},
gap: 2,
overflow: 'visible'
}}
>
{pillarData.map((pillar, index) => (
<PillarCard
key={pillar.id}
pillar={pillar}
index={index}
onOnboardingClick={handleOnboardingClick}
/>
))}
</Box>
</Container>
</Box>
{/* Onboarding Modal */}
<OnboardingModal
open={onboardingModalOpen}
onClose={handleCloseModal}
/>
</>
);
};
export default ContentLifecyclePillars;

View File

@@ -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 */}
<DashboardHeader
title="🚀 Alwrity Content Hub"
subtitle="Your AI-powered content creation suite"
title="Alwrity Content Hub"
subtitle=""
statusChips={[]}
rightContent={<SystemStatusIndicator />}
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 */}
<ContentLifecyclePillars />
{/* Search and Filter */}
<SearchFilter
searchQuery={searchQuery}
@@ -149,12 +267,14 @@ const MainDashboard: React.FC = () => {
transition={{ duration: 0.5, delay: categoryIndex * 0.1 }}
>
<Box sx={{ mb: 5 }}>
{/* Category Header */}
<CategoryHeader
categoryName={categoryName}
category={category}
theme={theme}
/>
{/* Only show Category Header when no specific category is selected (showing all tools) */}
{selectedCategory === null && (
<CategoryHeader
categoryName={categoryName}
category={category}
theme={theme}
/>
)}
<Grid container spacing={3}>
{getToolsForCategory(category, selectedSubCategory).map((tool: Tool, toolIndex: number) => (

View File

@@ -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<AnalyzePillarChipsProps> = ({
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 (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, width: '100%' }}>
{/* Today Chip - Always visible */}
<EnhancedTodayChip
pillarId="analyze"
pillarTitle="Analyze"
pillarColor={pillarColor}
tasks={todayTasks}
delay={0}
/>
{/* Progressive disclosure chips */}
<AnimatePresence>
{isHovered && (
<>
{/* Plan Dashboard Chip */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<Chip
icon={<Dashboard sx={{ fontSize: 16 }} />}
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)',
}}
/>
</motion.div>
{/* Disabled Analytics Chips */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<Chip
icon={<LinkedIn sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<Chip
icon={<Facebook sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<Chip
icon={<Twitter sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.5 }}
>
<Chip
icon={<Web sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
</>
)}
</AnimatePresence>
{/* Ellipsis indicator when not hovered */}
{!isHovered && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'rgba(255, 255, 255, 0.6)',
fontSize: '1.2rem',
animation: 'pulse 2s infinite',
'@keyframes pulse': {
'0%, 100%': { opacity: 0.6 },
'50%': { opacity: 1 },
},
}}
>
</Box>
)}
</Box>
);
};
export default AnalyzePillarChips;

View File

@@ -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<EngagePillarChipsProps> = ({
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 (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, width: '100%' }}>
{/* Today Chip - Always visible */}
<EnhancedTodayChip
pillarId="engage"
pillarTitle="Engage"
pillarColor={pillarColor}
tasks={todayTasks}
delay={0}
/>
{/* Progressive disclosure chips */}
<AnimatePresence>
{isHovered && (
<>
{/* Disabled Engagement Chips */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<Chip
icon={<LinkedIn sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<Chip
icon={<Facebook sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<Chip
icon={<Groups sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<Chip
icon={<Chat sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.5 }}
>
<Chip
icon={<Forum sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
</>
)}
</AnimatePresence>
{/* Ellipsis indicator when not hovered */}
{!isHovered && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'rgba(255, 255, 255, 0.6)',
fontSize: '1.2rem',
animation: 'pulse 2s infinite',
'@keyframes pulse': {
'0%, 100%': { opacity: 0.6 },
'50%': { opacity: 1 },
},
}}
>
</Box>
)}
</Box>
);
};
export default EngagePillarChips;

View File

@@ -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<EnhancedTodayChipProps> = ({
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 (
<>
<motion.div
animate={shouldShake && !isPillarComplete ? { x: [-2, 2, -2, 2, 0] } : {}}
transition={{ duration: 0.6 }}
>
<Tooltip title={`🎯 Today's ${pillarTitle} Tasks - Click to View!`} arrow>
<Box
onClick={handleClick}
data-pillar-id={pillarId}
sx={{
position: 'relative',
cursor: 'pointer',
'&:hover': {
transform: 'translateY(-2px) scale(1.05)',
'&::before': {
opacity: 1,
transform: 'translateX(0)'
}
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `linear-gradient(45deg, transparent 30%, ${pillarColor}20 50%, transparent 70%)`,
opacity: 0,
transform: 'translateX(-100%)',
transition: 'all 0.6s ease',
borderRadius: 'inherit',
zIndex: 1
}
}}
>
<Chip
icon={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<TodayIcon sx={{
fontSize: 16,
'@keyframes rotate': {
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' }
},
animation: 'rotate 3s linear infinite'
}} />
<motion.span
animate={{ scale: [1, 1.2, 1] }}
transition={{ duration: 1, repeat: Infinity }}
>
</motion.span>
</Box>
}
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 && (
<Box
sx={{
position: 'absolute',
top: -4,
right: -4,
width: 16,
height: 16,
borderRadius: '50%',
background: pillarProgress === 100 ? '#4CAF50' : pillarColor,
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.6rem',
fontWeight: 700,
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
border: '2px solid white'
}}
>
{pillarProgress === 100 ? '✓' : Math.round(pillarProgress)}
</Box>
)}
</Box>
</Tooltip>
</motion.div>
{/* Enhanced Modal */}
<EnhancedTodayModal
open={modalOpen}
onClose={handleCloseModal}
pillarId={pillarId}
pillarTitle={pillarTitle}
pillarColor={pillarColor}
tasks={liveTasks}
onPreventAutoReopen={() => setUserManuallyClosed(true)}
/>
</>
);
};
export default EnhancedTodayChip;

View File

@@ -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<EnhancedTodayModalProps> = ({
open,
onClose,
pillarId,
pillarTitle,
pillarColor,
tasks,
onPreventAutoReopen
}) => {
const navigate = useNavigate();
const {
currentWorkflow,
workflowProgress,
navigationState,
completeTask,
skipTask,
moveToNextTask,
isLoading,
isWorkflowComplete
} = useWorkflowStore();
const [selectedTask, setSelectedTask] = useState<TodayTask | null>(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 (
<Modal
open={open}
onClose={onClose}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
p: { xs: 1.5, md: 3 }
}}
>
<Paper
sx={{
width: { xs: '96vw', sm: '94vw', md: '90vw' },
maxWidth: 1200,
maxHeight: '92vh',
overflow: 'auto',
background: 'linear-gradient(135deg, rgba(255,255,255,0.96) 0%, rgba(250,250,252,0.92) 100%)',
backdropFilter: 'blur(24px)',
borderRadius: 4,
boxShadow: '0 30px 60px rgba(0,0,0,0.35)',
border: '1px solid rgba(0,0,0,0.06)'
}}
>
{/* Header */}
<Box sx={{ p: { xs: 2, md: 3 }, borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar
sx={{
background: pillarColor,
width: 48,
height: 48
}}
>
<TodayIcon sx={{ fontSize: 24, color: 'white' }} />
</Avatar>
<Box>
<Typography variant="h5" sx={{ fontWeight: 800, color: '#23252F', letterSpacing: 0.2 }}>
Today's {pillarTitle} Tasks
</Typography>
<Typography variant="body2" sx={{ color: '#5A5F6A' }}>
Complete your daily marketing workflow
</Typography>
</Box>
</Box>
<IconButton onClick={onClose} sx={{ color: '#6B7280' }}>
<CloseIcon />
</IconButton>
</Box>
</Box>
{/* Workflow Progress - Circular in Header */}
{workflowProgress && (
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
p: { xs: 2, md: 3 },
borderBottom: '1px solid rgba(0,0,0,0.08)'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography variant="body2" sx={{ color: '#5A5F6A', fontWeight: 600 }}>
Overall Progress
</Typography>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={workflowProgress.completionPercentage}
size={40}
thickness={4}
sx={{
color: pillarColor,
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography
variant="caption"
component="div"
sx={{
color: '#5A5F6A',
fontWeight: 700,
fontSize: '0.7rem'
}}
>
{`${Math.round(workflowProgress.completionPercentage)}%`}
</Typography>
</Box>
</Box>
</Box>
<Typography variant="body2" sx={{ color: '#5A5F6A', fontWeight: 600 }}>
{workflowProgress.completedTasks} of {workflowProgress.totalTasks} tasks
</Typography>
</Box>
)}
{/* Tasks List */}
<Box sx={{ p: { xs: 2, md: 3 } }}>
<Typography variant="h6" sx={{ mb: 2, color: '#23252F', fontWeight: 800 }}>
{pillarTitle} Tasks
</Typography>
<Stack spacing={2}>
{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 (
<motion.div
key={task.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: index * 0.1 }}
>
<Card
sx={{
border: isCurrentTask ? `2px solid ${pillarColor}` : '1px solid rgba(0,0,0,0.08)',
background: isCurrentTask ? `${pillarColor}12` : 'white',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 20px rgba(0,0,0,0.08)'
}
}}
>
<CardContent sx={{ p: { xs: 2, md: 2.5 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 1 }}>
{IconComponent && (
<Avatar
sx={{
background: statusColor,
width: 36,
height: 36
}}
>
<IconComponent sx={{ fontSize: 18, color: 'white' }} />
</Avatar>
)}
<Box sx={{ flexGrow: 1 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#23252F' }}>
{task.title}
</Typography>
<Typography variant="body2" sx={{ color: '#5A5F6A' }}>
{task.description}
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip
label={status}
size="small"
sx={{
background: `${statusColor}18`,
color: statusColor,
border: `1px solid ${statusColor}40`,
textTransform: 'capitalize'
}}
/>
<Typography variant="caption" sx={{ color: '#999' }}>
{task.estimatedTime} min
</Typography>
</Box>
</Box>
{/* Task Actions */}
<Box sx={{ display: 'flex', gap: 1.25, mt: 2 }}>
{status === 'pending' && task.enabled && (
<Button
variant="contained"
size="small"
startIcon={<AlwrityIcon />}
onClick={() => handleTaskAction(task)}
disabled={isLoading}
sx={{
background: pillarColor,
'&:hover': {
background: pillarColor,
opacity: 0.9
}
}}
>
ALwrity it
</Button>
)}
{status === 'active' && (
<Button
variant="outlined"
size="small"
startIcon={<PlayIcon />}
onClick={() => handleTaskAction(task)}
disabled={isLoading}
sx={{ borderColor: pillarColor, color: pillarColor }}
>
Continue
</Button>
)}
{status === 'completed' && (
<Button
variant="outlined"
size="small"
startIcon={<CheckIcon />}
disabled
sx={{ borderColor: '#4CAF50', color: '#4CAF50' }}
>
Completed
</Button>
)}
{status === 'pending' && (
<Button
variant="text"
size="small"
startIcon={<SkipIcon />}
onClick={() => handleSkipTask(task)}
disabled={isLoading}
sx={{ color: '#FF9800' }}
>
Skip
</Button>
)}
</Box>
</CardContent>
</Card>
</motion.div>
);
})}
</Stack>
</Box>
{/* Footer Actions */}
<Box sx={{ p: { xs: 2, md: 3 }, borderTop: '1px solid rgba(0,0,0,0.08)' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2" sx={{ color: '#5A5F6A' }}>
{isComplete ? '🎉 All tasks completed!' : `${pillarTasks.length} tasks in this pillar`}
</Typography>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
{/* Next button for all pillars except the last one */}
{!isLastPillar && (
<>
<Button variant="outlined" onClick={onClose}>
Close
</Button>
<Tooltip
title={areAllTasksCompleted
? `All tasks completed! Click to proceed to ${nextPillarId ? nextPillarId.charAt(0).toUpperCase() + nextPillarId.slice(1) : 'next'} pillar`
: "Complete or skip all tasks in this pillar to proceed"
}
arrow
>
<span>
<Button
variant="contained"
startIcon={<NavigateNext />}
onClick={handleNextPillar}
disabled={!areAllTasksCompleted || isLoading}
sx={{
background: pillarColor,
'&:hover': {
background: pillarColor,
opacity: 0.9
},
'&:disabled': {
background: '#ccc',
color: '#666'
}
}}
>
Next
</Button>
</span>
</Tooltip>
</>
)}
{/* Last pillar (Remarket) - Workflow Complete button acts as close */}
{isLastPillar && (
<Button
variant="contained"
startIcon={<CheckIcon />}
onClick={handleWorkflowComplete}
sx={{
background: '#4CAF50',
'&:hover': {
background: '#45a049',
opacity: 0.9
}
}}
>
Workflow Complete!
</Button>
)}
</Box>
</Box>
</Box>
</Paper>
</Modal>
);
};
export default EnhancedTodayModal;

View File

@@ -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 (
<Modal
open={open}
onClose={onClose}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
p: 2
}}
>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ duration: 0.3 }}
>
<Paper
elevation={24}
sx={{
width: { xs: '95%', sm: '90%', md: '600px' },
maxHeight: '80vh',
overflow: 'auto',
borderRadius: 3,
background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
position: 'relative'
}}
>
{/* Header */}
<Box
sx={{
p: 3,
background: 'linear-gradient(135deg, #2196F3 0%, #1565C0 100%)',
color: 'white',
borderRadius: '12px 12px 0 0',
position: 'relative'
}}
>
<IconButton
onClick={onClose}
sx={{
position: 'absolute',
top: 16,
right: 16,
color: 'white',
backgroundColor: 'rgba(255,255,255,0.1)',
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.2)'
}
}}
>
<CloseIcon />
</IconButton>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Avatar
sx={{
backgroundColor: 'rgba(255,255,255,0.2)',
mr: 2,
width: 48,
height: 48
}}
>
<TodayIcon sx={{ fontSize: 24 }} />
</Avatar>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 0.5 }}>
Today's Tasks
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
AI-powered content generation for today
</Typography>
</Box>
</Box>
</Box>
{/* Content */}
<Box sx={{ p: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, color: '#1565C0' }}>
🚀 Ready to Generate Content
</Typography>
<Stack spacing={2}>
{tasks.map((task, index) => {
const IconComponent = task.icon;
return (
<motion.div
key={task.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, delay: index * 0.1 }}
>
<Paper
elevation={2}
sx={{
p: 2.5,
borderRadius: 2,
border: `2px solid ${task.enabled ? task.color : '#E0E0E0'}`,
backgroundColor: task.enabled ? 'white' : '#F5F5F5',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: `0 8px 24px ${task.color}20`
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Avatar
sx={{
backgroundColor: task.enabled ? task.color : '#BDBDBD',
width: 40,
height: 40
}}
>
<IconComponent sx={{ fontSize: 20, color: 'white' }} />
</Avatar>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 1 }}>
{task.title}
</Typography>
<Chip
label={task.platform}
size="small"
sx={{
backgroundColor: task.enabled ? `${task.color}20` : '#E0E0E0',
color: task.enabled ? task.color : '#757575',
fontWeight: 500
}}
/>
</Box>
<Button
variant="contained"
startIcon={<AlwrityIcon />}
onClick={task.action}
disabled={!task.enabled}
sx={{
background: task.enabled
? `linear-gradient(135deg, ${task.color} 0%, ${task.color}CC 100%)`
: '#E0E0E0',
color: 'white',
px: 3,
py: 1,
borderRadius: 2,
fontWeight: 600,
textTransform: 'none',
boxShadow: task.enabled
? `0 4px 12px ${task.color}40`
: 'none',
'&:hover': task.enabled ? {
background: `linear-gradient(135deg, ${task.color}CC 0%, ${task.color} 100%)`,
boxShadow: `0 6px 16px ${task.color}50`
} : {},
'&:disabled': {
backgroundColor: '#E0E0E0',
color: '#9E9E9E'
}
}}
>
ALwrity it
</Button>
</Box>
</Paper>
</motion.div>
);
})}
</Stack>
<Divider sx={{ my: 3 }} />
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
💡 Tip: Use ALwrity's AI to generate engaging content tailored to each platform
</Typography>
</Box>
</Box>
</Paper>
</motion.div>
</Modal>
);
};
// 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 (
<Tooltip
title={
chip.bubbles && chip.bubbles.length > 0 ? (
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
{chip.label}
</Typography>
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
>
<Typography variant="caption" sx={{ color: 'white' }}>
{chip.bubbles[currentIndex]}
</Typography>
</motion.div>
</AnimatePresence>
</Box>
) : chip.label
}
arrow
placement="top"
>
<Box
sx={{
position: 'relative',
cursor: 'pointer',
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
'&:hover': {
transform: 'translateY(-4px) scale(1.05)',
'& .chip-glow': {
opacity: 1,
transform: 'scale(1.2)'
},
'& .chip-shadow': {
opacity: 0.6,
transform: 'translateY(8px) scale(1.1)'
}
}
}}
onClick={handleClick}
>
{/* Glow Effect */}
<Box
className="chip-glow"
sx={{
position: 'absolute',
top: -8,
left: -8,
right: -8,
bottom: -8,
background: chip.gradient || chip.color,
borderRadius: '20px',
opacity: 0,
transition: 'all 0.4s ease',
filter: 'blur(12px)',
zIndex: -2
}}
/>
{/* Shadow Effect */}
<Box
className="chip-shadow"
sx={{
position: 'absolute',
top: 4,
left: 2,
right: -2,
bottom: -4,
background: 'rgba(0,0,0,0.3)',
borderRadius: '16px',
opacity: 0.3,
transition: 'all 0.4s ease',
filter: 'blur(8px)',
zIndex: -1
}}
/>
{/* Main Chip */}
<Chip
icon={<IconComponent sx={{ fontSize: 14 }} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ fontWeight: 600, fontSize: '0.7rem' }}>
{chip.label}
</Typography>
{chip.value && (
<Box
sx={{
backgroundColor: 'rgba(255,255,255,0.9)',
color: chip.color,
borderRadius: '50%',
width: 16,
height: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.6rem',
fontWeight: 700,
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
}}
>
{chip.value}
</Box>
)}
</Box>
}
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)`
}
}}
/>
</Box>
</Tooltip>
);
};
// 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 (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 1,
flexGrow: 1,
justifyContent: isHovered ? 'center' : 'flex-start'
}}
>
{/* Today Chip - Always Visible */}
<EnhancedTodayChip
pillarId="generate"
pillarTitle="Generate"
pillarColor="#1565C0"
tasks={generateTodayTasks}
delay={index * 5}
/>
{/* More Options Indicator */}
{!isHovered && (
<motion.div
animate={{ opacity: [0.5, 1, 0.5] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
style={{ alignSelf: 'center', marginTop: '8px' }}
>
<Typography variant="caption" sx={{ fontSize: '0.6rem', opacity: 0.7, color: 'white' }}>
</Typography>
</motion.div>
)}
{/* Content Type Chips - Progressive Disclosure */}
<AnimatePresence>
{isHovered && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
style={{ overflow: 'hidden' }}
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 1 }}>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<GenerateChip chip={generateChips.text} delay={index * 5 + 1} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<GenerateChip chip={generateChips.image} delay={index * 5 + 2} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<GenerateChip chip={generateChips.audio} delay={index * 5 + 3} />
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<GenerateChip chip={generateChips.video} delay={index * 5 + 4} />
</motion.div>
</Box>
</motion.div>
)}
</AnimatePresence>
</Box>
);
};
export default GeneratePillarChips;

View File

@@ -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 (
<Modal
open={open}
onClose={onClose}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
p: 2
}}
>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ duration: 0.3 }}
>
<Paper
elevation={24}
sx={{
width: { xs: '95%', sm: '90%', md: '80%', lg: '70%' },
maxWidth: 800,
maxHeight: '90vh',
overflow: 'auto',
borderRadius: 3,
background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
position: 'relative'
}}
>
{/* Header */}
<Box
sx={{
p: 3,
background: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)',
color: 'white',
borderRadius: '12px 12px 0 0',
position: 'relative'
}}
>
<IconButton
onClick={onClose}
sx={{
position: 'absolute',
top: 16,
right: 16,
color: 'white',
backgroundColor: 'rgba(255,255,255,0.1)',
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.2)'
}
}}
>
<CloseIcon />
</IconButton>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Avatar
sx={{
backgroundColor: 'rgba(255,255,255,0.2)',
mr: 2,
width: 48,
height: 48
}}
>
<OnboardingIcon sx={{ fontSize: 24 }} />
</Avatar>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 0.5 }}>
Onboarding Status
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9 }}>
Complete your setup to unlock full potential
</Typography>
</Box>
</Box>
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
Overall Progress
</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{onboardingData.userProfile.completion}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={onboardingData.userProfile.completion}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(255,255,255,0.2)',
'& .MuiLinearProgress-bar': {
backgroundColor: 'white',
borderRadius: 4
}
}}
/>
</Box>
</Box>
{/* Content */}
<Box sx={{ p: 3 }}>
{/* User Profile Section */}
<Box sx={{ mb: 4 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, color: '#2E7D32' }}>
👤 User Profile
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: 'repeat(2, 1fr)' }, gap: 2 }}>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Name</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.userProfile.name}</Typography>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Company</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.userProfile.company}</Typography>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Role</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.userProfile.role}</Typography>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Completion</Typography>
<Typography variant="body1" sx={{ fontWeight: 600, color: '#4CAF50' }}>{onboardingData.userProfile.completion}%</Typography>
</Box>
</Box>
</Box>
{/* Preferences Section */}
<Box sx={{ mb: 4 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, color: '#2E7D32' }}>
Preferences
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: 'repeat(2, 1fr)' }, gap: 2 }}>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>Content Types</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap">
{onboardingData.preferences.contentTypes.map((type, idx) => (
<Chip key={idx} label={type} size="small" sx={{ backgroundColor: '#E3F2FD', color: '#1976D2' }} />
))}
</Stack>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>Platforms</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap">
{onboardingData.preferences.platforms.map((platform, idx) => (
<Chip key={idx} label={platform} size="small" sx={{ backgroundColor: '#E8F5E8', color: '#2E7D32' }} />
))}
</Stack>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Tone</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.preferences.tone}</Typography>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Frequency</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.preferences.frequency}</Typography>
</Box>
</Box>
</Box>
{/* Goals Section */}
<Box sx={{ mb: 4 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, color: '#2E7D32' }}>
🎯 Goals
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: 'repeat(2, 1fr)' }, gap: 2 }}>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Primary Goal</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.goals.primary}</Typography>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 0.5 }}>Secondary Goal</Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}>{onboardingData.goals.secondary}</Typography>
</Box>
<Box sx={{ p: 2, backgroundColor: 'white', borderRadius: 2, boxShadow: 1, gridColumn: { xs: '1', md: '1 / -1' } }}>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>Key Metrics</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap">
{onboardingData.goals.metrics.map((metric, idx) => (
<Chip key={idx} label={metric} size="small" sx={{ backgroundColor: '#FFF3E0', color: '#F57C00' }} />
))}
</Stack>
</Box>
</Box>
</Box>
{/* AI Analysis Section */}
<Box sx={{ mb: 4 }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, color: '#2E7D32' }}>
🤖 AI Analysis
</Typography>
<Box sx={{ p: 3, backgroundColor: 'white', borderRadius: 2, boxShadow: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<PsychologyIcon sx={{ color: '#9C27B0', mr: 1 }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Analysis Score: {onboardingData.aiAnalysis.score}/10
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: '#2E7D32' }}>
Key Insights:
</Typography>
{onboardingData.aiAnalysis.insights.map((insight, idx) => (
<Box key={idx} sx={{ display: 'flex', alignItems: 'flex-start', mb: 1 }}>
<CheckIcon sx={{ color: '#4CAF50', fontSize: 16, mr: 1, mt: 0.5 }} />
<Typography variant="body2">{insight}</Typography>
</Box>
))}
</Box>
<Box>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: '#2E7D32' }}>
Recommendations:
</Typography>
{onboardingData.aiAnalysis.recommendations.map((rec, idx) => (
<Box key={idx} sx={{ display: 'flex', alignItems: 'flex-start', mb: 1 }}>
<TrendingUpIcon sx={{ color: '#FF9800', fontSize: 16, mr: 1, mt: 0.5 }} />
<Typography variant="body2">{rec}</Typography>
</Box>
))}
</Box>
</Box>
</Box>
{/* Settings Button */}
<Divider sx={{ my: 3 }} />
<Box sx={{ textAlign: 'center' }}>
<Button
variant="contained"
startIcon={<SettingsIcon />}
sx={{
background: 'linear-gradient(135deg, #9C27B0 0%, #6A1B9A 100%)',
color: 'white',
px: 4,
py: 1.5,
borderRadius: 2,
fontWeight: 600,
boxShadow: '0 4px 12px rgba(156, 39, 176, 0.3)',
'&:hover': {
background: 'linear-gradient(135deg, #6A1B9A 0%, #4A148C 100%)',
boxShadow: '0 6px 16px rgba(156, 39, 176, 0.4)'
}
}}
>
Edit Onboarding Data
</Button>
<Typography variant="caption" sx={{ display: 'block', mt: 1, color: 'text.secondary' }}>
Configure your preferences and goals in the Settings page
</Typography>
</Box>
</Box>
</Paper>
</motion.div>
</Modal>
);
};
export default OnboardingModal;

View File

@@ -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<any>;
color: string;
gradient: string;
bubbles: string[];
value?: number | null;
}
// Define the pillar data interface
export interface PillarData {
id: string;
title: string;
icon: React.ComponentType<any>;
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;

View File

@@ -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<PublishPillarChipsProps> = ({
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 (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, width: '100%' }}>
{/* Today Chip - Always visible */}
<EnhancedTodayChip
pillarId="publish"
pillarTitle="Publish"
pillarColor={pillarColor}
tasks={todayTasks}
delay={0}
/>
{/* Progressive disclosure chips */}
<AnimatePresence>
{isHovered && (
<>
{/* Reviewed Chip */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.1 }}
style={{ position: 'relative' }}
>
<Chip
icon={<CheckCircle sx={{ fontSize: 16 }} />}
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)',
}}
/>
<Box
sx={{
position: 'absolute',
top: -8,
right: -8,
width: 20,
height: 20,
borderRadius: '50%',
background: 'linear-gradient(135deg, #FF6B35 0%, #F7931E 100%)',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.7rem',
fontWeight: 700,
boxShadow: '0 2px 8px rgba(255, 107, 53, 0.4)',
border: '2px solid white',
}}
>
3
</Box>
</motion.div>
{/* Facebook Chip */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<Chip
icon={<Facebook sx={{ fontSize: 16 }} />}
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)',
}}
/>
</motion.div>
{/* LinkedIn Chip */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<Chip
icon={<LinkedIn sx={{ fontSize: 16 }} />}
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)',
}}
/>
</motion.div>
{/* Disabled Social Media Chips */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<Chip
icon={<Twitter sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.5 }}
>
<Chip
icon={<Instagram sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.6 }}
>
<Chip
icon={<YouTube sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3, delay: 0.7 }}
>
<Chip
icon={<Article sx={{ fontSize: 16 }} />}
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,
}}
/>
</motion.div>
</>
)}
</AnimatePresence>
{/* Ellipsis indicator when not hovered */}
{!isHovered && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'rgba(255, 255, 255, 0.6)',
fontSize: '1.2rem',
animation: 'pulse 2s infinite',
'@keyframes pulse': {
'0%, 100%': { opacity: 0.6 },
'50%': { opacity: 1 },
},
}}
>
</Box>
)}
</Box>
);
};
export default PublishPillarChips;

View File

@@ -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<TaskNavigationControlsProps> = ({
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<string | null>(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 (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Card
sx={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 2
}}
>
<CardContent sx={{ p: compact ? 2 : 3 }}>
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<NavigationIcon sx={{ color: theme.palette.primary.main }} />
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
Task Navigation
</Typography>
{navigationError && (
<Chip
label={navigationError}
color="error"
size="small"
sx={{ ml: 'auto' }}
/>
)}
</Box>
{/* Current Task Info */}
{showTaskInfo && currentTask && (
<Box sx={{ mb: 3 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)', mb: 1 }}>
Current Task:
</Typography>
<Box
sx={{
background: 'rgba(255,255,255,0.05)',
borderRadius: 1,
p: 2,
border: '1px solid rgba(255,255,255,0.1)'
}}
>
<Typography variant="subtitle1" sx={{ color: 'white', fontWeight: 600, mb: 0.5 }}>
{currentTask.title}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
{currentTask.description}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={currentTask.pillarId}
size="small"
sx={{
background: `${currentTask.color}20`,
color: currentTask.color,
border: `1px solid ${currentTask.color}40`
}}
/>
<Chip
label={`${currentTask.estimatedTime} min`}
size="small"
sx={{
background: 'rgba(255,255,255,0.1)',
color: 'rgba(255,255,255,0.8)'
}}
/>
</Box>
</Box>
</Box>
)}
{/* Navigation Controls */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
{/* Back Button */}
<Tooltip title="Go to Previous Task">
<IconButton
onClick={handleNavigateBack}
disabled={!navigationState.canGoBack || isLoading || isNavigating}
sx={{
background: 'rgba(255,255,255,0.1)',
color: 'white',
'&:hover': {
background: 'rgba(255,255,255,0.2)'
},
'&:disabled': {
background: 'rgba(255,255,255,0.05)',
color: 'rgba(255,255,255,0.3)'
}
}}
>
<BackIcon />
</IconButton>
</Tooltip>
{/* Complete Task Button */}
<Button
variant="contained"
startIcon={<CompleteIcon />}
onClick={handleCompleteCurrentTask}
disabled={!currentTask || isLoading}
sx={{
background: '#4CAF50',
'&:hover': {
background: '#45a049'
},
flexGrow: 1
}}
>
Complete Task
</Button>
{/* Skip Task Button */}
<Tooltip title="Skip Current Task">
<IconButton
onClick={handleSkipCurrentTask}
disabled={!currentTask || isLoading}
sx={{
background: 'rgba(255,152,0,0.2)',
color: '#FF9800',
'&:hover': {
background: 'rgba(255,152,0,0.3)'
}
}}
>
<SkipIcon />
</IconButton>
</Tooltip>
{/* Forward Button */}
<Tooltip title="Go to Next Task">
<IconButton
onClick={handleNavigateToNext}
disabled={!navigationState.canGoForward || isLoading || isNavigating}
sx={{
background: 'rgba(255,255,255,0.1)',
color: 'white',
'&:hover': {
background: 'rgba(255,255,255,0.2)'
},
'&:disabled': {
background: 'rgba(255,255,255,0.05)',
color: 'rgba(255,255,255,0.3)'
}
}}
>
<ForwardIcon />
</IconButton>
</Tooltip>
</Box>
{/* Task Status Summary */}
{!compact && (
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Chip
label={`${readyTasks.length} Ready`}
size="small"
sx={{
background: 'rgba(76,175,80,0.2)',
color: '#4CAF50',
border: '1px solid rgba(76,175,80,0.3)'
}}
/>
<Chip
label={`${blockedTasks.length} Blocked`}
size="small"
sx={{
background: 'rgba(244,67,54,0.2)',
color: '#F44336',
border: '1px solid rgba(244,67,54,0.3)'
}}
/>
<Chip
label={`${currentWorkflow.completedTasks}/${currentWorkflow.totalTasks} Complete`}
size="small"
sx={{
background: 'rgba(33,150,243,0.2)',
color: '#2196F3',
border: '1px solid rgba(33,150,243,0.3)'
}}
/>
</Box>
)}
{/* Loading State */}
<AnimatePresence>
{(isLoading || isNavigating) && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 'inherit'
}}
>
<Typography variant="body2" sx={{ color: 'white' }}>
{isNavigating ? 'Navigating...' : 'Loading...'}
</Typography>
</motion.div>
)}
</AnimatePresence>
</CardContent>
</Card>
</motion.div>
);
};
export default TaskNavigationControls;

View File

@@ -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<WorkflowDemoProps> = ({ 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 (
<Card sx={{
background: 'linear-gradient(135deg, rgba(25, 118, 210, 0.1) 0%, rgba(25, 118, 210, 0.05) 100%)',
border: '1px solid rgba(25, 118, 210, 0.2)',
borderRadius: 2,
mb: 2
}}>
<CardContent sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Schedule color="primary" />
<Typography variant="h6" color="primary">
Today's Workflow
</Typography>
{hasWorkflow && (
<Chip
label={isComplete ? 'Complete' : isInProgress ? 'In Progress' : 'Ready'}
color={isComplete ? 'success' : isInProgress ? 'primary' : 'default'}
size="small"
/>
)}
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
{!hasWorkflow && (
<Button
variant="contained"
size="small"
startIcon={<PlayArrow />}
onClick={handleGenerateWorkflow}
disabled={isLoading}
>
Generate
</Button>
)}
{hasWorkflow && !isInProgress && !isComplete && (
<Button
variant="contained"
size="small"
startIcon={<PlayArrow />}
onClick={handleStartWorkflow}
disabled={isLoading}
>
Start
</Button>
)}
<IconButton
size="small"
onClick={() => setExpanded(!expanded)}
>
{expanded ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</Box>
</Box>
<Collapse in={expanded}>
<Box sx={{ mt: 2 }}>
{workflowProgress && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Progress: {workflowProgress.completedTasks} of {workflowProgress.totalTasks} tasks
</Typography>
</Box>
)}
{currentWorkflow && (
<Stack spacing={1}>
{currentWorkflow.tasks.slice(0, 3).map((task) => (
<Box
key={task.id}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
p: 1,
background: 'rgba(255, 255, 255, 0.05)',
borderRadius: 1,
border: '1px solid rgba(255, 255, 255, 0.1)'
}}
>
<Box>
<Typography variant="body2" fontWeight="medium">
{task.title}
</Typography>
<Typography variant="caption" color="text.secondary">
{task.estimatedTime} min • {task.priority}
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 0.5 }}>
{task.status === 'pending' && isInProgress && (
<>
<Tooltip title="Complete Task">
<IconButton
size="small"
onClick={() => handleCompleteTask(task.id)}
sx={{ color: 'success.main' }}
>
<CheckCircle fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Skip Task">
<IconButton
size="small"
onClick={() => handleSkipTask(task.id)}
sx={{ color: 'warning.main' }}
>
<Stop fontSize="small" />
</IconButton>
</Tooltip>
</>
)}
{task.status === 'completed' && (
<CheckCircle color="success" fontSize="small" />
)}
{task.status === 'skipped' && (
<Stop color="warning" fontSize="small" />
)}
</Box>
</Box>
))}
</Stack>
)}
</Box>
</Collapse>
</CardContent>
</Card>
);
}
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 (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Box
sx={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
borderRadius: 3,
p: 3,
border: '1px solid rgba(255,255,255,0.1)',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
mb: 3,
overflow: 'hidden'
}}
>
{/* Header Section */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography
variant="h5"
sx={{
fontWeight: 700,
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 1
}}
>
{isComplete ? <CheckCircle sx={{ color: 'success.main' }} /> :
isInProgress ? <TrendingUp sx={{ color: 'primary.main' }} /> :
<Schedule sx={{ color: 'grey.400' }} />}
Today's Marketing Workflow
</Typography>
<Chip
label={getStatusText()}
size="small"
color={getStatusColor()}
sx={{
background: `${getStatusColor() === 'success' ? 'success.main' : getStatusColor() === 'primary' ? 'primary.main' : 'grey.500'}20`,
color: getStatusColor() === 'success' ? 'success.main' : getStatusColor() === 'primary' ? 'primary.main' : 'grey.500',
border: `1px solid ${getStatusColor() === 'success' ? 'success.main' : getStatusColor() === 'primary' ? 'primary.main' : 'grey.500'}40`,
fontWeight: 600
}}
/>
</Box>
</Box>
{/* Current Task Navigation */}
{navigationState?.currentTask && isInProgress && (
<Box sx={{
p: 2,
mb: 3,
background: 'rgba(76, 175, 80, 0.1)',
border: '1px solid rgba(76, 175, 80, 0.3)',
borderRadius: 2,
backdropFilter: 'blur(10px)'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<TaskAlt color="success" />
<Typography variant="h6" color="success.main" sx={{ fontWeight: 600 }}>
Current Task
</Typography>
</Box>
<Typography variant="subtitle1" fontWeight="medium" sx={{ mb: 1, color: 'white' }}>
{navigationState.currentTask.title}
</Typography>
<Typography variant="body2" sx={{ mb: 2, color: 'rgba(255,255,255,0.7)' }}>
{navigationState.currentTask.description}
</Typography>
{/* Navigation Controls */}
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Button
variant="contained"
size="small"
startIcon={<CheckCircle />}
onClick={() => navigationState.currentTask && handleCompleteTask(navigationState.currentTask.id)}
sx={{ background: 'linear-gradient(135deg, #4caf50 0%, #388e3c 100%)' }}
>
Complete
</Button>
<Button
variant="outlined"
size="small"
startIcon={<SkipNext />}
onClick={() => navigationState.currentTask && handleSkipTask(navigationState.currentTask.id)}
sx={{ borderColor: 'warning.main', color: 'warning.main' }}
>
Skip
</Button>
<Button
variant="outlined"
size="small"
startIcon={<NavigateNext />}
onClick={handleNextTask}
disabled={!navigationState.canGoForward}
sx={{ borderColor: 'primary.main', color: 'primary.main' }}
>
Next
</Button>
<Button
variant="outlined"
size="small"
startIcon={<NavigateBefore />}
onClick={handlePreviousTask}
disabled={!navigationState.canGoBack}
sx={{ borderColor: 'primary.main', color: 'primary.main' }}
>
Previous
</Button>
</Box>
</Box>
)}
{/* Task List */}
{currentWorkflow && (
<Box>
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1, color: 'white', fontWeight: 600 }}>
<Assignment color="primary" />
Today's Tasks
</Typography>
<Grid container spacing={2}>
<AnimatePresence>
{currentWorkflow.tasks.map((task, index) => (
<Grid item xs={12} md={6} key={task.id}>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.1 }}
>
<Box
sx={{
p: 2,
background: task.id === navigationState?.currentTask?.id
? 'rgba(76, 175, 80, 0.1)'
: 'rgba(255, 255, 255, 0.05)',
border: task.id === navigationState?.currentTask?.id
? '2px solid rgba(76, 175, 80, 0.5)'
: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: 2,
height: '100%',
backdropFilter: 'blur(10px)'
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle1" fontWeight="medium" sx={{ flexGrow: 1, color: 'white' }}>
{task.title}
</Typography>
{task.id === navigationState?.currentTask?.id && (
<Chip label="Current" color="success" size="small" />
)}
</Box>
<Typography variant="body2" sx={{ mb: 2, color: 'rgba(255,255,255,0.7)' }}>
{task.description}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2, flexWrap: 'wrap' }}>
<Chip
label={`${task.estimatedTime} min`}
size="small"
variant="outlined"
icon={<Timer />}
/>
<Chip
label={task.priority}
size="small"
color={task.priority === 'high' ? 'error' : task.priority === 'medium' ? 'warning' : 'default'}
/>
<Chip
label={task.status}
size="small"
color={task.status === 'completed' ? 'success' : task.status === 'skipped' ? 'warning' : 'default'}
/>
</Box>
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'flex-end' }}>
{task.status === 'pending' && isInProgress && (
<>
<Tooltip title="Complete Task">
<IconButton
size="small"
onClick={() => handleCompleteTask(task.id)}
sx={{
color: 'success.main',
'&:hover': { background: 'rgba(76, 175, 80, 0.1)' }
}}
>
<CheckCircle />
</IconButton>
</Tooltip>
<Tooltip title="Skip Task">
<IconButton
size="small"
onClick={() => handleSkipTask(task.id)}
sx={{
color: 'warning.main',
'&:hover': { background: 'rgba(255, 152, 0, 0.1)' }
}}
>
<Stop />
</IconButton>
</Tooltip>
</>
)}
{task.status === 'completed' && (
<CheckCircle color="success" />
)}
{task.status === 'skipped' && (
<Stop color="warning" />
)}
</Box>
</Box>
</motion.div>
</Grid>
))}
</AnimatePresence>
</Grid>
</Box>
)}
{/* Help Section */}
{!hasWorkflow && (
<Alert severity="info" sx={{ mt: 3, background: 'rgba(33, 150, 243, 0.1)', border: '1px solid rgba(33, 150, 243, 0.3)' }}>
<AlertTitle>Getting Started</AlertTitle>
Generate today's workflow to see your personalized marketing tasks. The system will guide you through each task with clear instructions and navigation controls.
</Alert>
)}
</Box>
</motion.div>
);
};
export default WorkflowDemo;

View File

@@ -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<WorkflowProgressBarProps> = ({
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 (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Box
sx={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
borderRadius: 2,
p: compact ? 2 : 3,
border: '1px solid rgba(255,255,255,0.1)',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
mb: 3
}}
>
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography
variant={compact ? "h6" : "h5"}
sx={{
fontWeight: 700,
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 1
}}
>
{isComplete ? <CheckCircle sx={{ color: theme.palette.success.main }} /> :
currentWorkflow?.workflowStatus === 'in_progress' ? <TrendingUp sx={{ color: theme.palette.primary.main }} /> :
<Schedule sx={{ color: theme.palette.grey[400] }} />}
Today's Marketing Workflow
</Typography>
<Chip
label={getStatusText()}
size="small"
sx={{
background: `${getStatusColor()}20`,
color: getStatusColor(),
border: `1px solid ${getStatusColor()}40`,
fontWeight: 600
}}
/>
</Box>
{/* Controls */}
{showControls && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{(currentWorkflow?.workflowStatus === 'not_started' || !currentWorkflow) && (
<Tooltip title={currentWorkflow ? "Start Today's Workflow" : "Generate & Start Workflow"}>
<IconButton
onClick={handleStartWorkflow}
disabled={isLoading}
sx={{
background: theme.palette.primary.main,
color: 'white',
'&:hover': {
background: theme.palette.primary.dark,
}
}}
>
<PlayArrow />
</IconButton>
</Tooltip>
)}
{currentWorkflow?.workflowStatus === 'in_progress' && (
<Tooltip title="Pause Workflow">
<IconButton
onClick={onPauseWorkflow}
disabled={isLoading}
sx={{
background: theme.palette.warning.main,
color: 'white',
'&:hover': {
background: theme.palette.warning.dark,
}
}}
>
<Pause />
</IconButton>
</Tooltip>
)}
</Box>
)}
</Box>
{/* Progress Bar */}
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
Progress
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
{workflowProgress?.completedTasks || 0} of {workflowProgress?.totalTasks || 0} tasks
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={currentWorkflow ? completionPercentage : 0}
sx={{
height: 8,
borderRadius: 4,
background: 'rgba(255,255,255,0.1)',
'& .MuiLinearProgress-bar': {
background: isComplete
? `linear-gradient(90deg, ${theme.palette.success.main} 0%, ${theme.palette.success.light} 100%)`
: `linear-gradient(90deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.light} 100%)`,
borderRadius: 4,
boxShadow: `0 0 10px ${isComplete ? theme.palette.success.main : theme.palette.primary.main}40`
}
}}
/>
<Typography
variant="caption"
sx={{
color: 'rgba(255,255,255,0.6)',
mt: 0.5,
display: 'block',
textAlign: 'right'
}}
>
{currentWorkflow ? `${completionPercentage}% complete` : 'No workflow active'}
</Typography>
</Box>
{/* Current Task Info */}
{currentTask && !isComplete && (
<Box
sx={{
background: 'rgba(255,255,255,0.05)',
borderRadius: 1,
p: 2,
border: '1px solid rgba(255,255,255,0.1)'
}}
>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)', mb: 0.5 }}>
Current Task:
</Typography>
<Typography variant="body1" sx={{ color: 'white', fontWeight: 600 }}>
{currentTask.title}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
{currentTask.description}
</Typography>
</Box>
)}
{/* Time Information */}
{workflowProgress && (
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 2 }}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
Time Spent: {workflowProgress.actualTimeSpent} min
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
Est. Remaining: {workflowProgress.estimatedTimeRemaining} min
</Typography>
</Box>
)}
</Box>
</motion.div>
);
};
export default WorkflowProgressBar;

View File

@@ -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<DashboardHeaderProps> = ({
title,
subtitle,
statusChips = [],
rightContent
rightContent,
customIcon,
workflowControls
}) => {
return (
<ShimmerHeader sx={{ mb: 5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
<Box>
<Typography variant="h2" component="h1" sx={{
fontWeight: 800,
color: 'white',
textShadow: '0 4px 8px rgba(0,0,0,0.3)',
mb: 1,
fontSize: { xs: '2rem', md: '3rem' },
background: 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}>
{title}
</Typography>
<Typography variant="h5" sx={{
color: 'rgba(255, 255, 255, 0.9)',
fontWeight: 400,
fontSize: { xs: '1rem', md: '1.25rem' },
}}>
{subtitle}
</Typography>
<ShimmerHeader sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{customIcon && (
<Box
component="img"
src={customIcon}
alt="Alwrity Logo"
sx={{
width: { xs: 40, md: 48 },
height: { xs: 40, md: 48 },
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))',
}}
/>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Box>
<Typography variant="h2" component="h1" sx={{
fontWeight: 800,
color: 'white',
textShadow: '0 4px 8px rgba(0,0,0,0.3)',
mb: subtitle ? 1 : 0,
fontSize: { xs: '2rem', md: '3rem' },
background: 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}>
{title}
</Typography>
{subtitle && (
<Typography variant="h5" sx={{
color: 'rgba(255, 255, 255, 0.9)',
fontWeight: 400,
fontSize: { xs: '1rem', md: '1.25rem' },
}}>
{subtitle}
</Typography>
)}
</Box>
{/* Workflow Controls */}
{workflowControls && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{/* Workflow Control Buttons */}
{!workflowControls.isWorkflowActive ? (
/* Start Button with Badge and Lightning Glow */
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<Button
variant="contained"
size="small"
startIcon={<PlayArrow />}
onClick={workflowControls.onStartWorkflow}
disabled={workflowControls.isLoading}
sx={{
position: 'relative',
overflow: 'hidden',
background: 'linear-gradient(135deg, #4caf50 0%, #388e3c 100%)',
border: '2px solid transparent',
'&:hover': {
background: 'linear-gradient(135deg, #388e3c 0%, #2e7d32 100%)',
},
minWidth: 'auto',
px: 2,
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: 'linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent)',
animation: 'shimmer 2.5s infinite',
zIndex: 1,
},
'&::after': {
content: '""',
position: 'absolute',
top: -2,
left: -2,
right: -2,
bottom: -2,
background: 'linear-gradient(45deg, #4caf50, #8bc34a, #4caf50, #8bc34a)',
backgroundSize: '400% 400%',
borderRadius: 'inherit',
zIndex: -1,
animation: 'borderGlow 3s ease-in-out infinite',
},
'@keyframes shimmer': {
'0%': { left: '-100%' },
'100%': { left: '100%' },
},
'@keyframes borderGlow': {
'0%, 100%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
},
}}
>
Start
</Button>
<Box
sx={{
position: 'absolute',
top: -8,
right: -8,
backgroundColor: '#1976d2',
color: 'white',
borderRadius: '12px',
px: 0.75,
py: 0.25,
fontSize: '0.65rem',
fontWeight: 700,
boxShadow: '0 2px 6px rgba(0,0,0,0.3)'
}}
>
{`${workflowControls.completedTasks}/${workflowControls.totalTasks}`}
</Box>
</Box>
) : (
/* In-Progress/Completed Controls with Enhanced Styling */
<Box sx={{ display: 'flex', gap: 1 }}>
{/* In-Progress/Completed Status with Lightning Glow */}
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<Button
variant="contained"
size="small"
onClick={workflowControls.onResumePlanModal}
disabled={workflowControls.isLoading}
sx={{
position: 'relative',
overflow: 'hidden',
background: workflowControls.completedTasks === workflowControls.totalTasks
? 'linear-gradient(135deg, #4caf50 0%, #388e3c 100%)'
: 'linear-gradient(135deg, #2196f3 0%, #1976d2 100%)',
color: 'white',
minWidth: 'auto',
px: 2,
border: '2px solid transparent',
boxShadow: workflowControls.completedTasks === workflowControls.totalTasks
? '0 8px 25px rgba(76, 175, 80, 0.4), 0 0 0 1px rgba(255,255,255,0.2)'
: '0 8px 25px rgba(33, 150, 243, 0.4), 0 0 0 1px rgba(255,255,255,0.2)',
'&:hover': {
background: workflowControls.completedTasks === workflowControls.totalTasks
? 'linear-gradient(135deg, #388e3c 0%, #2e7d32 100%)'
: 'linear-gradient(135deg, #1976d2 0%, #1565c0 100%)',
transform: 'translateY(-2px)',
boxShadow: workflowControls.completedTasks === workflowControls.totalTasks
? '0 12px 35px rgba(76, 175, 80, 0.6), 0 0 0 1px rgba(255,255,255,0.3)'
: '0 12px 35px rgba(33, 150, 243, 0.6), 0 0 0 1px rgba(255,255,255,0.3)',
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: 'linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent)',
animation: 'shimmer 2.5s infinite',
zIndex: 1,
},
'&::after': {
content: '""',
position: 'absolute',
top: -2,
left: -2,
right: -2,
bottom: -2,
background: workflowControls.completedTasks === workflowControls.totalTasks
? 'linear-gradient(45deg, #4caf50, #8bc34a, #4caf50, #8bc34a)'
: 'linear-gradient(45deg, #2196f3, #64b5f6, #2196f3, #64b5f6)',
backgroundSize: '400% 400%',
borderRadius: 'inherit',
zIndex: -1,
animation: 'borderGlow 3s ease-in-out infinite',
},
'@keyframes shimmer': {
'0%': { left: '-100%' },
'100%': { left: '100%' },
},
'@keyframes borderGlow': {
'0%, 100%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
}}
title={workflowControls.completedTasks === workflowControls.totalTasks
? '🎉 All tasks completed! Click to review workflow progress.'
: 'Workflow in progress. Click to resume or check current tasks.'}
>
{workflowControls.completedTasks === workflowControls.totalTasks ? 'Completed' : 'In Progress'}
</Button>
<Box
sx={{
position: 'absolute',
top: -8,
right: -8,
backgroundColor: '#1976d2',
color: 'white',
borderRadius: '12px',
px: 0.75,
py: 0.25,
fontSize: '0.65rem',
fontWeight: 700,
boxShadow: '0 2px 6px rgba(0,0,0,0.3)'
}}
>
{`${workflowControls.completedTasks}/${workflowControls.totalTasks}`}
</Box>
</Box>
</Box>
)}
</Box>
)}
</Box>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
{statusChips.length > 0 && (

View File

@@ -26,6 +26,15 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
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 (
<SearchContainer>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 3 }}>
@@ -74,13 +83,14 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
</Tooltip>
</Box>
{/* Enhanced Category Filter */}
{/* Enhanced Category Filter with Tool Count Badges */}
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<CategoryChip
label="All Tools"
onClick={() => onCategoryChange(null)}
active={selectedCategory === null}
theme={theme}
toolCount={Object.values(toolCategories).reduce((total, category) => total + getToolCount(category), 0)}
/>
{Object.keys(toolCategories).map((category) => (
<CategoryChip
@@ -89,6 +99,7 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
onClick={() => onCategoryChange(category)}
active={selectedCategory === category}
theme={theme}
toolCount={getToolCount(toolCategories[category])}
/>
))}
</Box>

View File

@@ -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 }) => ({

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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<VerificationResult>;
weight: number; // Importance weight for confidence calculation
}
interface VerificationContext {
userId: string;
timestamp: Date;
platformData?: Record<string, any>;
userActivity?: Record<string, any>;
}
class TaskCompletionVerifier {
private verificationRules: Map<string, VerificationRule> = new Map();
private verificationHistory: Map<string, VerificationResult[]> = new Map();
constructor() {
this.initializeDefaultRules();
}
/**
* Verify if a task has been completed
*/
async verifyTaskCompletion(
task: TodayTask,
context?: VerificationContext
): Promise<VerificationResult> {
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<Map<string, VerificationResult>> {
const results = new Map<string, VerificationResult>();
// 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<VerificationResult> {
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<VerificationResult> {
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<string, number>();
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<string, VerificationResult[]>;
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;

View File

@@ -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<string>();
const visiting = new Set<string>();
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<string>();
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<string>();
const visiting = new Set<string>();
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;

View File

@@ -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<string, any>;
}
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<boolean> {
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<TodayTask | null> {
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<TodayTask | null> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<NavigationConfig>): 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;

View File

@@ -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<string, DailyWorkflow> = 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<DailyWorkflow> {
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<DailyWorkflow> {
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<TaskCompletionData>
): Promise<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 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<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 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<TodayTask | null> {
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<TodayTask[]> {
// 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;

View File

@@ -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<void>;
startWorkflow: (workflowId: string) => Promise<void>;
pauseWorkflow: (workflowId: string) => Promise<void>;
stopWorkflow: (workflowId: string) => Promise<void>;
completeTask: (taskId: string, completionData?: any) => Promise<void>;
skipTask: (taskId: string) => Promise<void>;
moveToNextTask: () => Promise<void>;
moveToPreviousTask: () => Promise<void>;
// UI actions
openWorkflowModal: () => void;
closeWorkflowModal: () => void;
setError: (error: WorkflowError | null) => void;
clearError: () => void;
// Preferences
updateUserPreferences: (preferences: Partial<UserWorkflowPreferences>) => void;
// Utility actions
refreshWorkflowProgress: () => void;
getCurrentTask: () => TodayTask | null;
getNextTask: () => TodayTask | null;
isWorkflowComplete: () => boolean;
getCompletionPercentage: () => number;
}
export const useWorkflowStore = create<WorkflowState>()(
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<UserWorkflowPreferences>) => {
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;

View File

@@ -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<string, any>;
icon?: string | React.ComponentType<any>; // 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<string, any>;
}
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<string, any>; // data from connected platforms
}
// Navigation and action interfaces
export interface TaskAction {
type: ActionType;
url?: string;
modalId?: string;
externalUrl?: string;
params?: Record<string, any>;
}
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<void>;
recoverFromError: (error: WorkflowError) => Promise<boolean>;
logError: (error: WorkflowError) => Promise<void>;
}