import { useTranslation } from 'react-i18next'; import { useState } from 'react'; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { Clock, Calendar, Play, Pause, MoreHorizontal, Edit, Trash2, Timer, CheckCircle, X, Lock, CornerDownRight, Share2, Users } from "lucide-react"; import { Task, Label, User } from '@shared/schema'; import { ShareTaskModal } from './ShareTaskModal'; import { useQuery } from '@tanstack/react-query'; import { Checkbox } from "@/components/ui/checkbox"; import { motion, PanInfo, useAnimation } from 'framer-motion'; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { triggerConfetti } from "@/lib/confetti"; import { playSuccessSound } from "@/lib/sounds"; import { simulateAIDecomposition } from "@/lib/ai-simulator"; import { Wand2, Loader2 } from "lucide-react"; import { apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; interface TaskCardProps { task: Task; onStartTimer?: () => void; onStopTimer?: () => void; onEdit?: () => void; onDelete?: () => void; onStatusChange?: (status: Task['status']) => void; onUpdate?: (updates: Partial) => void; onAutoSchedule?: () => void; isDragging?: boolean; } function SharedTaskIcon({ task }: { task: Task }) { const { t } = useTranslation(); const { data: user } = useQuery({ queryKey: ["/api/user"], retry: false }); const isOwner = user?.id === task.userId; // If owner, fetch shared users const { data: sharedUsers } = useQuery({ queryKey: [`/api/tasks/${task.id}/shared-users`], enabled: !!isOwner && !!task.id, retry: false }); if (!user) return null; // Case 1: Owner and task is shared if (isOwner && sharedUsers && sharedUsers.length > 0) { const names = sharedUsers.map(u => u.username).join(", "); return (

{t('share.sharedWith')}: {names}

); } // Case 2: Not owner (Shared WITH me) if (!isOwner && task.userId) { return (

{t('share.sharedWithMe') || "Shared with me"}

); } return null; } function SharedMenuItem({ task, onShare }: { task: Task, onShare: () => void }) { const { t } = useTranslation(); const { data: user } = useQuery({ queryKey: ["/api/user"], retry: false }); // Only owner can share if (user?.id !== task.userId) return null; return ( { e.stopPropagation(); onShare(); }} > {t('share.taskTitle')} ); } export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDelete, onStatusChange, onUpdate, onAutoSchedule, isDragging }: TaskCardProps) { const { t } = useTranslation(); const { toast } = useToast(); const [isAnalyzing, setIsAnalyzing] = useState(false); const [isShareModalOpen, setIsShareModalOpen] = useState(false); const handleAIMagic = async (e: React.MouseEvent) => { e.stopPropagation(); if (isAnalyzing) return; setIsAnalyzing(true); try { const aiContent = await simulateAIDecomposition(task.title); const newDescription = (task.description || '') + aiContent; onUpdate?.({ description: newDescription }); triggerConfetti(0.5, 0.5); // Small burst for "Magic" playSuccessSound(); } catch (err) { console.error(err); } finally { setIsAnalyzing(false); } }; const { data: allTasks = [] } = useQuery({ queryKey: ['/api/tasks'], staleTime: 5 * 60 * 1000, }); const blockingTasks = task.dependencies?.map(dId => allTasks.find(t => t.id === dId)).filter(t => t && t.status !== 'done') || []; const isBlocked = blockingTasks.length > 0; const subtasks = allTasks.filter(t => t.parentTaskId === task.id); const completedSubtasks = subtasks.filter(t => t.status === 'done'); // Fetch labels to get the label color const { data: labels = [] } = useQuery({ queryKey: ['/api/labels'], staleTime: 5 * 60 * 1000, // 5 minutes cache refetchOnWindowFocus: false, }); // Find the label for this task const taskLabel = task.labelId && labels.length > 0 ? labels.find(label => label.id === task.labelId) : null; const handleToggleTimer = () => { if (task.isTracking) { onStopTimer?.(); console.log(`Timer stopped for task: ${task.title}`); } else { onStartTimer?.(); console.log(`Timer started for task: ${task.title}`); } }; const getPriorityColor = (priority: string) => { switch (priority) { case 'high': return 'bg-destructive text-destructive-foreground'; case 'medium': return 'bg-yellow-500 text-white'; default: return 'bg-muted text-muted-foreground'; } }; const getStatusColor = (status: string) => { switch (status) { case 'done': return 'bg-green-500 text-white'; case 'inProgress': return 'bg-primary text-primary-foreground'; default: return 'bg-muted text-muted-foreground'; } }; const formatTime = (minutes: number) => { if (minutes < 60) { return `${minutes}m`; } const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (mins === 0) { return `${hours}h`; } return `${hours}h ${mins}m`; }; /* AI Follow-up Check */ const checkFollowUp = async () => { // Fire and forget - don't block UI try { const res = await apiRequest("POST", "/api/ai/analyze-completion", { taskId: task.id }); if (res.ok) { const data = await res.json(); if (data.needed && data.title) { toast({ title: t('ai.followUpSuggestion', 'Follow-up Suggested'), description: `${data.title} - ${data.description || ''}`, action: ( ), duration: 8000, }); } } } catch (e) { console.error("Follow-up check failed", e); } }; const handleToggleComplete = (e: React.MouseEvent) => { e.stopPropagation(); if (isBlocked) return; if (task.status === 'done') { onStatusChange?.('todo'); } else { triggerConfetti(e.clientX / window.innerWidth, e.clientY / window.innerHeight); playSuccessSound(); onStatusChange?.('done'); // Trigger AI check checkFollowUp(); } }; const controls = useAnimation(); const handleDragEnd = async (event: any, info: PanInfo) => { if (info.offset.x > 100) { if (isBlocked) { controls.start({ x: 0 }); return; } // Swiped right -> Complete triggerConfetti(0.5, 0.5); playSuccessSound(); onStatusChange?.('done'); await controls.start({ x: 500, opacity: 0 }); } else if (info.offset.x < -100 && onDelete) { // Swiped left -> Delete (optional, maybe just shake for now) onDelete(); } else { controls.start({ x: 0 }); } }; /* Retrieve parent task if this is a subtask */ const parentTask = task.parentTaskId ? allTasks.find(t => String(t.id) === String(task.parentTaskId)) : null; return (
{/* Checkbox for quick completion */} {isBlocked ? (

{t('taskCard.blockedBy', { tasks: blockingTasks.map(t => t?.title).join(", ") })}

) : (

{t('taskCard.toggleComplete')}

)}
{ onEdit?.(); console.log(`Task card clicked: ${task.title}`); }} > {/* Parent Task Link rendered in title now */}

