fix(chat): ajustes de texto, layout e icone do menu
- Mudar texto 'Chat #' para 'Ticket #' no desktop - Passar ticketRef via URL para exibir numero correto - Ajustar tamanho da janela minimizada (240px) - Incluir ticketRef no checkMachineUpdates (Convex) - Ajustar padding dos botoes no chat web - Mudar icone 'Todos os tickets' para ClipboardList 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3c2d1824fb
commit
d20ebf7416
8 changed files with 35 additions and 25 deletions
|
|
@ -70,6 +70,7 @@ pub struct ChatPollResponse {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChatSessionSummary {
|
pub struct ChatSessionSummary {
|
||||||
pub ticket_id: String,
|
pub ticket_id: String,
|
||||||
|
pub ticket_ref: u64,
|
||||||
pub unread_count: u32,
|
pub unread_count: u32,
|
||||||
pub last_activity_at: i64,
|
pub last_activity_at: i64,
|
||||||
}
|
}
|
||||||
|
|
@ -923,7 +924,7 @@ async fn process_chat_update(
|
||||||
if let Some(session) = current_sessions.first() {
|
if let Some(session) = current_sessions.first() {
|
||||||
let label = format!("chat-{}", session.ticket_id);
|
let label = format!("chat-{}", session.ticket_id);
|
||||||
if app.get_webview_window(&label).is_none() {
|
if app.get_webview_window(&label).is_none() {
|
||||||
let _ = open_chat_window(app, &session.ticket_id);
|
let _ = open_chat_window(app, &session.ticket_id, session.ticket_ref);
|
||||||
// Minimizar imediatamente após abrir
|
// Minimizar imediatamente após abrir
|
||||||
let _ = set_chat_minimized(app, &session.ticket_id, true);
|
let _ = set_chat_minimized(app, &session.ticket_id, true);
|
||||||
}
|
}
|
||||||
|
|
@ -950,7 +951,7 @@ async fn process_chat_update(
|
||||||
// WINDOW MANAGEMENT
|
// WINDOW MANAGEMENT
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
|
fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64) -> Result<(), String> {
|
||||||
let label = format!("chat-{}", ticket_id);
|
let label = format!("chat-{}", ticket_id);
|
||||||
|
|
||||||
// Verificar se ja existe
|
// Verificar se ja existe
|
||||||
|
|
@ -980,7 +981,7 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str) -> Result<
|
||||||
};
|
};
|
||||||
|
|
||||||
// Usar query param ao inves de path para compatibilidade com SPA
|
// Usar query param ao inves de path para compatibilidade com SPA
|
||||||
let url_path = format!("index.html?view=chat&ticketId={}", ticket_id);
|
let url_path = format!("index.html?view=chat&ticketId={}&ticketRef={}", ticket_id, ticket_ref);
|
||||||
|
|
||||||
WebviewWindowBuilder::new(
|
WebviewWindowBuilder::new(
|
||||||
app,
|
app,
|
||||||
|
|
@ -989,7 +990,7 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str) -> Result<
|
||||||
)
|
)
|
||||||
.title("Chat de Suporte")
|
.title("Chat de Suporte")
|
||||||
.inner_size(380.0, 520.0)
|
.inner_size(380.0, 520.0)
|
||||||
.min_inner_size(220.0, 52.0) // Tamanho minimo para modo minimizado com badge
|
.min_inner_size(240.0, 52.0) // Tamanho minimo para modo minimizado com badge
|
||||||
.position(x, y)
|
.position(x, y)
|
||||||
.decorations(false) // Sem decoracoes nativas - usa header customizado
|
.decorations(false) // Sem decoracoes nativas - usa header customizado
|
||||||
.transparent(true) // Permite fundo transparente
|
.transparent(true) // Permite fundo transparente
|
||||||
|
|
@ -1005,8 +1006,8 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str) -> Result<
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
|
pub fn open_chat_window(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64) -> Result<(), String> {
|
||||||
open_chat_window_internal(app, ticket_id)
|
open_chat_window_internal(app, ticket_id, ticket_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
|
pub fn close_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
|
||||||
|
|
@ -1032,7 +1033,7 @@ pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bo
|
||||||
|
|
||||||
// Tamanhos - chip minimizado com margem extra para badge (absolute -top-1 -right-1)
|
// Tamanhos - chip minimizado com margem extra para badge (absolute -top-1 -right-1)
|
||||||
let (width, height) = if minimized {
|
let (width, height) = if minimized {
|
||||||
(220.0, 52.0) // Tamanho com folga para badge que fica fora do chip
|
(240.0, 52.0) // Tamanho com folga para "Ticket #XXX" e badge
|
||||||
} else {
|
} else {
|
||||||
(380.0, 520.0) // Tamanho expandido
|
(380.0, 520.0) // Tamanho expandido
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,10 @@ function getFileIcon(fileName: string) {
|
||||||
|
|
||||||
interface ChatWidgetProps {
|
interface ChatWidgetProps {
|
||||||
ticketId: string
|
ticketId: string
|
||||||
|
ticketRef?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
|
||||||
const [messages, setMessages] = useState<ChatMessage[]>([])
|
const [messages, setMessages] = useState<ChatMessage[]>([])
|
||||||
const [inputValue, setInputValue] = useState("")
|
const [inputValue, setInputValue] = useState("")
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
@ -267,7 +268,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
<div className="pointer-events-auto flex items-center gap-2 rounded-full bg-slate-200 px-4 py-2 text-slate-600 shadow-lg">
|
<div className="pointer-events-auto flex items-center gap-2 rounded-full bg-slate-200 px-4 py-2 text-slate-600 shadow-lg">
|
||||||
<MessageCircle className="size-4" />
|
<MessageCircle className="size-4" />
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{ticketInfo ? `Chat #${ticketInfo.ref}` : "Chat"}
|
{ticketRef ? `Ticket #${ticketRef}` : ticketInfo?.ref ? `Ticket #${ticketInfo.ref}` : "Chat"}
|
||||||
</span>
|
</span>
|
||||||
<span className="size-2 rounded-full bg-slate-400" />
|
<span className="size-2 rounded-full bg-slate-400" />
|
||||||
<span className="text-xs text-slate-500">Offline</span>
|
<span className="text-xs text-slate-500">Offline</span>
|
||||||
|
|
@ -287,7 +288,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
>
|
>
|
||||||
<MessageCircle className="size-4" />
|
<MessageCircle className="size-4" />
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
Chat #{ticketInfo?.ref}
|
Ticket #{ticketRef ?? ticketInfo?.ref}
|
||||||
</span>
|
</span>
|
||||||
<span className="size-2 rounded-full bg-emerald-400" />
|
<span className="size-2 rounded-full bg-emerald-400" />
|
||||||
<ChevronUp className="size-4" />
|
<ChevronUp className="size-4" />
|
||||||
|
|
@ -321,9 +322,9 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
Online
|
Online
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{ticketInfo && (
|
{(ticketRef || ticketInfo) && (
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
#{ticketInfo.ref} - {ticketInfo.agentName ?? "Suporte"}
|
Ticket #{ticketRef ?? ticketInfo?.ref} - {ticketInfo?.agentName ?? "Suporte"}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ let cached: ClientCache | null = null
|
||||||
|
|
||||||
type MachineUpdatePayload = {
|
type MachineUpdatePayload = {
|
||||||
hasActiveSessions: boolean
|
hasActiveSessions: boolean
|
||||||
sessions: Array<{ ticketId: string; unreadCount: number; lastActivityAt: number }>
|
sessions: Array<{ ticketId: string; ticketRef: number; unreadCount: number; lastActivityAt: number }>
|
||||||
totalUnread: number
|
totalUnread: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { ChatWidget } from "./ChatWidget"
|
import { ChatWidget } from "./ChatWidget"
|
||||||
|
|
||||||
export function ChatApp() {
|
export function ChatApp() {
|
||||||
// Obter ticketId da URL
|
// Obter ticketId e ticketRef da URL
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
const ticketId = params.get("ticketId")
|
const ticketId = params.get("ticketId")
|
||||||
|
const ticketRef = params.get("ticketRef")
|
||||||
|
|
||||||
if (!ticketId) {
|
if (!ticketId) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -13,7 +14,7 @@ export function ChatApp() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ChatWidget ticketId={ticketId} />
|
return <ChatWidget ticketId={ticketId} ticketRef={ticketRef ? Number(ticketRef) : undefined} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ChatWidget }
|
export { ChatWidget }
|
||||||
|
|
|
||||||
|
|
@ -1098,10 +1098,10 @@ const resolvedAppUrl = useMemo(() => {
|
||||||
|
|
||||||
// Abre/minimiza chat quando aparecem novas não lidas
|
// Abre/minimiza chat quando aparecem novas não lidas
|
||||||
if (hasSessions && totalUnread > prevUnread) {
|
if (hasSessions && totalUnread > prevUnread) {
|
||||||
const ticketId = payload.sessions[0].ticketId
|
const session = payload.sessions[0]
|
||||||
invoke("open_chat_window", { ticketId }).catch(console.error)
|
invoke("open_chat_window", { ticketId: session.ticketId, ticketRef: session.ticketRef }).catch(console.error)
|
||||||
// Minimiza para não ser intrusivo
|
// Minimiza para não ser intrusivo
|
||||||
invoke("set_chat_minimized", { ticketId, minimized: true }).catch(console.error)
|
invoke("set_chat_minimized", { ticketId: session.ticketId, minimized: true }).catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
prevUnread = totalUnread
|
prevUnread = totalUnread
|
||||||
|
|
|
||||||
|
|
@ -524,11 +524,18 @@ export const checkMachineUpdates = query({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionSummaries = sessions.map((s) => ({
|
// Buscar tickets para obter o reference
|
||||||
ticketId: s.ticketId,
|
const sessionSummaries = await Promise.all(
|
||||||
unreadCount: s.unreadByMachine ?? 0,
|
sessions.map(async (s) => {
|
||||||
lastActivityAt: s.lastActivityAt,
|
const ticket = await ctx.db.get(s.ticketId)
|
||||||
}))
|
return {
|
||||||
|
ticketId: s.ticketId,
|
||||||
|
ticketRef: ticket?.reference ?? 0,
|
||||||
|
unreadCount: s.unreadByMachine ?? 0,
|
||||||
|
lastActivityAt: s.lastActivityAt,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const totalUnread = sessionSummaries.reduce((sum, s) => sum + s.unreadCount, 0)
|
const totalUnread = sessionSummaries.reduce((sum, s) => sum + s.unreadCount, 0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ const navigation: NavigationGroup[] = [
|
||||||
icon: Ticket,
|
icon: Ticket,
|
||||||
requiredRole: "staff",
|
requiredRole: "staff",
|
||||||
children: [
|
children: [
|
||||||
{ title: "Todos os tickets", url: "/tickets", icon: Ticket, requiredRole: "staff" },
|
{ title: "Todos os tickets", url: "/tickets", icon: ClipboardList, requiredRole: "staff" },
|
||||||
{ title: "Resolvidos", url: "/tickets/resolved", icon: ShieldCheck, requiredRole: "staff" },
|
{ title: "Resolvidos", url: "/tickets/resolved", icon: ShieldCheck, requiredRole: "staff" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ export function TicketChatPanel({ ticketId }: TicketChatPanelProps) {
|
||||||
return (
|
return (
|
||||||
<Card className="flex flex-col overflow-hidden border-slate-200">
|
<Card className="flex flex-col overflow-hidden border-slate-200">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<CardHeader className="flex flex-row items-center justify-between gap-2 border-b border-slate-100 bg-slate-50 px-4 py-3">
|
<CardHeader className="flex flex-row items-center justify-between gap-2 border-b border-slate-100 bg-slate-50 px-4 pr-3 py-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-full bg-black text-white">
|
<div className="flex size-10 items-center justify-center rounded-full bg-black text-white">
|
||||||
<MessageCircle className="size-5" />
|
<MessageCircle className="size-5" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue