Vista Calendario
Exportar contenido
Visualiza tareas en calendario mensual con indicadores y creación rápida.
Características
Sección titulada «Características»- Calendario mensual: Vista completa del mes con todas las semanas
- Indicadores de tareas: Puntos de colores según prioridad más alta
- Máximo 3 puntos: Si hay más tareas, muestra solo 3 puntos
- Panel lateral: Detalles de tareas del día seleccionado
- Navegación: Botones anterior/siguiente mes, botón “Hoy”
- Crear tarea: Click en fecha para crear con fecha preseleccionada
- Responsive: Layout adaptativo móvil/desktop
Indicadores Visuales
Sección titulada «Indicadores Visuales»Puntos por Prioridad:
- 🔴 Rojo: Urgente
- 🟠 Naranja: Alta
- 🟡 Amarillo: Media
- 🔵 Azul: Baja
Se muestra el color de la prioridad más alta del día.
Panel Lateral
Sección titulada «Panel Lateral»Muestra para el día seleccionado:
- Título de la tarea
- Badge de estado (Pendiente, En Progreso, etc.)
- Badge de prioridad
- Hora de vencimiento (si está configurada)
- Click para ver detalle completo
Navegación
Sección titulada «Navegación»- Botones ◀ ▶: Cambiar mes
- Botón “Hoy”: Volver al día actual
- Click en día: Seleccionar fecha y ver tareas
Detalles Técnicos
Componente
Sección titulada «Componente»- Archivo:
TaskCalendar.tsx - Ruta:
src/features/projects/sub-modules/tasks/components/TaskCalendar.tsx
Estado del Componente
Sección titulada «Estado del Componente»const [currentDate, setCurrentDate] = useState(new Date());const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());const [sheetOpen, setSheetOpen] = useState(false);Query Tareas del Mes
Sección titulada «Query Tareas del Mes»const { data: tasks } = useQuery({ queryKey: ["tasks-calendar", format(currentDate, "yyyy-MM")], queryFn: async () => { const start = startOfMonth(currentDate); const end = endOfMonth(currentDate);
const { data, error } = await supabase .from("tasks") .select("*") .gte("due_date", format(start, "yyyy-MM-dd")) .lte("due_date", format(end, "yyyy-MM-dd") + "T23:59:59") .order("due_date", { ascending: true });
if (error) throw error; return data as Task[]; },});Función Obtener Tareas del Día
Sección titulada «Función Obtener Tareas del Día»const getTasksForDate = (date: Date) => { return tasks.filter((task) => { return task.due_date && isSameDay(new Date(task.due_date), date); });};
const tasksForSelectedDate = selectedDate ? getTasksForDate(selectedDate) : [];Contenido Personalizado del Día
Sección titulada «Contenido Personalizado del Día»const getDayContent = (date: Date) => { const dayTasks = getTasksForDate(date); const taskCount = dayTasks.length;
if (taskCount === 0) return null;
// Máximo 3 puntos const dotsToShow = Math.min(taskCount, 3);
// Obtener prioridad más alta const highestPriority = dayTasks.reduce((highest, task) => { const priorityOrder = { urgent: 4, high: 3, medium: 2, low: 1 }; return priorityOrder[task.priority] > priorityOrder[highest] ? task.priority : highest; }, "low" as TaskPriority);
return ( <div className="absolute -bottom-1 left-1/2 -translate-x-1/2 flex gap-0.5"> {Array.from({ length: dotsToShow }).map((_, i) => ( <div key={i} className={cn( "w-1 h-1 rounded-full", priorityConfig[highestPriority].dotColor )} /> ))} </div> );};Componente Calendar con DayContent
Sección titulada «Componente Calendar con DayContent»<Calendar mode="single" selected={selectedDate} onSelect={setSelectedDate} month={currentDate} onMonthChange={handleMonthChange} locale={es} modifiers={{ hasTask: datesWithTasks, }} components={{ DayContent: ({ date }) => ( <div className="relative flex flex-col items-center justify-center w-full h-9"> <span>{date.getDate()}</span> {getDayContent(date)} </div> ), }}/>Panel Lateral
Sección titulada «Panel Lateral»<Card className="h-full"> <CardHeader> <CardTitle> {selectedDate ? format(selectedDate, "d 'de' MMMM", { locale: es }) : "Selecciona un día"} </CardTitle> <Button size="sm" onClick={handleNewTask}> <Plus className="h-3.5 w-3.5" /> Nueva </Button> </CardHeader> <CardContent> {tasksForSelectedDate.length === 0 ? ( <div className="text-center py-8"> <Clock className="h-5 w-5 text-muted-foreground mx-auto mb-3" /> <p className="text-sm text-muted-foreground"> No hay tareas para este día </p> <Button variant="link" size="sm" onClick={handleNewTask}> Crear una tarea </Button> </div> ) : ( <div className="space-y-2"> {tasksForSelectedDate.map((task) => ( <div key={task.id} className="p-3 border rounded-lg cursor-pointer hover:bg-accent/50" onClick={() => navigate(`/tasks/${task.id}`)} > {/* Indicador prioridad */} <div className={cn( "w-1 h-full min-h-[40px] rounded-full", priorityConfig[task.priority].dotColor )} />
{/* Título */} <p className="font-medium text-sm truncate">{task.title}</p>
{/* Badges: estado, prioridad, hora */} <div className="flex items-center gap-2 mt-1.5"> <Badge variant="outline" className={statusConfig[task.status].color}> {statusConfig[task.status].label} </Badge> <span className={priorityConfig[task.priority].color}> {priorityConfig[task.priority].label} </span> {task.due_date && ( <span className="text-[10px] text-muted-foreground"> <Clock className="h-2.5 w-2.5" /> {format(new Date(task.due_date), "HH:mm")} </span> )} </div> </div> ))} </div> )} </CardContent></Card>Configuración de Colores
Sección titulada «Configuración de Colores»const priorityConfig: Record<TaskPriority, { color: string; dotColor: string; label: string}> = { low: { color: "bg-blue-500/10 text-blue-700", dotColor: "bg-blue-500", label: "Baja" }, medium: { color: "bg-yellow-500/10 text-yellow-700", dotColor: "bg-yellow-500", label: "Media" }, high: { color: "bg-orange-500/10 text-orange-700", dotColor: "bg-orange-500", label: "Alta" }, urgent: { color: "bg-red-500/10 text-red-700", dotColor: "bg-red-500", label: "Urgente" },};Navegación de Meses
Sección titulada «Navegación de Meses»const handlePrevMonth = () => { const newDate = new Date(currentDate); newDate.setMonth(newDate.getMonth() - 1); setCurrentDate(newDate);};
const handleNextMonth = () => { const newDate = new Date(currentDate); newDate.setMonth(newDate.getMonth() + 1); setCurrentDate(newDate);};
const handleToday = () => { setCurrentDate(new Date()); setSelectedDate(new Date());};Crear Tarea con Fecha
Sección titulada «Crear Tarea con Fecha»<TaskFormSheet open={sheetOpen} onOpenChange={setSheetOpen} defaultDueDate={selectedDate ? format(selectedDate, "yyyy-MM-dd") : undefined} onSuccess={handleTaskSuccess}/>Layout Responsive
Sección titulada «Layout Responsive»<div className="flex flex-col lg:flex-row gap-4"> {/* Calendario principal */} <div className="flex-1 lg:flex-[2]"> <Card>{/* Calendar */}</Card> </div>
{/* Panel lateral */} <div className="w-full lg:w-80"> <Card>{/* Tareas del día */}</Card> </div></div>