Refine tickets filter layout
This commit is contained in:
parent
12acbc5b1c
commit
a08545fd40
1 changed files with 138 additions and 107 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"use client"
|
||||
|
||||
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 {
|
||||
|
|
@ -31,6 +31,7 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||||
|
||||
type QueueOption = string
|
||||
|
||||
|
|
@ -146,10 +147,10 @@ function DateRangeButton({ from, to, onChange, className }: DateRangeButtonProps
|
|||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
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" />
|
||||
<span className="line-clamp-1">{label}</span>
|
||||
<IconCalendar className="size-4 text-neutral-500" />
|
||||
<span className="truncate">{label}</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="end">
|
||||
|
|
@ -222,32 +223,47 @@ export function TicketsFilters({
|
|||
return chips
|
||||
}, [filters, assignees, categories, canUseAdvancedFilters])
|
||||
|
||||
const advancedFiltersCount = Number(Boolean(filters.status)) + Number(Boolean(filters.priority)) + Number(Boolean(filters.channel))
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<section className="rounded-2xl border border-slate-200 bg-white/80 p-4 shadow-sm">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] items-center gap-3">
|
||||
<div className="col-span-2">
|
||||
<Input
|
||||
placeholder="Buscar por assunto ou #ID"
|
||||
value={filters.search}
|
||||
onChange={(event) => setPartial({ search: event.target.value })}
|
||||
className="w-full rounded-xl border-slate-300 bg-white/90"
|
||||
/>
|
||||
<section className="rounded-3xl border border-slate-200 bg-white/90 p-4 shadow-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:gap-4">
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<div className="relative">
|
||||
<IconSearch className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
|
||||
<Input
|
||||
placeholder="Buscar por assunto ou #ID"
|
||||
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 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>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-9 rounded-full border-slate-300 bg-white text-neutral-800 shadow-sm hover:bg-slate-50"
|
||||
aria-label="Filtros avançados"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-64 space-y-4" align="end">
|
||||
<PopoverContent className="w-72 space-y-4" align="end">
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-semibold uppercase text-neutral-500">Status</p>
|
||||
<Select
|
||||
|
|
@ -311,118 +327,133 @@ export function TicketsFilters({
|
|||
</Popover>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-9 rounded-full text-neutral-700 hover:bg-slate-100"
|
||||
className="h-10 gap-2 rounded-full text-sm font-medium text-neutral-700 hover:bg-slate-100"
|
||||
onClick={() => setPartial(defaultTicketFilters)}
|
||||
aria-label="Resetar filtros"
|
||||
>
|
||||
<IconRefresh className="size-4" />
|
||||
Limpar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{canUseAdvancedFilters && (
|
||||
<Select
|
||||
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>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_VALUE}>Todas as filas</SelectItem>
|
||||
{queues.map((queue) => (
|
||||
<SelectItem key={queue!} value={queue!}>
|
||||
{queue}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="min-w-[220px] flex-1">
|
||||
<Select
|
||||
value={filters.queue ?? ALL_VALUE}
|
||||
onValueChange={(value) => setPartial({ queue: value === ALL_VALUE ? null : value })}
|
||||
>
|
||||
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
|
||||
<SelectValue placeholder="Fila" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_VALUE}>Todas as filas</SelectItem>
|
||||
{queues.map((queue) => (
|
||||
<SelectItem key={queue!} value={queue!}>
|
||||
{queue}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
{canUseAdvancedFilters && (
|
||||
<Select
|
||||
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>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_VALUE}>Todas as empresas</SelectItem>
|
||||
{companies.map((company) => (
|
||||
<SelectItem key={company!} value={company!}>
|
||||
{company}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="min-w-[220px] flex-1">
|
||||
<Select
|
||||
value={filters.company ?? ALL_VALUE}
|
||||
onValueChange={(value) => setPartial({ company: value === ALL_VALUE ? null : value })}
|
||||
>
|
||||
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
|
||||
<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>
|
||||
)}
|
||||
<Select
|
||||
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 && (
|
||||
<div className="min-w-[220px] flex-1">
|
||||
<Select
|
||||
value={filters.assigneeId ?? ALL_VALUE}
|
||||
onValueChange={(value) => setPartial({ assigneeId: value === ALL_VALUE ? null : value })}
|
||||
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="Responsável" />
|
||||
<SelectTrigger className="w-full rounded-2xl border-slate-300 bg-slate-50/80 focus:ring-neutral-300">
|
||||
<SelectValue placeholder="Categoria" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem>
|
||||
{assignees.map((user) => (
|
||||
<SelectItem key={user.id} value={user.id}>
|
||||
{user.name}
|
||||
<SelectItem value={ALL_VALUE}>Todas as categorias</SelectItem>
|
||||
{categories.map((category) => (
|
||||
<SelectItem key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</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 className="grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3">
|
||||
<Select
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
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">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="active">Em andamento</SelectItem>
|
||||
<SelectItem value="completed">Concluídos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
<ToggleGroupItem
|
||||
value="active"
|
||||
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"
|
||||
>
|
||||
Em andamento
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="completed"
|
||||
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}
|
||||
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">
|
||||
<SelectValue placeholder="Ordenar" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="recent">Mais recentes</SelectItem>
|
||||
<SelectItem value="oldest">Mais antigos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<DateRangeButton
|
||||
from={filters.dateFrom}
|
||||
to={filters.dateTo}
|
||||
onChange={({ from, to }) => setPartial({ dateFrom: from, dateTo: to })}
|
||||
className="w-full"
|
||||
/>
|
||||
<ToggleGroupItem
|
||||
value="recent"
|
||||
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"
|
||||
>
|
||||
Mais recentes
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="oldest"
|
||||
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"
|
||||
>
|
||||
Mais antigos
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue