diff --git a/convex/reports.ts b/convex/reports.ts index fa37ad9..ac14039 100644 --- a/convex/reports.ts +++ b/convex/reports.ts @@ -1422,6 +1422,8 @@ export async function ticketsByMachineAndCategoryHandler( companyId, machineId, userId, + dateFrom, + dateTo, }: { tenantId: string viewerId: Id<"users"> @@ -1429,10 +1431,12 @@ export async function ticketsByMachineAndCategoryHandler( companyId?: Id<"companies"> machineId?: Id<"machines"> userId?: Id<"users"> + dateFrom?: string + dateTo?: string } ) { const viewer = await requireStaff(ctx, viewerId, tenantId) - const { startMs, endMs, days } = resolveRangeWindow(range, undefined, undefined, 90) + const { startMs, endMs, days } = resolveRangeWindow(range, dateFrom, dateTo, 90) const tickets = days === 0 @@ -1584,6 +1588,8 @@ export async function hoursByMachineHandler( companyId, machineId, userId, + dateFrom, + dateTo, }: { tenantId: string viewerId: Id<"users"> @@ -1591,12 +1597,14 @@ export async function hoursByMachineHandler( companyId?: Id<"companies"> machineId?: Id<"machines"> userId?: Id<"users"> + dateFrom?: string + dateTo?: string } ) { const viewer = await requireStaff(ctx, viewerId, tenantId) const tickets = await fetchScopedTickets(ctx, tenantId, viewer) - const { startMs, endMs, days } = resolveRangeWindow(range, undefined, undefined, 90) + const { startMs, endMs, days } = resolveRangeWindow(range, dateFrom, dateTo, 90) const machinesById = new Map | null>() const companiesById = new Map | null>() diff --git a/src/components/admin/companies/admin-companies-manager.tsx b/src/components/admin/companies/admin-companies-manager.tsx index 4906bf2..3f49625 100644 --- a/src/components/admin/companies/admin-companies-manager.tsx +++ b/src/components/admin/companies/admin-companies-manager.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react" import dynamic from "next/dynamic" import { Controller, FormProvider, useFieldArray, useForm, type UseFormReturn } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" +import { zodResolver } from "@/lib/zod-resolver" import { IconAlertTriangle, IconBuildingSkyscraper, diff --git a/src/components/admin/users/admin-users-workspace.tsx b/src/components/admin/users/admin-users-workspace.tsx index 8f3a1d7..9699949 100644 --- a/src/components/admin/users/admin-users-workspace.tsx +++ b/src/components/admin/users/admin-users-workspace.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from import { format } from "date-fns" import { ptBR } from "date-fns/locale" import { Controller, FormProvider, useFieldArray, useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" +import { zodResolver } from "@/lib/zod-resolver" import { IconBuildingSkyscraper, IconChevronRight, diff --git a/src/components/dashboards/dashboard-builder.tsx b/src/components/dashboards/dashboard-builder.tsx index f1a5902..35749a7 100644 --- a/src/components/dashboards/dashboard-builder.tsx +++ b/src/components/dashboards/dashboard-builder.tsx @@ -76,7 +76,7 @@ import { useSidebar } from "@/components/ui/sidebar" import { toast } from "sonner" import { z } from "zod" import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" +import { zodResolver } from "@/lib/zod-resolver" import { Check, Copy, diff --git a/src/components/reports/backlog-report.tsx b/src/components/reports/backlog-report.tsx index 422d47a..c097e24 100644 --- a/src/components/reports/backlog-report.tsx +++ b/src/components/reports/backlog-report.tsx @@ -52,6 +52,14 @@ export function BacklogReport() { const { session, convexUserId, isStaff, isAdmin } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const enabled = Boolean(isStaff && convexUserId) + + const dateRangeFilters = useMemo(() => { + const filters: { dateFrom?: string; dateTo?: string } = {} + if (dateFrom) filters.dateFrom = dateFrom + if (dateTo) filters.dateTo = dateTo + return filters + }, [dateFrom, dateTo]) + const data = useQuery( api.reports.backlogOverview, enabled @@ -60,8 +68,7 @@ export function BacklogReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), - dateFrom, - dateTo, + ...dateRangeFilters, } : "skip" ) diff --git a/src/components/reports/category-report.tsx b/src/components/reports/category-report.tsx index 2508c60..69e8b43 100644 --- a/src/components/reports/category-report.tsx +++ b/src/components/reports/category-report.tsx @@ -60,6 +60,13 @@ export function CategoryReport() { const [dateTo, setDateTo] = useState(null) const enabled = Boolean(isStaff && convexUserId) + const dateRangeFilters = useMemo(() => { + const filters: { dateFrom?: string; dateTo?: string } = {} + if (dateFrom) filters.dateFrom = dateFrom + if (dateTo) filters.dateTo = dateTo + return filters + }, [dateFrom, dateTo]) + const companyFilter = companyId !== "all" ? (companyId as Id<"companies">) : undefined const data = useQuery( @@ -70,10 +77,9 @@ export function CategoryReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyFilter, - dateFrom, - dateTo, + ...dateRangeFilters, } - : "skip", + : "skip" ) as CategoryInsightsResponse | undefined const companies = useQuery( diff --git a/src/components/reports/csat-report.tsx b/src/components/reports/csat-report.tsx index b2009a2..3e5ab47 100644 --- a/src/components/reports/csat-report.tsx +++ b/src/components/reports/csat-report.tsx @@ -9,7 +9,7 @@ import { DEFAULT_TENANT_ID } from "@/lib/constants" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" import { Badge } from "@/components/ui/badge" -import { useState } from "react" +import { useMemo, useState } from "react" import { Bar, BarChart, CartesianGrid, XAxis } from "recharts" import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart" import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox" @@ -31,6 +31,12 @@ export function CsatReport() { const { session, convexUserId, isStaff, isAdmin } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const enabled = Boolean(isStaff && convexUserId) + const dateRangeFilters = useMemo(() => { + const filters: { dateFrom?: string; dateTo?: string } = {} + if (dateFrom) filters.dateFrom = dateFrom + if (dateTo) filters.dateTo = dateTo + return filters + }, [dateFrom, dateTo]) const data = useQuery( api.reports.csatOverview, enabled @@ -39,8 +45,7 @@ export function CsatReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), - dateFrom, - dateTo, + ...dateRangeFilters, }) : "skip" ) diff --git a/src/components/reports/hours-report.tsx b/src/components/reports/hours-report.tsx index b64eb5d..9df3726 100644 --- a/src/components/reports/hours-report.tsx +++ b/src/components/reports/hours-report.tsx @@ -79,6 +79,12 @@ export function HoursReport() { const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const enabled = Boolean(isStaff && convexUserId) + const dateRangeFilters = useMemo(() => { + const filters: { dateFrom?: string; dateTo?: string } = {} + if (dateFrom) filters.dateFrom = dateFrom + if (dateTo) filters.dateTo = dateTo + return filters + }, [dateFrom, dateTo]) const data = useQuery( api.reports.hoursByClient, enabled && groupBy === "company" @@ -86,8 +92,7 @@ export function HoursReport() { tenantId, viewerId: convexUserId as Id<"users">, range: timeRange === "365d" || timeRange === "all" ? "90d" : timeRange, - dateFrom, - dateTo, + ...dateRangeFilters, } : "skip" ) as { rangeDays: number; items: HoursItem[] } | undefined @@ -100,8 +105,7 @@ export function HoursReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), - dateFrom, - dateTo, + ...dateRangeFilters, } : "skip" ) as HoursByMachineResponse | undefined diff --git a/src/components/reports/machine-category-report.tsx b/src/components/reports/machine-category-report.tsx index 65d291d..ea583d9 100644 --- a/src/components/reports/machine-category-report.tsx +++ b/src/components/reports/machine-category-report.tsx @@ -57,6 +57,12 @@ export function MachineCategoryReport() { const canView = Boolean(isStaff) const enabled = Boolean(canView && convexUserId) + const dateRangeFilters = useMemo(() => { + const filters: { dateFrom?: string; dateTo?: string } = {} + if (dateFrom) filters.dateFrom = dateFrom + if (dateTo) filters.dateTo = dateTo + return filters + }, [dateFrom, dateTo]) const data = useQuery( api.reports.ticketsByMachineAndCategory, @@ -66,8 +72,7 @@ export function MachineCategoryReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), - dateFrom, - dateTo, + ...dateRangeFilters, } as const) : "skip" ) as MachineCategoryReportData | undefined @@ -163,8 +168,7 @@ export function MachineCategoryReport() { companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), machineId: selectedMachineId !== "all" ? (selectedMachineId as Id<"machines">) : undefined, userId: selectedUserId !== "all" ? (selectedUserId as Id<"users">) : undefined, - dateFrom, - dateTo, + ...dateRangeFilters, } as const) : "skip" ) as MachineHoursResponse | undefined diff --git a/src/components/reports/sla-report.tsx b/src/components/reports/sla-report.tsx index 6f0a7a7..ab2993f 100644 --- a/src/components/reports/sla-report.tsx +++ b/src/components/reports/sla-report.tsx @@ -68,6 +68,12 @@ export function SlaReport() { const { session, convexUserId, isStaff, isAdmin } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const enabled = Boolean(isStaff && convexUserId) + const dateRangeFilters = useMemo(() => { + const filters: { dateFrom?: string; dateTo?: string } = {} + if (dateFrom) filters.dateFrom = dateFrom + if (dateTo) filters.dateTo = dateTo + return filters + }, [dateFrom, dateTo]) const data = useQuery( api.reports.slaOverview, enabled @@ -76,8 +82,7 @@ export function SlaReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), - dateFrom, - dateTo, + ...dateRangeFilters, }) : "skip" ) @@ -96,8 +101,7 @@ export function SlaReport() { viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">), - dateFrom, - dateTo, + ...dateRangeFilters, }) : "skip" ) as { rangeDays: number; series: Array<{ date: string; opened: number; resolved: number }> } | undefined diff --git a/src/components/tickets/new-ticket-dialog.tsx b/src/components/tickets/new-ticket-dialog.tsx index 26a0123..78a1ec1 100644 --- a/src/components/tickets/new-ticket-dialog.tsx +++ b/src/components/tickets/new-ticket-dialog.tsx @@ -16,7 +16,7 @@ import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { FieldSet, FieldGroup, Field, FieldLabel, FieldError } from "@/components/ui/field" import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" +import { zodResolver } from "@/lib/zod-resolver" import { toast } from "sonner" import { Spinner } from "@/components/ui/spinner" import { Dropzone } from "@/components/ui/dropzone" diff --git a/src/lib/zod-resolver.ts b/src/lib/zod-resolver.ts new file mode 100644 index 0000000..0aeeec9 --- /dev/null +++ b/src/lib/zod-resolver.ts @@ -0,0 +1,15 @@ +import { zodResolver as baseZodResolver } from "@hookform/resolvers/zod" +import { ZodError } from "zod" + +// Zod v4 renamed `error.errors` to `error.issues`, but @hookform/resolvers +// still expects the legacy `errors` getter. Patch the prototype once so every +// resolver consumer keeps working without duplicating validation logic. +if (typeof ZodError !== "undefined" && !("errors" in ZodError.prototype)) { + Object.defineProperty(ZodError.prototype, "errors", { + get() { + return this.issues + }, + }) +} + +export const zodResolver = baseZodResolver