Alwrity today's tasks workflow implementation plan.
This commit is contained in:
@@ -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;
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
576
frontend/src/components/MainDashboard/components/PillarData.tsx
Normal file
576
frontend/src/components/MainDashboard/components/PillarData.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }) => ({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user