From 8a237a820d1123e486b67907d647f1c6af28f0bf Mon Sep 17 00:00:00 2001 From: rever-tecnologia Date: Wed, 17 Dec 2025 19:01:56 -0300 Subject: [PATCH] feat(reports): adiciona opcao Todas as empresas no relatorio por empresa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Frontend: usa usePersistentCompanyFilter para persistir selecao - Frontend: adiciona opcao "Todas as empresas" como primeira opcao - Backend: torna companyId opcional na query companyOverview - Backend: usa resolveScopedCompanyId para scoping de gestores 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- convex/reports.ts | 67 +++++++++++++++-------- src/components/reports/company-report.tsx | 31 ++++++----- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/convex/reports.ts b/convex/reports.ts index 5c18044..2f16dbe 100644 --- a/convex/reports.ts +++ b/convex/reports.ts @@ -2406,18 +2406,20 @@ export const companyOverview = query({ args: { tenantId: v.string(), viewerId: v.id("users"), - companyId: v.id("companies"), + companyId: v.optional(v.id("companies")), range: v.optional(v.string()), }, handler: async (ctx, { tenantId, viewerId, companyId, range }) => { const viewer = await requireStaff(ctx, viewerId, tenantId); - if (viewer.role === "MANAGER" && viewer.user.companyId && viewer.user.companyId !== companyId) { - throw new ConvexError("Gestores só podem consultar relatórios da própria empresa"); - } + const scopedCompanyId = resolveScopedCompanyId(viewer, companyId); - const company = await ctx.db.get(companyId); - if (!company || company.tenantId !== tenantId) { - throw new ConvexError("Empresa não encontrada"); + // Buscar dados da empresa selecionada (se houver) + let company: Doc<"companies"> | null = null; + if (scopedCompanyId) { + company = await ctx.db.get(scopedCompanyId); + if (!company || company.tenantId !== tenantId) { + throw new ConvexError("Empresa não encontrada"); + } } const normalizedRange = (range ?? "30d").toLowerCase(); @@ -2426,20 +2428,35 @@ export const companyOverview = query({ const startMs = now - rangeDays * ONE_DAY_MS; // Limita consultas para evitar OOM em empresas muito grandes - const tickets = await ctx.db - .query("tickets") - .withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", companyId)) - .take(2000); + const tickets = scopedCompanyId + ? await ctx.db + .query("tickets") + .withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", scopedCompanyId)) + .take(2000) + : await ctx.db + .query("tickets") + .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)) + .take(2000); - const machines = await ctx.db - .query("machines") - .withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", companyId)) - .take(1000); + const machines = scopedCompanyId + ? await ctx.db + .query("machines") + .withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", scopedCompanyId)) + .take(1000) + : await ctx.db + .query("machines") + .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)) + .take(1000); - const users = await ctx.db - .query("users") - .withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", companyId)) - .take(500); + const users = scopedCompanyId + ? await ctx.db + .query("users") + .withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", scopedCompanyId)) + .take(500) + : await ctx.db + .query("users") + .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)) + .take(500); const statusCounts = {} as Record; const priorityCounts = {} as Record; @@ -2534,11 +2551,13 @@ export const companyOverview = query({ }); return { - company: { - id: company._id, - name: company.name, - isAvulso: company.isAvulso ?? false, - }, + company: company + ? { + id: company._id, + name: company.name, + isAvulso: company.isAvulso ?? false, + } + : null, rangeDays, generatedAt: now, tickets: { diff --git a/src/components/reports/company-report.tsx b/src/components/reports/company-report.tsx index 6480e51..abef3a0 100644 --- a/src/components/reports/company-report.tsx +++ b/src/components/reports/company-report.tsx @@ -1,12 +1,13 @@ "use client" -import { useEffect, useMemo, useState } from "react" +import { useMemo, useState } from "react" import Link from "next/link" import { useQuery } from "convex/react" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { useAuth } from "@/lib/auth-client" +import { usePersistentCompanyFilter } from "@/lib/use-company-filter" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" @@ -59,7 +60,7 @@ const MACHINE_STATUS_CONFIG = { export function CompanyReport() { const { session, convexUserId, isStaff } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID - const [selectedCompany, setSelectedCompany] = useState("") + const [selectedCompany, setSelectedCompany] = usePersistentCompanyFilter("all") const [timeRange, setTimeRange] = useState<"7d" | "30d" | "90d">("30d") const companies = useQuery( @@ -67,28 +68,28 @@ export function CompanyReport() { isStaff && convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" ) as CompanyRecord[] | undefined - const companyOptions = useMemo( - () => - (companies ?? []).map((company) => ({ + const companyOptions = useMemo(() => { + const base: SearchableComboboxOption[] = [{ value: "all", label: "Todas as empresas" }] + if (!companies || companies.length === 0) { + return base + } + const sorted = [...companies].sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) + return [ + base[0], + ...sorted.map((company) => ({ value: company.id as string, label: company.name, })), - [companies] - ) - - useEffect(() => { - if (!selectedCompany && companyOptions.length > 0) { - setSelectedCompany(companyOptions[0]?.value ?? "") - } - }, [companyOptions, selectedCompany]) + ] + }, [companies]) const report = useQuery( api.reports.companyOverview, - selectedCompany && convexUserId && isStaff + convexUserId && isStaff ? { tenantId, viewerId: convexUserId as Id<"users">, - companyId: selectedCompany as Id<"companies">, + companyId: selectedCompany === "all" ? undefined : (selectedCompany as Id<"companies">), range: timeRange, } : "skip"