CreaRack-SL

Componente TaskZohoLinks — sección vínculos Zoho del TaskModal

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:

  1. Eventos Calendar — crear un evento en Zoho Calendar y vincularlo a la tarea.
  2. Emails Mail — buscar y vincular mensajes de Zoho Mail a la tarea.

Soporta dos modos de operación:

Prop taskIdModoComportamiento
numbereditPOST/DELETE inmediato al backend al añadir o quitar vínculos
nullcreateEstado 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

StateTipoDescripción
calendarLinksCalendarLink[]Eventos vinculados (cargados del backend en edit mode)
emailLinksEmailLink[]Emails vinculados (cargados del backend en edit mode)
statusMembersZohoStatusMember[]Miembros con zoho_email para resolver attendees
showCreateEventbooleanControla visibilidad del mini-form de evento
showLinkEmailbooleanControla visibilidad del picker de emails
loadingbooleanInicializado 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)

  1. Usuario abre mini-form (showCreateEvent = true), rellena título, inicio, fin, ubicación.
  2. 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 → actualiza calendarLinks.

+ Vincular email (ambos modos)

  1. Usuario abre picker, busca por q / from / subject → GET /api/zoho/mail/search.
  2. handlePickEmail(msg):
    • create: añade a pendingEmailLinks (deduplicado por messageId).
    • edit: POST /api/tasks/{id}/links/email → actualiza emailLinks.

+ 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 llama onPendingEmailLinksChange

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 badge pendiente en itálica.
  • Contador de eventos/emails usa pendingEvent ? 1 : 0 y pendingEmailLinks.length en 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ónDescripció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étodoRutaModo
GET/api/tasks/{id}/links/calendaredit
GET/api/tasks/{id}/links/emailedit
GET/api/zoho/statusambos
GET/api/zoho/mail/searchambos
POST/api/zoho/calendaredit (en create lo hace el padre)
POST/api/tasks/{id}/links/calendaredit (en create lo hace el padre)
POST/api/tasks/{id}/links/emailedit (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

FechaCambio
2026-05-17Soporte 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]]