diff --git a/src/components/tickets/priority-select.tsx b/src/components/tickets/priority-select.tsx index e244d4f..e4e9f2c 100644 --- a/src/components/tickets/priority-select.tsx +++ b/src/components/tickets/priority-select.tsx @@ -36,7 +36,15 @@ export function PriorityIcon({ value }: { value: TicketPriority }) { return } -export function PrioritySelect({ ticketId, value }: { ticketId: string; value: TicketPriority }) { +export function PrioritySelect({ + ticketId, + value, + className, +}: { + ticketId: string + value: TicketPriority + className?: string +}) { const updatePriority = useMutation(api.tickets.updatePriority) const [priority, setPriority] = useState(value) const { convexUserId } = useAuth() @@ -59,7 +67,7 @@ export function PrioritySelect({ ticketId, value }: { ticketId: string; value: T } }} > - + @@ -68,7 +76,11 @@ export function PrioritySelect({ ticketId, value }: { ticketId: string; value: T - + e.preventDefault()} + className="rounded-lg border border-slate-200 bg-white text-neutral-800 shadow-sm" + > {(["LOW", "MEDIUM", "HIGH", "URGENT"] as const).map((option) => ( diff --git a/src/components/tickets/recent-tickets-panel.tsx b/src/components/tickets/recent-tickets-panel.tsx index 927ca26..3c8bb48 100644 --- a/src/components/tickets/recent-tickets-panel.tsx +++ b/src/components/tickets/recent-tickets-panel.tsx @@ -17,20 +17,10 @@ import { TicketStatusBadge } from "@/components/tickets/status-badge" import { useAuth } from "@/lib/auth-client" import { cn } from "@/lib/utils" -const channelLabel: Record = { - EMAIL: "E-mail", - WHATSAPP: "WhatsApp", - CHAT: "Chat", - PHONE: "Telefone", - API: "API", - MANUAL: "Manual", -} - function TicketRow({ ticket, entering }: { ticket: Ticket; entering: boolean }) { const queueLabel = ticket.queue ?? "Sem fila" const requesterName = ticket.requester.name ?? ticket.requester.email ?? "Solicitante" const categoryBadges = [ticket.category?.name, ticket.subcategory?.name].filter((value): value is string => Boolean(value)) - const channel = channelLabel[ticket.channel] ?? ticket.channel const badgeClass = "rounded-lg border border-slate-300 px-3.5 py-1.5 text-sm font-medium text-slate-600 transition-colors" @@ -65,17 +55,17 @@ function TicketRow({ ticket, entering }: { ticket: Ticket; entering: boolean }) {formatDistanceToNow(ticket.updatedAt, { addSuffix: true, locale: ptBR })} - {categoryBadges.length > 0 ? ( -
- {categoryBadges.map((label) => ( +
+ {categoryBadges.length > 0 ? ( + categoryBadges.map((label) => ( {label} - ))} -
- ) : ( - {channel} - )} + )) + ) : ( + Sem categoria + )} +
diff --git a/src/components/tickets/ticket-queue-summary.tsx b/src/components/tickets/ticket-queue-summary.tsx index b9171d0..329731a 100644 --- a/src/components/tickets/ticket-queue-summary.tsx +++ b/src/components/tickets/ticket-queue-summary.tsx @@ -36,29 +36,46 @@ export function TicketQueueSummaryCards({ queues }: TicketQueueSummaryProps) { } return ( -
+
{data.map((queue) => { const total = queue.pending + queue.waiting const breachPercent = total === 0 ? 0 : Math.round((queue.breached / total) * 100) return ( - - + + Fila - {queue.name} + + {queue.name} + - -
-
-

Pendentes

-

{queue.pending}

+ +
+
+

+ Pendentes +

+

+ {queue.pending} +

-
-

Aguardando resposta

-

{queue.waiting}

+
+

+ Aguardando resposta +

+

+ {queue.waiting} +

-
-

Violados

-

{queue.breached}

+
+

+ Violados +

+

+ {queue.breached} +

diff --git a/src/components/tickets/tickets-table.tsx b/src/components/tickets/tickets-table.tsx index 2723554..f6344a8 100644 --- a/src/components/tickets/tickets-table.tsx +++ b/src/components/tickets/tickets-table.tsx @@ -4,9 +4,8 @@ import { useEffect, useRef, useState } from "react" import { useRouter } from "next/navigation" import { format, formatDistanceToNowStrict } from "date-fns" import { ptBR } from "date-fns/locale" -import { type LucideIcon, Code, FileText, Mail, MessageCircle, MessageSquare, Phone } from "lucide-react" -import type { Ticket, TicketChannel } from "@/lib/schemas/ticket" +import type { Ticket } from "@/lib/schemas/ticket" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { Card, CardContent } from "@/components/ui/card" @@ -25,26 +24,10 @@ import { cn } from "@/lib/utils" import { deriveServerOffset, toServerTimestamp } from "@/components/tickets/ticket-timer.utils" import { getTicketStatusLabel, getTicketStatusTextClass } from "@/lib/ticket-status-style" -const channelLabel: Record = { - EMAIL: "E-mail", - WHATSAPP: "WhatsApp", - CHAT: "Chat", - PHONE: "Telefone", - API: "API", - MANUAL: "Manual", -} - -const channelIcon: Record = { - EMAIL: Mail, - WHATSAPP: MessageCircle, - CHAT: MessageSquare, - PHONE: Phone, - API: Code, - MANUAL: FileText, -} - -const cellClass = "px-4 py-4 align-middle text-sm text-neutral-700 whitespace-normal first:pl-5 last:pr-6" -const channelIconBadgeClass = "inline-flex size-8 items-center justify-center rounded-full border border-slate-200 bg-slate-50 text-neutral-700" +const cellClass = + "px-3 py-4 sm:px-4 xl:px-3 xl:py-3 align-middle text-sm text-neutral-700 whitespace-normal " + + "first:pl-3 last:pr-3 sm:first:pl-4 sm:last:pr-4 xl:first:pl-4 xl:last:pr-4" +const borderedCellClass = `${cellClass} xl:border-l xl:border-slate-200` const categoryChipClass = "inline-flex items-center gap-1 rounded-full bg-slate-200/60 px-2.5 py-1 text-[11px] font-medium text-neutral-700" const tableRowClass = "group border-b border-slate-100 text-sm transition-colors hover:bg-slate-100/70 last:border-none" @@ -76,16 +59,16 @@ function AssigneeCell({ ticket }: { ticket: Ticket }) { .join("") return ( -
+
{initials} -
- +
+ {ticket.assignee.name} - + {ticket.assignee.teams?.[0] ?? "Agente"}
@@ -137,166 +120,172 @@ export function TicketsTable({ tickets, enteringIds }: TicketsTableProps) { return ( -
- +
+
- - Ticket - - - Assunto - - - Fila - - - Canal - - - Empresa - - - Prioridade - - - Status - - - Tempo - - - Responsável - - - Atualizado - - - - - {safeTickets.map((ticket) => { - const ChannelIcon = channelIcon[ticket.channel] ?? MessageCircle - const rowClass = cn( - `${tableRowClass} cursor-pointer`, - enteringIds?.has(ticket.id) ? "recent-ticket-enter" : undefined, - ) + + Ticket + + + Assunto + + + Empresa + + + Prioridade + + + Fila + + + Status + + + Tempo + + + Responsável + + + Atualizado + + + + + {safeTickets.map((ticket) => { + const rowClass = cn( + `${tableRowClass} cursor-pointer`, + enteringIds?.has(ticket.id) ? "recent-ticket-enter" : undefined, + ) - return ( - router.push(`/tickets/${ticket.id}`)} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault() - router.push(`/tickets/${ticket.id}`) - } - }} - > - -
- - #{ticket.reference} - - + return ( + router.push(`/tickets/${ticket.id}`)} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault() + router.push(`/tickets/${ticket.id}`) + } + }} + > + +
+ + #{ticket.reference} + + + {ticket.queue ?? "Sem fila"} + +
+
+ +
+ + {ticket.subject} + + + {ticket.summary ?? "Sem resumo"} + +
+ {ticket.category ? ( + + {ticket.category.name} + {ticket.subcategory ? ( + + {" "} + • {ticket.subcategory.name} + + ) : null} + + ) : ( + Sem categoria + )} +
+
+
+ + +
-
- -
- - {ticket.subject} - - - {ticket.summary ?? "Sem resumo"} - -
- {ticket.requester.name} - {ticket.category ? ( - - {ticket.category.name} - {ticket.subcategory ? • {ticket.subcategory.name} : null} - - ) : ( - Sem categoria - )} -
-
-
- - - - - -
- - {getTicketStatusLabel(ticket.status)} - - {ticket.metrics?.timeWaitingMinutes ? ( - - Em espera há {ticket.metrics.timeWaitingMinutes} min + + +
+ + {getTicketStatusLabel(ticket.status)} - ) : null} -
-
- - - -
- - {`há cerca de ${formatDistanceToNowStrict(ticket.updatedAt, { locale: ptBR })}`} - - - {format(ticket.updatedAt, "dd/MM/yyyy HH:mm")} - -
-
- + {ticket.metrics?.timeWaitingMinutes ? ( + + Em espera há {ticket.metrics.timeWaitingMinutes} min + + ) : null} +
+
+ + + +
+ + {`há ${formatDistanceToNowStrict(ticket.updatedAt, { locale: ptBR })}`} + + + {format(ticket.updatedAt, "dd/MM/yyyy HH:mm")} + +
+
+
) })} -
+
{safeTickets.length === 0 && ( diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx index 5b52761..6dc820f 100644 --- a/src/components/ui/sidebar.tsx +++ b/src/components/ui/sidebar.tsx @@ -27,9 +27,9 @@ import { const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" -const SIDEBAR_WIDTH_MOBILE = "18rem" -const SIDEBAR_WIDTH_ICON = "3rem" +const SIDEBAR_WIDTH = "clamp(12rem, 15vw + 1.5rem, 16.5rem)" +const SIDEBAR_WIDTH_MOBILE = "18rem" +const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b" type SidebarContextProps = { @@ -314,17 +314,17 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { ) } -function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { - return ( -
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { + return ( +
) } diff --git a/src/lib/ticket-status-style.ts b/src/lib/ticket-status-style.ts index c402a89..0b4a522 100644 --- a/src/lib/ticket-status-style.ts +++ b/src/lib/ticket-status-style.ts @@ -19,7 +19,7 @@ const META: Record = { summaryTone: "muted", chipClass: "bg-slate-100 text-neutral-700 ring-1 ring-slate-200", badgeClass: "border border-slate-200 bg-slate-100 text-neutral-700", - tableTextClass: "text-neutral-600", + tableTextClass: "text-slate-700", dotClass: "bg-slate-400", }, AWAITING_ATTENDANCE: { @@ -27,7 +27,7 @@ const META: Record = { summaryTone: "info", chipClass: "bg-sky-100 text-sky-700 ring-1 ring-sky-200", badgeClass: "border border-sky-200 bg-sky-100 text-sky-700", - tableTextClass: "text-sky-700", + tableTextClass: "text-[#0a4760]", dotClass: "bg-sky-500", }, PAUSED: { @@ -35,7 +35,7 @@ const META: Record = { summaryTone: "warning", chipClass: "bg-amber-100 text-amber-800 ring-1 ring-amber-200", badgeClass: "border border-amber-200 bg-amber-50 text-amber-700", - tableTextClass: "text-amber-700", + tableTextClass: "text-[#7a5901]", dotClass: "bg-amber-400", }, RESOLVED: { @@ -43,7 +43,7 @@ const META: Record = { summaryTone: "success", chipClass: "bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200", badgeClass: "border border-emerald-200 bg-emerald-50 text-emerald-700", - tableTextClass: "text-emerald-700", + tableTextClass: "text-[#1f6a45]", dotClass: "bg-emerald-500", }, }