Componente TaskZohoLinks
Ruta: src/components/tasks/TaskZohoLinks.tsx
App: tasks (frontend React/TypeScript)
Introducido: s68 / commit 18f32fb (2026-05-17)
ADR referenciado: decision--20260517--integracion-zoho-calendar-mail-pivot (ver comentario de cabecera del fichero)
Propósito
Encapsula la sección “Vínculos Zoho” del TaskModal. Gestiona dos subsecciones:
- Eventos Calendar — crear un evento en Zoho Calendar y vincularlo a la tarea.
- Emails Mail — buscar y vincular mensajes de Zoho Mail a la tarea.
Soporta dos modos de operación:
Prop taskId | Modo | Comportamiento |
|---|---|---|
number | edit | POST/DELETE inmediato al backend al añadir o quitar vínculos |
null | create | Estado local (pending*); el padre ejecuta los POSTs tras crear la task |
Interface de props
interface Props {
taskId: number | null; // null = create mode
taskTitle: string;
taskDescription: string;
selectedAssignees: string[];
isLocal: boolean;
onCountChange?: (taskId: number, kind: 'calendar' | 'email', newCount: number) => void;
// Solo relevantes en create mode (ignoradas en edit):
pendingEvent?: PendingCalendarEvent | null;
onPendingEventChange?: (event: PendingCalendarEvent | null) => void;
pendingEmailLinks?: PendingEmailLink[];
onPendingEmailLinksChange?: (links: PendingEmailLink[]) => void;
}
Tipos exportados
// Datos de evento pre-creación. Enviados al padre via onPendingEventChange.
export interface PendingCalendarEvent {
title: string;
description?: string;
start: string; // ISO UTC
end: string; // ISO UTC
location?: string;
attendees: string[];
}
// Email seleccionado pre-creación. Enviado al padre via onPendingEmailLinksChange.
export interface PendingEmailLink {
zoho_message_id: string;
thread_id?: string;
subject: string;
from_address: string;
email_date: string;
url: string;
}
Estado interno relevante
| State | Tipo | Descripción |
|---|---|---|
calendarLinks | CalendarLink[] | Eventos vinculados (cargados del backend en edit mode) |
emailLinks | EmailLink[] | Emails vinculados (cargados del backend en edit mode) |
statusMembers | ZohoStatusMember[] | Miembros con zoho_email para resolver attendees |
showCreateEvent | boolean | Controla visibilidad del mini-form de evento |
showLinkEmail | boolean | Controla visibilidad del picker de emails |
loading | boolean | Inicializado a false si isCreate, true en edit |
lastFetchedTaskIdRef (useRef) previene refetch del backend cuando taskId no cambia entre re-renders.
Acciones del usuario
+ Crear evento (ambos modos)
- Usuario abre mini-form (
showCreateEvent = true), rellena título, inicio, fin, ubicación. handleCreateEvent():- create: llama
onPendingEventChange(payload)y cierra el form. Constraint: solo 1 pending a la vez. - edit:
POST /api/zoho/calendar→POST /api/tasks/{id}/links/calendar→ actualizacalendarLinks.
- create: llama
+ Vincular email (ambos modos)
- Usuario abre picker, busca por
q / from / subject→GET /api/zoho/mail/search. handlePickEmail(msg):- create: añade a
pendingEmailLinks(deduplicado pormessageId). - edit:
POST /api/tasks/{id}/links/email→ actualizaemailLinks.
- create: añade a
+ Nuevo email (solo edit)
Deep-link a Zoho Mail compose con subject y body pre-rellenados. Deshabilitado en create mode (necesita taskId para contexto útil en la URL de compose).
Quitar pending (solo create mode)
handleRemovePendingEvent()→onPendingEventChange(null)handleRemovePendingEmail(messageId)→ filtra del array y llamaonPendingEmailLinksChange
Desvincular (solo edit mode)
handleDeleteCalendarLink(uid)→DELETE /api/tasks/{id}/links/calendar?uid=...handleDeleteEmailLink(messageId)→DELETE /api/tasks/{id}/links/email?message_id=...
Comportamiento visual create mode
- Items pendientes se renderizan con
border: 1px dashed var(--color-border)y badgependienteen itálica. - Contador de eventos/emails usa
pendingEvent ? 1 : 0ypendingEmailLinks.lengthen lugar de las longitudes de los arrays del backend. - Texto de ayuda en el mini-form: “El evento se crea cuando pulses ‘Crear’ la tarea abajo.”
Carga de datos por modo
edit mode:
useEffect #1 → Promise.all([
GET /api/tasks/{id}/links/calendar,
GET /api/tasks/{id}/links/email,
GET /api/zoho/status
])
create mode:
useEffect #1 → skip (isCreate)
useEffect #2 → GET /api/zoho/status (solo para resolver attendees)
Funciones utilitarias internas
| Función | Descripción |
|---|---|
nextHourLocal() | Devuelve string para <input type="datetime-local"> con la próxima hora redondeada |
plusOneHour(localIso) | Suma 1h a un datetime-local string |
localToISO(local) | Convierte datetime-local a ISO UTC (new Date(local).toISOString()) |
formatDateShort(iso) | Formatea ISO → DD/MM HH:MM |
resolveAttendees(assignees, members) | Mapea assignees a emails Zoho filtrando pending/unknown |
Endpoints consumidos
| Método | Ruta | Modo |
|---|---|---|
GET | /api/tasks/{id}/links/calendar | edit |
GET | /api/tasks/{id}/links/email | edit |
GET | /api/zoho/status | ambos |
GET | /api/zoho/mail/search | ambos |
POST | /api/zoho/calendar | edit (en create lo hace el padre) |
POST | /api/tasks/{id}/links/calendar | edit (en create lo hace el padre) |
POST | /api/tasks/{id}/links/email | edit (en create lo hace el padre) |
DELETE | /api/tasks/{id}/links/calendar?uid=... | edit |
DELETE | /api/tasks/{id}/links/email?message_id=... | edit |
Historial de versiones relevante
| Fecha | Cambio |
|---|---|
| 2026-05-17 | Soporte taskId: null (create mode), tipos PendingCalendarEvent + PendingEmailLink exportados, patrón pending, resolveAttendees extraído, lastFetchedTaskIdRef, carga separada de statusMembers en create mode |
Véase también
- [[feature—tasks—zoho-links-create-mode]]
- [[entity—tasks—component—task-modal]]