"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { browserFetch } from "../lib/api"; type User = { id: string; email: string; role: string; }; type MakerProfile = { id: string; slug: string; business_name: string | null; status: string; }; type MessageAccess = "customer" | "maker" | "admin"; type InquiryStatus = "open" | "agreed" | "completed" | "rejected" | "incident"; type Conversation = { id: string; business_name: string; maker_slug: string; customer_name: string; customer_id: string; need_text: string; size_text: string | null; has_model: boolean; material_preference: string | null; quantity: number; urgency: string; delivery_type: string; source_type: string; status: InquiryStatus; rejection_reason: string | null; proposal_price_cents: number | null; proposal_quantity: number | null; proposal_lead_time_days: number | null; proposal_note: string | null; proposal_created_at: string | null; agreed_at: string | null; maker_completed_at: string | null; customer_completed_at: string | null; completed_at: string | null; incident_reason: string | null; incident_detail: string | null; last_message: string | null; last_message_at: string | null; last_sender_user_id: string | null; message_count: number; created_at: string; }; type Message = { id: string; sender_user_id: string; full_name: string; body: string; created_at: string; }; type ConversationDetail = { inquiry: Conversation & { maker_user_id: string; customer_name: string; }; messages: Message[]; }; type FilterKey = "all" | "consultas" | "active"; const incidentOptions = [ "El trabajo no fue entregado", "El resultado no fue el acordado", "Hubo un problema con el plazo", "El maker no responde", "Otro" ]; function formatTime(value?: string | null) { if (!value) { return ""; } return new Intl.DateTimeFormat("es-AR", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(value)); } function formatMoney(value?: number | null) { if (!value) { return ""; } return `$${Math.round(value / 100).toLocaleString("es-AR")}`; } function statusInfo(conversation: Conversation, userId?: string) { if (conversation.status === "completed") { return { label: "Finalizado", tone: "success", icon: "✓" }; } if (conversation.status === "rejected") { return { label: "Rechazado", tone: "danger", icon: "×" }; } if (conversation.status === "incident") { return { label: "Incidencia", tone: "danger", icon: "!" }; } if (conversation.status === "agreed") { return { label: "Trabajo acordado", tone: "info", icon: "↔" }; } if (conversation.message_count <= 1) { return { label: "Nueva consulta", tone: "info", icon: "✉" }; } if (conversation.last_sender_user_id === userId) { return { label: "Esperando respuesta", tone: "warning", icon: "○" }; } return { label: "En conversacion", tone: "success", icon: "●" }; } function deliveryLabel(value: string) { return value === "local" ? "Retiro / local" : "Envio"; } function urgencyLabel(value: string) { if (value === "high") { return "Urgente"; } if (value === "low") { return "Sin urgencia"; } return "Esta semana"; } export default function MessagesClient() { const [loading, setLoading] = useState(true); const [user, setUser] = useState(null); const [messageAccess, setMessageAccess] = useState("customer"); const [conversations, setConversations] = useState([]); const [selectedId, setSelectedId] = useState(""); const [detail, setDetail] = useState(null); const [filter, setFilter] = useState("all"); const [reply, setReply] = useState(""); const [review, setReview] = useState({ rating: 5, comment: "" }); const [incident, setIncident] = useState({ reason: incidentOptions[0], detail: "" }); const [showActions, setShowActions] = useState(false); const [status, setStatus] = useState(""); async function loadConversations(preferredId?: string, access = messageAccess) { if (access === "admin") { setConversations([]); setSelectedId(""); setDetail(null); return; } const scope = access === "maker" ? "maker" : "customer"; const data = await browserFetch<{ conversations: Conversation[] }>(`/account/conversations?scope=${scope}`); setConversations(data.conversations); setSelectedId((current) => preferredId || current || data.conversations[0]?.id || ""); } async function refreshDetail(id = selectedId) { if (!id) { return; } const data = await browserFetch(`/inquiries/${id}`); setDetail(data); } async function refreshAll(id = selectedId) { await Promise.all([loadConversations(id), refreshDetail(id)]); } useEffect(() => { let active = true; async function load() { setLoading(true); try { const me = await browserFetch<{ user: User | null; makerProfile: MakerProfile | null }>("/auth/me"); if (!active) { return; } setUser(me.user); const access: MessageAccess = me.user?.role === "admin" ? "admin" : me.makerProfile && me.makerProfile.status !== "draft" ? "maker" : "customer"; setMessageAccess(access); if (me.user) { await loadConversations(undefined, access); } } catch (error) { if (active) { setStatus(error instanceof Error ? error.message : "No se pudo cargar mensajes"); } } finally { if (active) { setLoading(false); } } } void load(); return () => { active = false; }; }, []); useEffect(() => { if (!selectedId || !user) { setDetail(null); return; } let active = true; async function loadDetail() { try { const data = await browserFetch(`/inquiries/${selectedId}`); if (active) { setDetail(data); setShowActions(false); } } catch (error) { if (active) { setStatus(error instanceof Error ? error.message : "No se pudo abrir la conversacion"); } } } void loadDetail(); return () => { active = false; }; }, [selectedId, user]); const filteredConversations = useMemo(() => { if (filter === "consultas") { return conversations.filter((conversation) => conversation.status === "open"); } if (filter === "active") { return conversations.filter((conversation) => ["open", "agreed", "incident"].includes(conversation.status)); } return conversations; }, [conversations, filter]); const accessCopy = messageAccess === "maker" ? { eyebrow: "Consultas maker", title: "Bandeja de clientes", empty: "No hay consultas recibidas para este filtro.", auth: "Inicia sesion como maker para responder consultas de clientes." } : { eyebrow: "Mensajes", title: "Bandeja de consultas", empty: "No hay conversaciones para este filtro.", auth: "Inicia sesion para ver tus consultas y respuestas de los makers." }; async function sendReply(event: React.FormEvent) { event.preventDefault(); const body = reply.trim(); if (!selectedId || !body) { return; } setStatus("Enviando..."); try { await browserFetch(`/inquiries/${selectedId}/messages`, { method: "POST", body: JSON.stringify({ body }) }); setReply(""); setStatus(""); await refreshAll(); } catch (error) { setStatus(error instanceof Error ? error.message : "No se pudo enviar el mensaje"); } } async function acceptProposal() { if (!selectedId) { return; } setStatus("Aceptando propuesta..."); try { await browserFetch(`/inquiries/${selectedId}/accept-proposal`, { method: "POST" }); setStatus(""); await refreshAll(); } catch (error) { setStatus(error instanceof Error ? error.message : "No se pudo aceptar la propuesta"); } } async function confirmCompleted() { if (!selectedId) { return; } setStatus("Confirmando finalizacion..."); try { await browserFetch(`/inquiries/${selectedId}/complete`, { method: "POST" }); setStatus(""); await refreshAll(); } catch (error) { setStatus(error instanceof Error ? error.message : "No se pudo confirmar"); } } async function submitReview(event: React.FormEvent) { event.preventDefault(); if (!detail) { return; } setStatus("Publicando resena..."); try { await browserFetch("/reviews", { method: "POST", body: JSON.stringify({ inquiryId: detail.inquiry.id, ratingOverall: review.rating, ratingQuality: review.rating, ratingCommunication: review.rating, ratingValue: review.rating, comment: review.comment }) }); setReview({ rating: 5, comment: "" }); setStatus("Resena publicada."); } catch (error) { setStatus(error instanceof Error ? error.message : "No se pudo publicar la resena"); } } async function submitIncident(event: React.FormEvent) { event.preventDefault(); if (!selectedId) { return; } setStatus("Reportando incidencia..."); try { await browserFetch(`/inquiries/${selectedId}/incident`, { method: "POST", body: JSON.stringify(incident) }); setIncident({ reason: incidentOptions[0], detail: "" }); setStatus(""); await refreshAll(); } catch (error) { setStatus(error instanceof Error ? error.message : "No se pudo reportar la incidencia"); } } if (loading) { return (
); } if (!user) { return (

Mensajes

{accessCopy.auth}

Entrar
); } if (messageAccess === "admin") { return (
Mensajes administrador

Bandeja administrativa

Las consultas operativas, incidencias y reportes del administrador se gestionan desde el backoffice para mantener trazabilidad.

); } const current = detail?.inquiry; const currentInfo = current ? statusInfo(current, user.id) : null; const currentDisplayName = current ? messageAccess === "maker" ? current.customer_name : current.business_name : ""; const whatsappText = current ? encodeURIComponent(`Hola, soy ${detail.inquiry.customer_name}. Te contacto por la consulta "${current.need_text}".`) : ""; return (
{current && detail ? ( <>
{currentDisplayName.slice(0, 2).toUpperCase()}

{currentDisplayName}

{currentInfo?.icon} {currentInfo?.label}
Consulta estructurada {current.need_text} Ver perfil del maker
Cantidad {current.quantity || 1} Urgencia {urgencyLabel(current.urgency)} Entrega {deliveryLabel(current.delivery_type)} Material {current.material_preference || "A definir"} Medidas {current.size_text || "Sin medidas"} Adjuntos {current.has_model ? "Con archivo" : "Sin archivo"}
{showActions ? (
Continuar por WhatsApp Continuar por email
) : null}
{detail.messages.map((message) => { const isMine = message.sender_user_id === user.id; return (
{isMine ? "Tu" : currentDisplayName} {formatTime(message.created_at)}

{message.body}

); })} {current.proposal_price_cents ? (
↔
{current.status === "agreed" || current.status === "completed" ? "Trabajo acordado" : "Propuesta de trabajo"}
Trabajo
{current.need_text}
Cantidad
{current.proposal_quantity || current.quantity || 1}
Precio
{formatMoney(current.proposal_price_cents)}
Plazo
{current.proposal_lead_time_days} dias
{current.proposal_note ?

{current.proposal_note}

: null} {current.status === "open" ? ( ) : null}
) : null} {current.status === "completed" ? (
✓
Trabajo finalizado

Ambas partes confirmaron que el trabajo fue completado.

Finalizado el {formatTime(current.completed_at)}
) : null} {current.status === "rejected" ? (
×
Consulta rechazada

{current.rejection_reason || "El maker no puede realizar este trabajo."}

Ver mas alternativas
) : null} {current.status === "incident" ? (
!
Incidencia abierta

{current.incident_reason}: {current.incident_detail}

) : null}
{current.status === "agreed" || current.status === "open" ? ( ) : null} {current.status !== "incident" && current.status !== "rejected" ? ( ) : null}
{current.status === "completed" ? (
Dejar resena verificada
{[1, 2, 3, 4, 5].map((star) => ( ))}