chore: sync staging

This commit is contained in:
Esdras Renan 2025-11-10 01:57:45 -03:00
parent c5ddd54a3e
commit 561b19cf66
610 changed files with 105285 additions and 1206 deletions

View file

@ -7,17 +7,29 @@ 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 {
Card,
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 { usePersistentCompanyFilter } from "@/lib/use-company-filter"
import { Progress } from "@/components/ui/progress"
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
import type { ChartConfig } from "@/components/ui/chart"
import { formatHoursCompact } from "@/lib/utils"
import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox"
import { ReportsFilterToolbar } from "@/components/reports/report-filter-toolbar"
import { ReportScheduleDrawer } from "@/components/reports/report-schedule-drawer"
type HoursItem = {
companyId: string
@ -43,7 +55,9 @@ const topClientsChartConfig = {
export function HoursReport() {
const [timeRange, setTimeRange] = useState("90d")
const [companyId, setCompanyId] = usePersistentCompanyFilter("all")
const { session, convexUserId, isStaff } = useAuth()
const [billingFilter, setBillingFilter] = useState<"all" | "avulso" | "contratado">("all")
const [schedulerOpen, setSchedulerOpen] = useState(false)
const { session, convexUserId, isStaff, isAdmin } = useAuth()
const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID
const enabled = Boolean(isStaff && convexUserId)
@ -71,12 +85,17 @@ export function HoursReport() {
]
}, [companies])
const filtered = useMemo(() => {
const items = data?.items ?? []
let items = data?.items ?? []
if (companyId !== "all") {
return items.filter((it) => String(it.companyId) === companyId)
items = items.filter((it) => String(it.companyId) === companyId)
}
if (billingFilter === "avulso") {
items = items.filter((it) => it.isAvulso)
} else if (billingFilter === "contratado") {
items = items.filter((it) => !it.isAvulso)
}
return items
}, [data?.items, companyId])
}, [data?.items, companyId, billingFilter])
const totals = useMemo(() => {
return filtered.reduce(
@ -128,6 +147,30 @@ export function HoursReport() {
return (
<div className="space-y-6">
{isAdmin ? (
<ReportScheduleDrawer
open={schedulerOpen}
onOpenChange={setSchedulerOpen}
defaultReports={["hours"]}
defaultRange={timeRange}
defaultCompanyId={companyId === "all" ? null : companyId}
companyOptions={companyOptions}
/>
) : null}
<ReportsFilterToolbar
companyId={companyId}
onCompanyChange={(value) => setCompanyId(value)}
companyOptions={companyOptions}
timeRange={timeRange as "90d" | "30d" | "7d"}
onTimeRangeChange={(value) => setTimeRange(value)}
showBillingFilter
billingFilter={billingFilter}
onBillingFilterChange={(value) => setBillingFilter(value)}
exportHref={`/api/reports/hours-by-client.xlsx?range=${timeRange}${
companyId !== "all" ? `&companyId=${companyId}` : ""
}`}
onOpenScheduler={isAdmin ? () => setSchedulerOpen(true) : undefined}
/>
<Card className="border-slate-200">
<CardHeader>
<CardTitle>Top clientes por horas</CardTitle>
@ -176,45 +219,10 @@ export function HoursReport() {
)}
</CardContent>
</Card>
<Card className="border-slate-200">
<CardHeader>
<CardTitle>Horas</CardTitle>
<CardDescription>Visualize o esforço interno e externo por empresa e acompanhe o consumo contratado.</CardDescription>
<CardAction>
<div className="space-y-4">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<SearchableCombobox
value={companyId}
onValueChange={(next) => setCompanyId(next ?? "all")}
options={companyOptions}
placeholder="Todas as empresas"
className="w-full min-w-56 lg:w-72"
/>
<div className="flex flex-wrap items-center gap-2">
{["90d", "30d", "7d"].map((range) => (
<Button
key={range}
type="button"
size="sm"
variant={timeRange === range ? "default" : "outline"}
onClick={() => setTimeRange(range)}
>
{range === "90d" ? "90 dias" : range === "30d" ? "30 dias" : "7 dias"}
</Button>
))}
<Button asChild size="sm" variant="outline" className="gap-2">
<a
href={`/api/reports/hours-by-client.xlsx?range=${timeRange}${companyId !== "all" ? `&companyId=${companyId}` : ""}`}
download
>
Exportar XLSX
</a>
</Button>
</div>
</div>
</div>
</CardAction>
</CardHeader>
<CardContent>
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">