feat: historial de correos en ticket + fechas fijas a America/Santiago
This commit is contained in:
1 parent
eaa96fbc24
commit
38e21d20dd
6 files changed
+132
-7
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
type EmailLogEntry,
|
||||
} from "@/lib/api";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import Modal from "@/components/Modal";
|
||||
|
||||
const PLACEHOLDERS = [
|
||||
@@ -415,7 +416,7 @@ export default function EmailPage() {
|
||||
log.map((e) => (
|
||||
<tr key={e.id}>
|
||||
<td className="text-muted small">
|
||||
{new Date(e.createdAt).toLocaleString("es-CL")}
|
||||
{formatDateTime(e.createdAt)}
|
||||
</td>
|
||||
<td>
|
||||
<code className="small">{e.template}</code>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useAuth } from "@/lib/auth";
|
||||
import {
|
||||
getTicket,
|
||||
getComments,
|
||||
getTicketEmails,
|
||||
addComment,
|
||||
updateTicketStatus,
|
||||
assignTicket,
|
||||
@@ -15,9 +16,12 @@ import {
|
||||
type Comment,
|
||||
type Group,
|
||||
type TeamMember,
|
||||
type TicketSentEmail,
|
||||
type TicketReceivedEmail,
|
||||
} from "@/lib/api";
|
||||
import BadgeStatus from "@/components/BadgeStatus";
|
||||
import BadgePriority from "@/components/BadgePriority";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import Link from "next/link";
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
@@ -37,6 +41,8 @@ export default function TicketDetailPage() {
|
||||
|
||||
const [ticket, setTicket] = useState<Ticket | null>(null);
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [sentEmails, setSentEmails] = useState<TicketSentEmail[]>([]);
|
||||
const [receivedEmails, setReceivedEmails] = useState<TicketReceivedEmail[]>([]);
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [team, setTeam] = useState<TeamMember[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -52,12 +58,15 @@ export default function TicketDetailPage() {
|
||||
Promise.all([
|
||||
getTicket(id).catch(() => null),
|
||||
getComments(id).catch(() => []),
|
||||
getTicketEmails(id).catch(() => ({ sent: [], received: [] })),
|
||||
getGroups().catch(() => []),
|
||||
getTeam().catch(() => []),
|
||||
])
|
||||
.then(([ticketData, commentsData, groupsData, teamData]) => {
|
||||
.then(([ticketData, commentsData, emailsData, groupsData, teamData]) => {
|
||||
setTicket(ticketData);
|
||||
setComments(commentsData);
|
||||
setSentEmails(emailsData.sent ?? []);
|
||||
setReceivedEmails(emailsData.received ?? []);
|
||||
setGroups(groupsData);
|
||||
setTeam(teamData);
|
||||
})
|
||||
@@ -303,7 +312,7 @@ export default function TicketDetailPage() {
|
||||
</div>
|
||||
<div className="comment-text">{c.content}</div>
|
||||
<div className="comment-time">
|
||||
{new Date(c.createdAt).toLocaleString("es-CL")}
|
||||
{formatDateTime(c.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,6 +326,74 @@ export default function TicketDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Historial de correos: enviados desde soporte + respuestas
|
||||
recibidas, integrados al ticket (hilo email <-> ticket) */}
|
||||
<div className="card mb-4">
|
||||
<div className="card-header d-flex align-items-center gap-2">
|
||||
<i className="bi bi-envelope-at" style={{ color: "var(--primary)" }}></i>
|
||||
Historial de correos ({sentEmails.length + receivedEmails.length})
|
||||
</div>
|
||||
<div className="card-body" style={{ maxHeight: "400px", overflowY: "auto" }}>
|
||||
{sentEmails.length === 0 && receivedEmails.length === 0 ? (
|
||||
<div className="text-center text-muted py-4">
|
||||
<i className="bi bi-envelope fs-3 d-block mb-2" style={{ opacity: 0.3 }}></i>
|
||||
Sin correos asociados a este ticket
|
||||
</div>
|
||||
) : (
|
||||
[...sentEmails.map((e) => ({
|
||||
key: `sent-${e.id}`,
|
||||
dir: "sent" as const,
|
||||
who: e.recipient,
|
||||
subject: e.subject,
|
||||
extra: e.template,
|
||||
status: e.status,
|
||||
error: e.error,
|
||||
createdAt: e.createdAt,
|
||||
})), ...receivedEmails.map((e, i) => ({
|
||||
key: `recv-${i}-${e.createdAt}`,
|
||||
dir: "received" as const,
|
||||
who: e.authorEmail,
|
||||
subject: e.subject,
|
||||
extra: e.status === "inserted" ? "Agregado como comentario" : (e.error || e.status),
|
||||
status: e.status,
|
||||
error: e.error,
|
||||
createdAt: e.createdAt,
|
||||
}))]
|
||||
.sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt))
|
||||
.map((m) => (
|
||||
<div key={m.key} className="d-flex gap-2 mb-3">
|
||||
<span
|
||||
className={`badge ${m.dir === "sent" ? "bg-primary" : "bg-success"} align-self-start mt-1`}
|
||||
title={m.dir === "sent" ? "Enviado desde soporte" : "Respuesta recibida"}
|
||||
>
|
||||
<i className={`bi ${m.dir === "sent" ? "bi-send" : "bi-inbox"}`}></i>
|
||||
</span>
|
||||
<div className="flex-grow-1">
|
||||
<div className="fw-semibold" style={{ fontSize: "0.85rem" }}>
|
||||
{m.dir === "sent" ? "Para: " : "De: "}{m.who}
|
||||
</div>
|
||||
<div className="text-muted" style={{ fontSize: "0.85rem" }}>{m.subject}</div>
|
||||
<div className="d-flex gap-2 align-items-center flex-wrap">
|
||||
<small className="text-muted">{formatDateTime(m.createdAt)}</small>
|
||||
<small className="text-muted">·</small>
|
||||
<small className="text-muted">{m.extra}</small>
|
||||
{m.status !== "sent" && m.status !== "inserted" && (
|
||||
<span className="badge bg-warning text-dark" title={m.error || ""}>
|
||||
{m.status}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<small className="text-muted d-block mt-2">
|
||||
Las respuestas con etiqueta HD-{String(id).padStart(6, "0")} en el asunto
|
||||
se agregan solas como comentarios (ingesta cada 5 min).
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comment Form */}
|
||||
<div className="comment-form">
|
||||
<form onSubmit={handleAddComment}>
|
||||
@@ -384,11 +461,11 @@ export default function TicketDetailPage() {
|
||||
</div>
|
||||
<div className="ticket-info-row">
|
||||
<span className="label">Creado</span>
|
||||
<span className="value">{new Date(ticket.createdAt).toLocaleString("es-CL")}</span>
|
||||
<span className="value">{formatDateTime(ticket.createdAt)}</span>
|
||||
</div>
|
||||
<div className="ticket-info-row">
|
||||
<span className="label">Actualizado</span>
|
||||
<span className="value">{new Date(ticket.updatedAt).toLocaleString("es-CL")}</span>
|
||||
<span className="value">{formatDateTime(ticket.updatedAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useAuth } from "@/lib/auth";
|
||||
import BadgeStatus from "@/components/BadgeStatus";
|
||||
import BadgePriority from "@/components/BadgePriority";
|
||||
import PaginationComponent from "@/components/Pagination";
|
||||
import { formatDate } from "@/lib/format";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function TicketsPage() {
|
||||
@@ -204,7 +205,7 @@ export default function TicketsPage() {
|
||||
<td><BadgePriority priority={ticket.priority} /></td>
|
||||
<td>{ticket.assigneeName || <span className="text-muted">Sin asignar</span>}</td>
|
||||
<td className="text-muted" style={{ fontSize: "0.85rem" }}>
|
||||
{new Date(ticket.updatedAt).toLocaleDateString("es-CL")}
|
||||
{formatDate(ticket.updatedAt)}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
|
||||
Reference in new issue
Block a user