"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" function formatHours(ms: number) { const hours = ms / 3600000 return hours.toFixed(2) } 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] = useState("all") const { session, convexUserId } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const data = useQuery( api.reports.hoursByClient, convexUserId ? { tenantId, viewerId: convexUserId as Id<"users">, range: timeRange } : "skip" ) as { rangeDays: number; items: HoursItem[] } | undefined const items = data?.items ?? [] const companies = useQuery(api.companies.list, convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip") as Array<{ id: Id<"companies">; name: string }> | undefined const filtered = useMemo(() => { 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 }, [items, query, companyId]) return (
Horas por cliente Horas internas e externas registradas por empresa.
setQuery(e.target.value)} className="h-9 w-full min-w-56 sm:w-72" /> 90 dias 30 dias 7 dias
{filtered.map((row) => { const totalH = Number(formatHours(row.totalMs)) const contracted = row.contractedHoursPerMonth ?? null const pct = contracted ? Math.round((totalH / contracted) * 100) : null const pctBadgeVariant: "secondary" | "destructive" = pct !== null && pct >= 90 ? "destructive" : "secondary" return ( ) })}
Cliente Avulso Horas internas Horas externas Total Contratadas/mês Uso
{row.name} {row.isAvulso ? "Sim" : "Não"} {formatHours(row.internalMs)} {formatHours(row.externalMs)} {formatHours(row.totalMs)} {contracted ?? "—"} {pct !== null ? ( {pct}% ) : ( )}
) }