ui: header cleanup (edit icon, time tooltip), delete button style; filters: server-side assignee + company mapping; UX: toasts on save/clear default filter
This commit is contained in:
parent
f5b3abd277
commit
9b31a47f82
4 changed files with 38 additions and 28 deletions
|
|
@ -48,9 +48,9 @@ export function DeleteTicketDialog({ ticketId }: { ticketId: Id<"tickets"> }) {
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Excluir ticket"
|
aria-label="Excluir ticket"
|
||||||
className="h-9 w-9 rounded-lg border border-transparent bg-transparent text-[#ef4444] transition hover:border-[#fecaca] hover:bg-[#fee2e2] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#fecaca]/50"
|
className="h-9 w-9 rounded-lg border border-rose-300 bg-white text-rose-600 transition hover:bg-rose-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200"
|
||||||
>
|
>
|
||||||
<Trash2 className="size-4 text-current" />
|
<Trash2 className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-w-md">
|
<DialogContent className="max-w-md">
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { format, formatDistanceToNow } from "date-fns"
|
import { format, formatDistanceToNow } from "date-fns"
|
||||||
import { ptBR } from "date-fns/locale"
|
import { ptBR } from "date-fns/locale"
|
||||||
import { IconClock, IconDownload, IconInfoCircle, IconPlayerPause, IconPlayerPlay } from "@tabler/icons-react"
|
import { IconClock, IconDownload, IconInfoCircle, IconPlayerPause, IconPlayerPlay, IconPencil } from "@tabler/icons-react"
|
||||||
import { useMutation, useQuery } from "convex/react"
|
import { useMutation, useQuery } from "convex/react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
|
|
@ -617,10 +617,16 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cardClass}>
|
<div className={cardClass}>
|
||||||
<div className="absolute right-6 top-6 flex items-center gap-3">
|
<div className="absolute right-6 top-6 flex items-center gap-3">
|
||||||
{!editing ? (
|
{!editing ? (
|
||||||
<Button size="sm" className={editButtonClass} onClick={() => setEditing(true)}>
|
<Button
|
||||||
Editar
|
size="icon"
|
||||||
|
aria-label="Editar"
|
||||||
|
className="inline-flex items-center justify-center rounded-lg border border-slate-200 bg-white text-neutral-800 hover:bg-slate-50"
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
title="Editar"
|
||||||
|
>
|
||||||
|
<IconPencil className="size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -635,28 +641,22 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
{exportingPdf ? <Spinner className="size-4 text-neutral-700" /> : <IconDownload className="size-5" />}
|
{exportingPdf ? <Spinner className="size-4 text-neutral-700" /> : <IconDownload className="size-5" />}
|
||||||
</Button>
|
</Button>
|
||||||
{workSummary ? (
|
{workSummary ? (
|
||||||
<div className="flex items-center gap-2">
|
<Tooltip>
|
||||||
<Badge className="inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
|
<TooltipTrigger asChild>
|
||||||
<IconClock className="size-4 text-neutral-700" /> Tempo total: {formattedTotalWorked}
|
<Badge
|
||||||
</Badge>
|
className="inline-flex h-9 cursor-help items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700"
|
||||||
<Tooltip>
|
title="Tempo total de atendimento"
|
||||||
<TooltipTrigger asChild>
|
>
|
||||||
<button
|
<IconClock className="size-4 text-neutral-700" /> Tempo total: {formattedTotalWorked}
|
||||||
type="button"
|
</Badge>
|
||||||
className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 bg-white text-neutral-600 transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#00d6eb]/30"
|
</TooltipTrigger>
|
||||||
aria-label="Ver detalhamento das horas internas e externas"
|
<TooltipContent className="rounded-lg border border-slate-200 bg-white px-3 py-2 text-xs font-medium text-neutral-700 shadow-lg">
|
||||||
>
|
<div className="flex flex-col gap-1">
|
||||||
<IconInfoCircle className="size-4" />
|
<span>Horas internas: {formatDuration(internalWorkedMs)}</span>
|
||||||
</button>
|
<span>Horas externas: {formatDuration(externalWorkedMs)}</span>
|
||||||
</TooltipTrigger>
|
</div>
|
||||||
<TooltipContent className="rounded-lg border border-slate-200 bg-white px-3 py-2 text-xs font-medium text-neutral-700 shadow-lg">
|
</TooltipContent>
|
||||||
<div className="flex flex-col gap-1">
|
</Tooltip>
|
||||||
<span>Horas internas: {formatDuration(internalWorkedMs)}</span>
|
|
||||||
<span>Horas externas: {formatDuration(externalWorkedMs)}</span>
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
<DeleteTicketDialog ticketId={ticket.id as Id<"tickets">} />
|
<DeleteTicketDialog ticketId={ticket.id as Id<"tickets">} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
import { useQuery } from "convex/react"
|
import { useQuery } from "convex/react"
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
import type { Id } from "@/convex/_generated/dataModel"
|
import type { Id } from "@/convex/_generated/dataModel"
|
||||||
|
|
@ -96,6 +97,7 @@ export function TicketsView({ initialFilters }: TicketsViewProps = {}) {
|
||||||
try {
|
try {
|
||||||
const key = `tickets:filters:${tenantId}:${String(convexUserId)}`
|
const key = `tickets:filters:${tenantId}:${String(convexUserId)}`
|
||||||
localStorage.setItem(key, JSON.stringify(filters))
|
localStorage.setItem(key, JSON.stringify(filters))
|
||||||
|
toast.success("Filtro salvo como padrão")
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +108,7 @@ export function TicketsView({ initialFilters }: TicketsViewProps = {}) {
|
||||||
try {
|
try {
|
||||||
const key = `tickets:filters:${tenantId}:${String(convexUserId)}`
|
const key = `tickets:filters:${tenantId}:${String(convexUserId)}`
|
||||||
localStorage.removeItem(key)
|
localStorage.removeItem(key)
|
||||||
|
toast.success("Padrão de filtro limpo")
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@ const serverTicketSchema = z.object({
|
||||||
queue: z.string().nullable(),
|
queue: z.string().nullable(),
|
||||||
requester: serverUserSchema,
|
requester: serverUserSchema,
|
||||||
assignee: serverUserSchema.nullable(),
|
assignee: serverUserSchema.nullable(),
|
||||||
|
company: z
|
||||||
|
.object({ id: z.string(), name: z.string(), isAvulso: z.boolean().optional() })
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
slaPolicy: z.any().nullable().optional(),
|
slaPolicy: z.any().nullable().optional(),
|
||||||
dueAt: z.number().nullable().optional(),
|
dueAt: z.number().nullable().optional(),
|
||||||
firstResponseAt: z.number().nullable().optional(),
|
firstResponseAt: z.number().nullable().optional(),
|
||||||
|
|
@ -131,6 +135,9 @@ export function mapTicketFromServer(input: unknown) {
|
||||||
const ui = {
|
const ui = {
|
||||||
...s,
|
...s,
|
||||||
status: normalizeTicketStatus(s.status),
|
status: normalizeTicketStatus(s.status),
|
||||||
|
company: s.company
|
||||||
|
? { id: s.company.id, name: s.company.name, isAvulso: s.company.isAvulso ?? false }
|
||||||
|
: undefined,
|
||||||
category: s.category ?? undefined,
|
category: s.category ?? undefined,
|
||||||
subcategory: s.subcategory ?? undefined,
|
subcategory: s.subcategory ?? undefined,
|
||||||
lastTimelineEntry: s.lastTimelineEntry ?? undefined,
|
lastTimelineEntry: s.lastTimelineEntry ?? undefined,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue