"use client" import { useState, useMemo } from "react" import { useQuery } from "convex/react" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { useAuth } from "@/lib/auth-client" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { usePersistentCompanyFilter } from "@/lib/use-company-filter" import { Progress } from "@/components/ui/progress" import { Bar, BarChart, CartesianGrid, XAxis } from "recharts" import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart" type HoursItem = { companyId: string name: string isAvulso: boolean internalMs: number externalMs: number totalMs: number contractedHoursPerMonth?: number | null } export function HoursReport() { const [timeRange, setTimeRange] = useState("90d") const [query, setQuery] = useState("") const [companyId, setCompanyId] = usePersistentCompanyFilter("all") const { session, convexUserId, isStaff } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const enabled = Boolean(isStaff && convexUserId) const data = useQuery( api.reports.hoursByClient, enabled ? { tenantId, viewerId: convexUserId as Id<"users">, range: timeRange } : "skip" ) as { rangeDays: number; items: HoursItem[] } | undefined const companies = useQuery(api.companies.list, enabled ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip") as Array<{ id: Id<"companies">; name: string }> | undefined const filtered = useMemo(() => { const items = data?.items ?? [] const q = query.trim().toLowerCase() let list = items if (companyId !== "all") list = list.filter((it) => String(it.companyId) === companyId) if (q) list = list.filter((it) => it.name.toLowerCase().includes(q)) return list }, [data?.items, query, companyId]) const totals = useMemo(() => { return filtered.reduce( (acc, item) => { acc.internal += item.internalMs / 3600000 acc.external += item.externalMs / 3600000 acc.total += item.totalMs / 3600000 return acc }, { internal: 0, external: 0, total: 0 } ) }, [filtered]) const numberFormatter = useMemo( () => new Intl.NumberFormat("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2, }), [] ) const filteredWithComputed = useMemo( () => filtered.map((row) => { const internal = row.internalMs / 3600000 const external = row.externalMs / 3600000 const total = row.totalMs / 3600000 const contracted = row.contractedHoursPerMonth ?? null const usagePercent = contracted && contracted > 0 ? Math.min(100, Math.round((total / contracted) * 100)) : null return { ...row, internal, external, total, contracted, usagePercent, } }), [filtered] ) return (
Top clientes por horas Comparativo empilhado de horas internas x externas (top 10). {!filteredWithComputed || filteredWithComputed.length === 0 ? (

Sem dados para o período.

) : ( b.total - a.total) .slice(0, 10) .map((r) => ({ name: r.name, internas: r.internal, externas: r.external }))} > } /> )}
Horas Visualize o esforço interno e externo por empresa e acompanhe o consumo contratado.
setQuery(e.target.value)} className="h-9 w-full min-w-56 sm:w-72" /> 90 dias 30 dias 7 dias
{[ { key: "internal", label: "Horas internas", value: numberFormatter.format(totals.internal) }, { key: "external", label: "Horas externas", value: numberFormatter.format(totals.external) }, { key: "total", label: "Total acumulado", value: numberFormatter.format(totals.total) }, ].map((item) => (

{item.label}

{item.value} h

))}
{filteredWithComputed.length === 0 ? (
Nenhuma empresa encontrada para o filtro selecionado.
) : (
{filteredWithComputed.map((row) => (

{row.name}

ID {row.companyId}

{row.isAvulso ? "Cliente avulso" : "Recorrente"}
Horas internas {numberFormatter.format(row.internal)} h
Horas externas {numberFormatter.format(row.external)} h
Total {numberFormatter.format(row.total)} h
Contratadas/mês {row.contracted ? `${numberFormatter.format(row.contracted)} h` : "—"}
Uso {row.usagePercent !== null ? `${row.usagePercent}%` : "—"}
{row.usagePercent !== null ? ( ) : (
Defina horas contratadas para acompanhar o uso
)}
))}
)}
) }