chore: sync staging
This commit is contained in:
parent
c5ddd54a3e
commit
561b19cf66
610 changed files with 105285 additions and 1206 deletions
|
|
@ -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" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue