feat(contactos): detalle completo del cliente con propiedad e historial
- GET /api/contactos/:id/detalle: todas las columnas del contacto, la propiedad comprada/en proceso (apartado relacionado por teléfono/email, enriquecido con el desarrollo de la unidad), quién atiende cada item relacionado (leads/oportunidades/apartados, columna people) y el historial completo de atención (activity log de los 4 tableros + capturas y alta local con nombre del capturista) - monday.service: getContactoDetalle (columnas por título), getHistorialItems (todos los eventos del activity log por items) y ApartadoMonday enriquecido (teléfono, email, precio final, fecha compra) - Landing: la lista de Contactos abre un modal por cliente con datos, propiedad, relaciones CRM (con quién atiende) y timeline de historial - Tests: 465 API + 12 landing, verde
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getContactos, type Contacto } from '../services/api'
|
||||
import { ModalContacto } from './ModalContacto'
|
||||
|
||||
interface Props {
|
||||
onVolver: () => void
|
||||
@@ -8,12 +9,14 @@ interface Props {
|
||||
/**
|
||||
* Vista interna "Contactos": lista del tablero Contactos de Monday (lo más
|
||||
* reciente primero). Vive dentro de la landing (no hay router); se abre desde
|
||||
* la tarjeta Contactos del grid.
|
||||
* la tarjeta Contactos del grid. Click en un contacto → modal con toda su
|
||||
* información, propiedad, quién lo atiende e historial de atención.
|
||||
*/
|
||||
export function VistaContactos({ onVolver }: Props) {
|
||||
const [contactos, setContactos] = useState<Contacto[] | null>(null)
|
||||
const [cargando, setCargando] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [seleccionado, setSeleccionado] = useState<Contacto | null>(null)
|
||||
|
||||
const cargar = useCallback(() => {
|
||||
setCargando(true)
|
||||
@@ -60,20 +63,35 @@ export function VistaContactos({ onVolver }: Props) {
|
||||
) : (
|
||||
<ul className="divide-y divide-[#E5E7EB] rounded-xl border border-[#E5E7EB] bg-white shadow-sm">
|
||||
{contactos.map((contacto) => (
|
||||
<li key={contacto.id} className="flex flex-wrap items-center gap-x-4 gap-y-1 px-6 py-4">
|
||||
<span className="text-sm font-semibold text-[#111827]">{contacto.nombre}</span>
|
||||
{contacto.estado && (
|
||||
<span className="rounded-full bg-blue-50 px-2.5 py-0.5 text-xs font-medium text-accent">
|
||||
{contacto.estado}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm text-[#64748B]">{contacto.telefono || 'Sin teléfono'}</span>
|
||||
<span className="text-sm text-[#64748B]">{contacto.email || 'Sin email'}</span>
|
||||
<li key={contacto.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSeleccionado(contacto)}
|
||||
className="flex w-full flex-wrap items-center gap-x-4 gap-y-1 px-6 py-4 text-left transition-colors duration-100 hover:bg-[#F8FAFC]"
|
||||
>
|
||||
<span className="text-sm font-semibold text-[#111827]">{contacto.nombre}</span>
|
||||
{contacto.estado && (
|
||||
<span className="rounded-full bg-blue-50 px-2.5 py-0.5 text-xs font-medium text-accent">
|
||||
{contacto.estado}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm text-[#64748B]">{contacto.telefono || 'Sin teléfono'}</span>
|
||||
<span className="text-sm text-[#64748B]">{contacto.email || 'Sin email'}</span>
|
||||
<span className="ml-auto text-xs font-medium text-accent">Ver detalle →</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{seleccionado && (
|
||||
<ModalContacto
|
||||
contactoId={seleccionado.id}
|
||||
nombre={seleccionado.nombre}
|
||||
onCerrar={() => setSeleccionado(null)}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user