Refine tickets filter layout

This commit is contained in:
Esdras Renan 2025-11-13 19:50:06 -03:00
parent 12acbc5b1c
commit a08545fd40

View file

@ -1,7 +1,7 @@
"use client" "use client"
import { useEffect, useMemo, useState } from "react" import { useEffect, useMemo, useState } from "react"
import { IconCalendar, IconFilter, IconRefresh } from "@tabler/icons-react" import { IconCalendar, IconFilter, IconRefresh, IconSearch } from "@tabler/icons-react"
import type { DateRange } from "react-day-picker" import type { DateRange } from "react-day-picker"
import { import {
@ -31,6 +31,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select" } from "@/components/ui/select"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
type QueueOption = string type QueueOption = string
@ -146,10 +147,10 @@ function DateRangeButton({ from, to, onChange, className }: DateRangeButtonProps
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant="outline" variant="outline"
className={`w-full justify-start rounded-xl border-slate-300 bg-white/90 ${className ?? ""}`} className={`flex h-10 w-full items-center justify-start gap-2 rounded-2xl border-slate-300 bg-white/95 text-sm font-semibold text-neutral-700 ${className ?? ""}`}
> >
<IconCalendar className="mr-2 size-4" /> <IconCalendar className="size-4 text-neutral-500" />
<span className="line-clamp-1">{label}</span> <span className="truncate">{label}</span>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-auto overflow-hidden p-0" align="end"> <PopoverContent className="w-auto overflow-hidden p-0" align="end">
@ -222,32 +223,47 @@ export function TicketsFilters({
return chips return chips
}, [filters, assignees, categories, canUseAdvancedFilters]) }, [filters, assignees, categories, canUseAdvancedFilters])
const advancedFiltersCount = Number(Boolean(filters.status)) + Number(Boolean(filters.priority)) + Number(Boolean(filters.channel))
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<section className="rounded-2xl border border-slate-200 bg-white/80 p-4 shadow-sm"> <section className="rounded-3xl border border-slate-200 bg-white/90 p-4 shadow-sm">
<div className="grid gap-4"> <div className="flex flex-col gap-4">
<div className="grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] items-center gap-3"> <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:gap-4">
<div className="col-span-2"> <div className="flex flex-1 flex-col gap-2">
<Input <div className="relative">
placeholder="Buscar por assunto ou #ID" <IconSearch className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
value={filters.search} <Input
onChange={(event) => setPartial({ search: event.target.value })} placeholder="Buscar por assunto ou #ID"
className="w-full rounded-xl border-slate-300 bg-white/90" value={filters.search}
/> onChange={(event) => setPartial({ search: event.target.value })}
className="w-full rounded-2xl border-slate-300 bg-white/95 pl-9"
/>
</div>
</div> </div>
<div className="col-span-full flex items-center justify-end gap-2"> <div className="flex flex-wrap items-center gap-2">
<DateRangeButton
from={filters.dateFrom}
to={filters.dateTo}
onChange={({ from, to }) => setPartial({ dateFrom: from, dateTo: to })}
className="w-full min-w-[200px] rounded-2xl border-slate-300 bg-white/95 text-left text-sm font-semibold text-neutral-700 lg:w-auto"
/>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant="outline" variant="outline"
size="icon" className="h-10 gap-2 rounded-full border-dashed border-slate-300 bg-white/80 px-4 text-sm font-medium text-neutral-700 shadow-none hover:bg-slate-50"
className="size-9 rounded-full border-slate-300 bg-white text-neutral-800 shadow-sm hover:bg-slate-50"
aria-label="Filtros avançados"
> >
<IconFilter className="size-4" /> <IconFilter className="size-4" />
<span>Filtros rápidos</span>
{advancedFiltersCount > 0 ? (
<Badge className="rounded-full bg-neutral-900 px-2 py-0 text-[0.65rem] font-semibold text-white">
{advancedFiltersCount}
</Badge>
) : null}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-64 space-y-4" align="end"> <PopoverContent className="w-72 space-y-4" align="end">
<div className="space-y-2"> <div className="space-y-2">
<p className="text-xs font-semibold uppercase text-neutral-500">Status</p> <p className="text-xs font-semibold uppercase text-neutral-500">Status</p>
<Select <Select
@ -311,118 +327,133 @@ export function TicketsFilters({
</Popover> </Popover>
<Button <Button
variant="ghost" variant="ghost"
size="icon" className="h-10 gap-2 rounded-full text-sm font-medium text-neutral-700 hover:bg-slate-100"
className="size-9 rounded-full text-neutral-700 hover:bg-slate-100"
onClick={() => setPartial(defaultTicketFilters)} onClick={() => setPartial(defaultTicketFilters)}
aria-label="Resetar filtros"
> >
<IconRefresh className="size-4" /> <IconRefresh className="size-4" />
Limpar
</Button> </Button>
</div> </div>
</div> </div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3"> <div className="flex flex-wrap gap-3">
{canUseAdvancedFilters && ( {canUseAdvancedFilters && (
<Select <div className="min-w-[220px] flex-1">
value={filters.queue ?? ALL_VALUE} <Select
onValueChange={(value) => setPartial({ queue: value === ALL_VALUE ? null : value })} value={filters.queue ?? ALL_VALUE}
> onValueChange={(value) => setPartial({ queue: value === ALL_VALUE ? null : value })}
<SelectTrigger className="w-full rounded-xl border-slate-300 bg-slate-50/70 focus:ring-neutral-300"> >
<SelectValue placeholder="Fila" /> <SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
</SelectTrigger> <SelectValue placeholder="Fila" />
<SelectContent> </SelectTrigger>
<SelectItem value={ALL_VALUE}>Todas as filas</SelectItem> <SelectContent>
{queues.map((queue) => ( <SelectItem value={ALL_VALUE}>Todas as filas</SelectItem>
<SelectItem key={queue!} value={queue!}> {queues.map((queue) => (
{queue} <SelectItem key={queue!} value={queue!}>
</SelectItem> {queue}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
</Select>
</div>
)} )}
{canUseAdvancedFilters && ( {canUseAdvancedFilters && (
<Select <div className="min-w-[220px] flex-1">
value={filters.company ?? ALL_VALUE} <Select
onValueChange={(value) => setPartial({ company: value === ALL_VALUE ? null : value })} value={filters.company ?? ALL_VALUE}
> onValueChange={(value) => setPartial({ company: value === ALL_VALUE ? null : value })}
<SelectTrigger className="w-full rounded-xl border-slate-300 bg-slate-50/70 focus:ring-neutral-300"> >
<SelectValue placeholder="Empresa" /> <SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
</SelectTrigger> <SelectValue placeholder="Empresa" />
<SelectContent> </SelectTrigger>
<SelectItem value={ALL_VALUE}>Todas as empresas</SelectItem> <SelectContent>
{companies.map((company) => ( <SelectItem value={ALL_VALUE}>Todas as empresas</SelectItem>
<SelectItem key={company!} value={company!}> {companies.map((company) => (
{company} <SelectItem key={company!} value={company!}>
</SelectItem> {company}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
</Select>
</div>
)} )}
<Select <div className="min-w-[220px] flex-1">
value={filters.categoryId ?? ALL_VALUE}
onValueChange={(value) => setPartial({ categoryId: value === ALL_VALUE ? null : value })}
>
<SelectTrigger className="w-full rounded-xl border-slate-300 bg-slate-50/70 focus:ring-neutral-300">
<SelectValue placeholder="Categoria" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL_VALUE}>Todas as categorias</SelectItem>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
{canUseAdvancedFilters && (
<Select <Select
value={filters.assigneeId ?? ALL_VALUE} value={filters.categoryId ?? ALL_VALUE}
onValueChange={(value) => setPartial({ assigneeId: value === ALL_VALUE ? null : value })} onValueChange={(value) => setPartial({ categoryId: value === ALL_VALUE ? null : value })}
> >
<SelectTrigger className="w-full rounded-xl border-slate-300 bg-slate-50/70 focus:ring-neutral-300"> <SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
<SelectValue placeholder="Responsável" /> <SelectValue placeholder="Categoria" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem> <SelectItem value={ALL_VALUE}>Todas as categorias</SelectItem>
{assignees.map((user) => ( {categories.map((category) => (
<SelectItem key={user.id} value={user.id}> <SelectItem key={category.id} value={category.id}>
{user.name} {category.name}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
</div>
{canUseAdvancedFilters && (
<div className="min-w-[220px] flex-1">
<Select
value={filters.assigneeId ?? ALL_VALUE}
onValueChange={(value) => setPartial({ assigneeId: value === ALL_VALUE ? null : value })}
>
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
<SelectValue placeholder="Responsável" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem>
{assignees.map((user) => (
<SelectItem key={user.id} value={user.id}>
{user.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)} )}
</div> </div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3"> <div className="flex flex-wrap gap-3">
<Select <ToggleGroup
type="single"
value={filters.view} value={filters.view}
onValueChange={(value) => setPartial({ view: value as TicketFiltersState["view"] })} onValueChange={(value) => value && setPartial({ view: value as TicketFiltersState["view"] })}
className="flex h-10 min-w-[220px] flex-1 items-center rounded-full border border-slate-200 bg-slate-50/70 p-1"
> >
<SelectTrigger className="w-full rounded-xl border-slate-300 bg-slate-50/70 focus:ring-neutral-300"> <ToggleGroupItem
<SelectValue /> value="active"
</SelectTrigger> className="inline-flex h-full flex-1 items-center justify-center rounded-full px-4 text-sm font-semibold text-neutral-600 transition data-[state=on]:bg-neutral-900 data-[state=on]:text-white"
<SelectContent> >
<SelectItem value="active">Em andamento</SelectItem> Em andamento
<SelectItem value="completed">Concluídos</SelectItem> </ToggleGroupItem>
</SelectContent> <ToggleGroupItem
</Select> value="completed"
<Select className="inline-flex h-full flex-1 items-center justify-center rounded-full px-4 text-sm font-semibold text-neutral-600 transition data-[state=on]:bg-neutral-900 data-[state=on]:text-white"
>
Concluídos
</ToggleGroupItem>
</ToggleGroup>
<ToggleGroup
type="single"
value={filters.sort} value={filters.sort}
onValueChange={(value) => setPartial({ sort: value as TicketFiltersState["sort"] })} onValueChange={(value) => value && setPartial({ sort: value as TicketFiltersState["sort"] })}
className="flex h-10 min-w-[220px] flex-1 items-center rounded-full border border-slate-200 bg-slate-50/70 p-1"
> >
<SelectTrigger className="w-full rounded-xl border-slate-300 bg-slate-50/70 focus:ring-neutral-300"> <ToggleGroupItem
<SelectValue placeholder="Ordenar" /> value="recent"
</SelectTrigger> className="inline-flex h-full flex-1 items-center justify-center rounded-full px-4 text-sm font-semibold text-neutral-600 transition data-[state=on]:bg-neutral-900 data-[state=on]:text-white"
<SelectContent> >
<SelectItem value="recent">Mais recentes</SelectItem> Mais recentes
<SelectItem value="oldest">Mais antigos</SelectItem> </ToggleGroupItem>
</SelectContent> <ToggleGroupItem
</Select> value="oldest"
<DateRangeButton className="inline-flex h-full flex-1 items-center justify-center rounded-full px-4 text-sm font-semibold text-neutral-600 transition data-[state=on]:bg-neutral-900 data-[state=on]:text-white"
from={filters.dateFrom} >
to={filters.dateTo} Mais antigos
onChange={({ from, to }) => setPartial({ dateFrom: from, dateTo: to })} </ToggleGroupItem>
className="w-full" </ToggleGroup>
/>
</div> </div>
</div> </div>
</section> </section>