feat(filters): ticket company filter + column; reports: company filter in CSVs; dashboard: queue summary; docs: agents.md and roadmap updates

This commit is contained in:
Esdras Renan 2025-10-07 14:18:59 -03:00
parent 70f91f5bbd
commit 2cf399dcb1
9 changed files with 100 additions and 31 deletions

View file

@ -66,6 +66,7 @@ export type TicketFiltersState = {
priority: string | null
queue: string | null
channel: string | null
company: string | null
view: "active" | "completed"
}
@ -75,17 +76,19 @@ export const defaultTicketFilters: TicketFiltersState = {
priority: null,
queue: null,
channel: null,
company: null,
view: "active",
}
interface TicketsFiltersProps {
onChange?: (filters: TicketFiltersState) => void
queues?: QueueOption[]
companies?: string[]
}
const ALL_VALUE = "ALL"
export function TicketsFilters({ onChange, queues = [] }: TicketsFiltersProps) {
export function TicketsFilters({ onChange, queues = [], companies = [] }: TicketsFiltersProps) {
const [filters, setFilters] = useState<TicketFiltersState>(defaultTicketFilters)
function setPartial(partial: Partial<TicketFiltersState>) {
@ -103,6 +106,7 @@ export function TicketsFilters({ onChange, queues = [] }: TicketsFiltersProps) {
if (filters.priority) chips.push(`Prioridade: ${filters.priority}`)
if (filters.queue) chips.push(`Fila: ${filters.queue}`)
if (filters.channel) chips.push(`Canal: ${filters.channel}`)
if (filters.company) chips.push(`Empresa: ${filters.company}`)
if (!filters.status && filters.view === "completed") chips.push("Exibindo concluídos")
return chips
}, [filters])
@ -133,6 +137,22 @@ export function TicketsFilters({ onChange, queues = [] }: TicketsFiltersProps) {
))}
</SelectContent>
</Select>
<Select
value={filters.company ?? ALL_VALUE}
onValueChange={(value) => setPartial({ company: value === ALL_VALUE ? null : value })}
>
<SelectTrigger className="md:w-[220px]">
<SelectValue placeholder="Empresa" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL_VALUE}>Todas as empresas</SelectItem>
{companies.map((company) => (
<SelectItem key={company!} value={company!}>
{company}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<Select

View file

@ -145,6 +145,9 @@ export function TicketsTable({ tickets = ticketsMock }: TicketsTableProps) {
<TableHead className="hidden w-[120px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 lg:table-cell">
Fila
</TableHead>
<TableHead className="hidden w-[180px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 lg:table-cell">
Empresa
</TableHead>
<TableHead className="hidden w-[80px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 md:table-cell">
Canal
</TableHead>
@ -231,6 +234,11 @@ export function TicketsTable({ tickets = ticketsMock }: TicketsTableProps) {
</span>
</div>
</TableCell>
<TableCell className={`${cellClass} hidden lg:table-cell pl-1`}>
<span className="text-sm text-neutral-800">
{(ticket as any).company?.name ?? "—"}
</span>
</TableCell>
<TableCell className={`${cellClass} hidden md:table-cell pl-1 pr-8`}>
<div
className="inline-flex"

View file

@ -39,6 +39,14 @@ export function TicketsView() {
)
const tickets = useMemo(() => mapTicketsFromServerList((ticketsRaw ?? []) as unknown[]), [ticketsRaw])
const companies = useMemo(() => {
const set = new Set<string>()
for (const t of tickets) {
const name = (t as any).company?.name as string | undefined
if (name) set.add(name)
}
return Array.from(set).sort((a, b) => a.localeCompare(b, "pt-BR"))
}, [tickets])
const filteredTickets = useMemo(() => {
const completedStatuses = new Set<Ticket["status"]>(["RESOLVED"])
@ -55,13 +63,16 @@ export function TicketsView() {
if (filters.queue) {
working = working.filter((t) => t.queue === filters.queue)
}
if (filters.company) {
working = working.filter((t) => ((t as any).company?.name ?? null) === filters.company)
}
return working
}, [tickets, filters.queue, filters.status, filters.view])
return (
<div className="flex flex-col gap-6 px-4 lg:px-6">
<TicketsFilters onChange={setFilters} queues={(queues ?? []).map((q) => q.name)} />
<TicketsFilters onChange={setFilters} queues={(queues ?? []).map((q) => q.name)} companies={companies} />
{ticketsRaw === undefined ? (
<div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
<div className="grid gap-3">