TaskSection.tsx8.7 KBView on GitHub
'use client';

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { SectionHeading } from '@/modules/conversations/components/SectionHeading';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import type { ConversationUserTask as UserTask } from '@/modules/crm/types';
import { taskOutputLabel } from '@/modules/userTasks/utils/task-output';
import { Mail, ExternalLink, ChevronDown, Filter } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useCedarStore } from '@/modules/store';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import { useState } from 'react';

interface TaskSectionProps {
  tasks: UserTask[];
}

type TaskFilter = 'todo' | 'done' | 'all';

export function TaskSection({ tasks }: TaskSectionProps) {
  const selectThreadId = useCedarStore((state) => state.selectThreadId);
  const setIsThreadOpen = useCedarStore((state) => state.setIsThreadOpen);
  const [statusFilter, setStatusFilter] = useState<TaskFilter>('todo');

  if (!tasks || tasks.length === 0) {
    return (
      <div className="rounded-lg p-3">
        <div className="mb-2">
          <SectionHeading>Tasks</SectionHeading>
        </div>
        <div className="text-muted-foreground rounded-sm border border-dashed p-3 text-center text-xs">
          No tasks for this conversation
        </div>
      </div>
    );
  }

  // Filter tasks based on selected filter
  const filteredTasks = tasks.filter((t) => {
    if (statusFilter === 'all') return true;
    return t.status === statusFilter;
  });

  const handleOpenThread = (task: UserTask) => {
    const actionData = task.taskActionData;
    if (actionData?.channel === 'email' && actionData.threadId) {
      selectThreadId(actionData.threadId);
      setIsThreadOpen(true);
    }
  };

  const getTaskTypeAndIcon = (task: UserTask) => {
    // Check if it has a draft ready using new schema
    const actionData = task.taskActionData;
    const hasDraft = actionData?.channel === 'email' && actionData.draftId;

    if (hasDraft) {
      return {
        type: 'Follow-up drafted',
        icon: <Mail className="h-3 w-3 fill-green-800 dark:fill-green-200" />,
        hasDraft: true,
      };
    }

    // Use task label
    return {
      type: task.taskType || 'Task',
      icon: <Mail className="h-3 w-3 fill-current" />,
      hasDraft: false,
    };
  };

  const getTaskDescription = (task: UserTask) => {
    return task.description || 'No description';
  };

  const renderTask = (task: UserTask) => {
    const actionData = task.taskActionData;
    const hasThread = actionData?.channel === 'email' && actionData.threadId;
    const { type: taskType, icon: taskIcon, hasDraft } = getTaskTypeAndIcon(task);
    const description = getTaskDescription(task);

    return (
      <div
        key=[redacted]
        className={cn(
          'space-y-2 rounded-md border p-2.5 text-xs transition-colors',
          task.status === 'todo'
            ? 'border-orange-200 bg-orange-50/50 dark:border-orange-900/50 dark:bg-orange-950/20'
            : task.status === 'done'
              ? 'border-blue-200 bg-blue-50/50 dark:border-blue-900/50 dark:bg-blue-950/20'
              : 'border-gray-200 bg-gray-50/50 dark:border-gray-800 dark:bg-gray-900/20',
        )}
      >
        {/* Row 1: Task Type Badge + Type Badge + External Link */}
        <div className="flex items-center justify-between gap-2">
          <div className="flex items-center gap-1.5">
            <Badge
              variant="outline"
              className={cn(
                'shrink-0 gap-1.5 rounded-sm',
                hasDraft
                  ? 'border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700 dark:bg-orange-900 dark:text-orange-200 hover:bg-orange-200 dark:hover:bg-orange-900/80'
                  : 'bg-primary/10 text-primary border-primary/20 hover:bg-primary/20 dark:hover:bg-primary/15',
              )}
            >
              {taskIcon}
              <span className="truncate">{taskType}</span>
            </Badge>
            {/* Task Output Badge */}
            <Badge variant="secondary" className="rounded-sm text-[10px]">
              {taskOutputLabel(task)}
            </Badge>
            {/* Status Badge */}
            <Badge
              variant="outline"
              className={cn(
                'rounded-sm text-[10px]',
                task.status === 'todo'
                  ? 'border-green-300 bg-green-50 text-green-700 dark:border-green-700 dark:bg-green-900/20 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/30'
                  : task.status === 'done'
                    ? 'border-blue-300 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/30'
                    : 'border-gray-300 bg-gray-50 text-gray-700 dark:border-gray-700 dark:bg-gray-800/20 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800/30',
              )}
            >
              {task.status === 'todo' ? 'To Do' : task.status === 'done' ? 'Done' : task.status}
            </Badge>
          </div>
          {hasThread && (
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  variant="outline"
                  size="sm"
                  className="h-6 gap-1 px-2 text-xs"
                  onClick={(e) => {
                    e.stopPropagation();
                    handleOpenThread(task);
                  }}
                >
                  <ExternalLink className="h-3 w-3" />
                  Open
                </Button>
              </TooltipTrigger>
              <TooltipContent>
                <p className="text-xs">Open thread</p>
              </TooltipContent>
            </Tooltip>
          )}
        </div>

        {/* Row 2: Task Description */}
        <div className="text-muted-foreground text-xs leading-relaxed">{description}</div>
      </div>
    );
  };

  const getFilterLabel = (filter: TaskFilter): string => {
    switch (filter) {
      case 'todo':
        return 'To Do';
      case 'done':
        return 'Done';
      case 'all':
        return 'All';
    }
  };

  const getFilterCount = (filter: TaskFilter): number => {
    switch (filter) {
      case 'todo':
        return tasks.filter((t) => t.status === 'todo').length;
      case 'done':
        return tasks.filter((t) => t.status === 'done').length;
      case 'all':
        return tasks.length;
    }
  };

  return (
    <div className="rounded-lg p-3">
      {/* Header with Filter */}
      <div className="mb-3 flex items-center justify-between">
        <SectionHeading>Tasks ({getFilterCount(statusFilter)})</SectionHeading>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="outline" size="sm" className="h-7 gap-1.5 text-xs">
              <Filter className="h-3 w-3" />
              {getFilterLabel(statusFilter)}
              <ChevronDown className="h-3 w-3" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuItem onClick={() => setStatusFilter('todo')}>
              <div className="flex w-full items-center justify-between gap-2">
                <span>To Do</span>
                <Badge variant="secondary" className="ml-2 rounded-sm text-[10px]">
                  {getFilterCount('todo')}
                </Badge>
              </div>
            </DropdownMenuItem>
            <DropdownMenuItem onClick={() => setStatusFilter('done')}>
              <div className="flex w-full items-center justify-between gap-2">
                <span>Done</span>
                <Badge variant="secondary" className="ml-2 rounded-sm text-[10px]">
                  {getFilterCount('done')}
                </Badge>
              </div>
            </DropdownMenuItem>
            <DropdownMenuItem onClick={() => setStatusFilter('all')}>
              <div className="flex w-full items-center justify-between gap-2">
                <span>All</span>
                <Badge variant="secondary" className="ml-2 rounded-sm text-[10px]">
                  {tasks.length}
                </Badge>
              </div>
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </div>

      {/* Tasks List */}
      {filteredTasks.length > 0 ? (
        <div className="space-y-2">{filteredTasks.map(renderTask)}</div>
      ) : (
        <div className="text-muted-foreground rounded-sm border border-dashed p-3 text-center text-xs">
          No {statusFilter !== 'all' ? getFilterLabel(statusFilter).toLowerCase() : ''} tasks
        </div>
      )}
    </div>
  );
}