Fix chat widget UI and allow attachment-only messages

- Allow sending messages with only attachments (no text required)
- Change "Chat Ativo" header to just "Chat"
- Replace Headphones icon with MessageCircle for own messages
- Replace PhoneOff icon with XCircle for end chat button

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-07 03:09:51 -03:00
parent 60e98dd47c
commit f45ee91804
2 changed files with 18 additions and 15 deletions

View file

@ -3178,14 +3178,17 @@ export const postChatMessage = mutation({
}
const trimmedBody = body.replace(/\r\n/g, "\n").trim()
if (trimmedBody.length === 0) {
throw new ConvexError("Digite uma mensagem para enviar no chat")
const files = attachments ?? []
// Validar que há pelo menos texto ou anexo
if (trimmedBody.length === 0 && files.length === 0) {
throw new ConvexError("Digite uma mensagem ou anexe um arquivo")
}
if (trimmedBody.length > 4000) {
throw new ConvexError("Mensagem muito longa (máx. 4000 caracteres)")
}
const files = attachments ?? []
// Validar anexos
if (files.length > 5) {
throw new ConvexError("Envie até 5 arquivos por mensagem")
}
@ -3196,13 +3199,14 @@ export const postChatMessage = mutation({
}
}
const normalizedBody = await normalizeTicketMentions(ctx, trimmedBody, { user: participant.user, role: participant.role ?? "" }, ticketDoc.tenantId)
const plainLength = plainTextLength(normalizedBody)
if (plainLength === 0) {
throw new ConvexError("A mensagem está vazia após a formatação")
}
if (plainLength > 4000) {
throw new ConvexError("Mensagem muito longa (máx. 4000 caracteres)")
// Normalizar corpo apenas se houver texto
let normalizedBody = ""
if (trimmedBody.length > 0) {
normalizedBody = await normalizeTicketMentions(ctx, trimmedBody, { user: participant.user, role: participant.role ?? "" }, ticketDoc.tenantId)
const plainLength = plainTextLength(normalizedBody)
if (plainLength > 4000) {
throw new ConvexError("Mensagem muito longa (máx. 4000 caracteres)")
}
}
const authorSnapshot: CommentAuthorSnapshot = {

View file

@ -22,10 +22,9 @@ import {
X,
Minimize2,
User,
Headphones,
ChevronDown,
WifiOff,
PhoneOff,
XCircle,
Paperclip,
FileText,
Image as ImageIcon,
@ -428,7 +427,7 @@ export function ChatWidget() {
</div>
<div>
<div className="flex items-center gap-2">
<p className="text-sm font-semibold text-slate-900">Chat Ativo</p>
<p className="text-sm font-semibold text-slate-900">Chat</p>
{/* Indicador online/offline */}
{liveChat?.hasMachine && (
machineOnline ? (
@ -464,7 +463,7 @@ export function ChatWidget() {
{isEndingChat ? (
<Spinner className="size-3.5" />
) : (
<PhoneOff className="size-3.5" />
<XCircle className="size-3.5" />
)}
<span className="hidden sm:inline">Encerrar</span>
</Button>
@ -548,7 +547,7 @@ export function ChatWidget() {
isOwn ? "bg-black text-white" : "bg-slate-200 text-slate-600"
)}
>
{isOwn ? <Headphones className="size-3.5" /> : <User className="size-3.5" />}
{isOwn ? <MessageCircle className="size-3.5" /> : <User className="size-3.5" />}
</div>
<div
className={cn(