chore: sync staging

This commit is contained in:
Esdras Renan 2025-11-10 01:57:45 -03:00
parent c5ddd54a3e
commit 561b19cf66
610 changed files with 105285 additions and 1206 deletions

View file

@ -24,7 +24,7 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
import { formatDateDM, formatDateDMY } from "@/lib/utils"
import { cn, formatDateDM, formatDateDMY } from "@/lib/utils"
import { Skeleton } from "@/components/ui/skeleton"
import {
Select,
@ -39,15 +39,38 @@ import {
ToggleGroupItem,
} from "@/components/ui/toggle-group"
import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox"
export const description = "Distribuição semanal de tickets por canal"
export function ChartAreaInteractive() {
export const description = "Distribuição semanal de tickets por canal"
type ChartRange = "7d" | "30d" | "90d"
type ChartAreaInteractiveProps = {
range?: ChartRange
onRangeChange?: (value: ChartRange) => void
companyId?: string
onCompanyChange?: (value: string) => void
hideControls?: boolean
title?: string
description?: React.ReactNode
className?: string
}
export function ChartAreaInteractive({
range,
onRangeChange,
companyId,
onCompanyChange,
hideControls = false,
title = "Entrada de tickets por canal",
description: descriptionOverride,
className,
}: ChartAreaInteractiveProps = {}) {
const [mounted, setMounted] = React.useState(false)
const isMobile = useIsMobile()
const [timeRange, setTimeRange] = React.useState("7d")
const [internalRange, setInternalRange] = React.useState<ChartRange>(range ?? "7d")
const timeRange = range ?? internalRange
// Persistir seleção de empresa globalmente
const [companyId, setCompanyId] = usePersistentCompanyFilter("all")
const [internalCompanyId, setInternalCompanyId] = usePersistentCompanyFilter("all")
const selectedCompanyId = companyId ?? internalCompanyId
const { session, convexUserId, isStaff } = useAuth()
const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID
@ -56,16 +79,36 @@ export function ChartAreaInteractive() {
}, [])
React.useEffect(() => {
if (isMobile) {
setTimeRange("7d")
if (!range && isMobile) {
setInternalRange("7d")
}
}, [isMobile])
}, [isMobile, range])
const handleRangeChange = (value: ChartRange) => {
if (!value) return
onRangeChange?.(value)
if (!range) {
setInternalRange(value)
}
}
const handleCompanyChange = (value: string) => {
onCompanyChange?.(value)
if (!companyId) {
setInternalCompanyId(value)
}
}
const reportsEnabled = Boolean(isStaff && convexUserId)
const report = useQuery(
api.reports.ticketsByChannel,
reportsEnabled
? ({ tenantId, viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">) })
? ({
tenantId,
viewerId: convexUserId as Id<"users">,
range: timeRange,
companyId: selectedCompanyId === "all" ? undefined : (selectedCompanyId as Id<"companies">),
})
: "skip"
)
const companies = useQuery(
@ -133,60 +176,67 @@ export function ChartAreaInteractive() {
)
}
return (
<Card className="@container/card">
<CardHeader>
<CardTitle>Entrada de tickets por canal</CardTitle>
<CardDescription>
<span className="hidden @[540px]/card:block">
Distribuição dos canais nos últimos {timeRange.replace("d", " dias")}
</span>
<span className="@[540px]/card:hidden">Período: {timeRange}</span>
</CardDescription>
const defaultDescription = (
<>
<span className="hidden @[540px]/card:block">
Distribuição dos canais nos últimos {timeRange.replace("d", " dias")}
</span>
<span className="@[540px]/card:hidden">Período: {timeRange}</span>
</>
)
return (
<Card className={cn("@container/card", className)}>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{descriptionOverride ?? defaultDescription}</CardDescription>
<CardAction>
<div className="flex w-full flex-col items-stretch gap-2 sm:flex-row sm:items-center sm:justify-end sm:gap-2">
{/* Company picker with search */}
<SearchableCombobox
value={companyId}
onValueChange={(next) => setCompanyId(next ?? "all")}
options={companyOptions}
placeholder="Todas as empresas"
className="w-full min-w-56 sm:w-64"
/>
{/* Desktop time range toggles */}
<ToggleGroup
type="single"
value={timeRange}
onValueChange={setTimeRange}
variant="outline"
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
>
<ToggleGroupItem value="90d">90 dias</ToggleGroupItem>
<ToggleGroupItem value="30d">30 dias</ToggleGroupItem>
<ToggleGroupItem value="7d">7 dias</ToggleGroupItem>
</ToggleGroup>
{/* Mobile time range select */}
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger
className="flex w-full min-w-40 @[767px]/card:hidden"
size="sm"
aria-label="Selecionar período"
>
<SelectValue placeholder="Selecionar período" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="90d" className="rounded-lg">Últimos 90 dias</SelectItem>
<SelectItem value="30d" className="rounded-lg">Últimos 30 dias</SelectItem>
<SelectItem value="7d" className="rounded-lg">Últimos 7 dias</SelectItem>
</SelectContent>
</Select>
{/* Export button aligned at the end */}
<Button asChild size="sm" variant="outline" className="sm:ml-1">
{!hideControls ? (
<>
<SearchableCombobox
value={selectedCompanyId}
onValueChange={(next) => handleCompanyChange(next ?? "all")}
options={companyOptions}
placeholder="Todas as empresas"
className="w-full min-w-56 sm:w-64"
/>
<ToggleGroup
type="single"
value={timeRange}
onValueChange={(value) => handleRangeChange((value as ChartRange) ?? timeRange)}
variant="outline"
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
>
<ToggleGroupItem value="90d">90 dias</ToggleGroupItem>
<ToggleGroupItem value="30d">30 dias</ToggleGroupItem>
<ToggleGroupItem value="7d">7 dias</ToggleGroupItem>
</ToggleGroup>
<Select value={timeRange} onValueChange={(value) => handleRangeChange(value as ChartRange)}>
<SelectTrigger
className="flex w-full min-w-40 @[767px]/card:hidden"
size="sm"
aria-label="Selecionar período"
>
<SelectValue placeholder="Selecionar período" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="90d" className="rounded-lg">
Últimos 90 dias
</SelectItem>
<SelectItem value="30d" className="rounded-lg">
Últimos 30 dias
</SelectItem>
<SelectItem value="7d" className="rounded-lg">
Últimos 7 dias
</SelectItem>
</SelectContent>
</Select>
</>
) : null}
<Button asChild size="sm" variant="outline" className={cn(!hideControls && "sm:ml-1")}>
<a
href={`/api/reports/tickets-by-channel.xlsx?range=${timeRange}${companyId !== "all" ? `&companyId=${companyId}` : ""}`}
href={`/api/reports/tickets-by-channel.xlsx?range=${timeRange}${selectedCompanyId !== "all" ? `&companyId=${selectedCompanyId}` : ""}`}
download
>
Exportar XLSX
@ -194,8 +244,8 @@ export function ChartAreaInteractive() {
</Button>
</div>
</CardAction>
</CardHeader>
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
</CardHeader>
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
{report === undefined ? (
<div className="flex h-[250px] items-center justify-center">
<Skeleton className="h-24 w-full" />