{parentTask ? ( {parentTask.title} {task.title} ) : ( task.title )}

{task.description && (

{task.description}

)}
{t(`priority.${task.priority}`)} {t(`status.${task.status}`)} {/* Shared Status Icon */} {(task.dueDate || task.startDate) && (
{task.startDate ? `${new Date(task.startDate).toLocaleDateString()} - ` : ''} {task.dueDate ? new Date(task.dueDate).toLocaleDateString() : ''}
)} {task.energyLevel && task.energyLevel !== 'medium' && ( {t(`energy.${task.energyLevel}`)} )} {task.estimatedDuration && ( ⏳ {formatTime(task.estimatedDuration)} )} {subtasks.length > 0 && ( {completedSubtasks.length}/{subtasks.length} Subtasks )} {isBlocked && ( Blocked

Waiting for: {blockingTasks.map(t => t?.title).join(", ")}

)}
{(task.timeTracked > 0 || task.isTracking) && (
{task.timeTracked > 0 ? formatTime(task.timeTracked) : '0m'} {task.isTracking && ` (${t('taskCard.running')})`}
)}
{/* Quick delete button */} {onDelete && (

{t('taskCard.quickDelete')}

)} e.stopPropagation()}> { e.stopPropagation(); onEdit?.(); console.log(`Edit task: ${task.title}`); }} data-testid={`menu-edit-${task.id}`} > {t('taskCard.editTask')} {onAutoSchedule && !task.dueDate && ( { e.stopPropagation(); onAutoSchedule(); }} className="text-indigo-600 dark:text-indigo-400" data-testid={`menu-schedule-${task.id}`} > {t('taskDetails.autoSchedule', 'Auto-Schedule')} )} { e.stopPropagation(); handleToggleTimer(); }} data-testid={`menu-timer-${task.id}`} > {task.isTracking ? t('taskCard.stopTimer') : t('taskCard.startTimer')} {/* Share Option - Check if owner */} setIsShareModalOpen(true)} /> { e.stopPropagation(); if (isBlocked) return; onStatusChange?.('done'); console.log(`Mark task as done: ${task.title}`); }} disabled={isBlocked} data-testid={`menu-complete-${task.id}`} > {isBlocked ? : } {t('taskCard.markAsDone')} {task.status === 'todo' && ( { e.stopPropagation(); onStatusChange?.('inProgress'); console.log(`Start working on: ${task.title}`); }} data-testid={`menu-start-${task.id}`} > {t('taskCard.startWorking')} )} {task.status === 'inProgress' && ( { e.stopPropagation(); onStatusChange?.('todo'); console.log(`Move back to todo: ${task.title}`); }} data-testid={`menu-todo-${task.id}`} > {t('taskCard.moveToTodo')} )} {task.status === 'done' && ( { e.stopPropagation(); onStatusChange?.('inProgress'); console.log(`Reopen task: ${task.title}`); }} data-testid={`menu-reopen-${task.id}`} > {t('taskCard.reopenTask')} )} {onDelete && ( { e.stopPropagation(); onDelete(); console.log(`Delete task: ${task.title}`); }} className="text-destructive" data-testid={`menu-delete-${task.id}`} > {t('taskCard.deleteTask')} )}
{ e.stopPropagation(); onEdit?.(); }} data-testid={`context-menu-edit-${task.id}`} > {t('taskCard.editTask')} { e.stopPropagation(); handleToggleTimer(); }} data-testid={`context-menu-timer-${task.id}`} > {task.isTracking ? t('taskCard.stopTimer') : t('taskCard.startTimer')} {task.status !== 'done' && ( { e.stopPropagation(); onStatusChange?.('done'); }} data-testid={`context-menu-complete-${task.id}`} > {t('taskCard.markAsDone')} )} {task.status === 'todo' && ( { e.stopPropagation(); onStatusChange?.('inProgress'); }} data-testid={`context-menu-start-${task.id}`} > {t('taskCard.startWorking')} )} {task.status === 'inProgress' && ( { e.stopPropagation(); onStatusChange?.('todo'); }} data-testid={`context-menu-todo-${task.id}`} > {t('taskCard.moveToTodo')} )} {task.status === 'done' && ( { e.stopPropagation(); onStatusChange?.('inProgress'); }} data-testid={`context-menu-reopen-${task.id}`} > {t('taskCard.reopenTask')} )} {onDelete && ( { e.stopPropagation(); onDelete(); }} className="text-destructive" data-testid={`context-menu-delete-${task.id}`} > {t('taskCard.deleteTask')} )}
); }