diff --git a/.gitignore b/.gitignore index d0a28da..332964c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ next-env.d.ts # backups locais .archive/ + +# arquivos locais temporários +Captura de tela *.png +Screenshot*.png diff --git a/agents.md b/agents.md index f79e5c8..341fdc8 100644 --- a/agents.md +++ b/agents.md @@ -39,7 +39,9 @@ - Autenticação Better Auth com guardas client-side (`AuthGuard`) bloqueando rotas protegidas. - Menu de usuário no rodapé da sidebar com link para `/settings` e logout confiável. - Formulários de novo ticket (dialog, página e portal) com seleção de responsável, placeholders claros e validação obrigatória de assunto/descrição/categorias. -- Relatórios e dashboards utilizam `AppShell`, garantindo header/sidebar consistentes. +- Relatórios, dashboards e páginas administrativas utilizam `AppShell`, garantindo header/sidebar consistentes. + - Use `SiteHeader` no `header` do `AppShell` para título/lead e ações. + - O conteúdo deve ficar dentro de `
`. ## Entregas recentes - Exportações CSV (Backlog, Canais, CSAT, SLA e Horas por cliente) com parâmetros de período. diff --git a/src/app/admin/alerts/page.tsx b/src/app/admin/alerts/page.tsx index eacaf79..8c8c8b6 100644 --- a/src/app/admin/alerts/page.tsx +++ b/src/app/admin/alerts/page.tsx @@ -1,123 +1,20 @@ -"use client" - -import { useQuery } from "convex/react" -import { api } from "@/convex/_generated/api" -import type { Id, Doc } 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 { Skeleton } from "@/components/ui/skeleton" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { useMemo, useState } from "react" -import { Button } from "@/components/ui/button" +import { AppShell } from "@/components/app-shell" +import { SiteHeader } from "@/components/site-header" +import { AdminAlertsManager } from "@/components/admin/alerts/admin-alerts-manager" export default function AdminAlertsPage() { - const [companyId, setCompanyId] = useState("all") - const [range, setRange] = useState("30d") - const { session, convexUserId } = useAuth() - const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID - const now = new Date() - const days = range === "7d" ? 7 : range === "30d" ? 30 : range === "90d" ? 90 : null - const end = now.getTime() - const start = days ? end - days * 24 * 60 * 60 * 1000 : undefined - const alertsRaw = useQuery( - api.alerts.list, - convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" - ) as Doc<"alerts">[] | undefined - const alerts = useMemo(() => { - let list = alertsRaw ?? [] - if (companyId !== "all") { - list = list.filter((a) => String(a.companyId) === companyId) - } - if (typeof start === "number") list = list.filter((a) => a.createdAt >= start) - if (typeof end === "number") list = list.filter((a) => a.createdAt < end) - return list.sort((a, b) => b.createdAt - a.createdAt) - }, [alertsRaw, companyId, start, end]) - const companies = useQuery(api.companies.list, convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip") as Array<{ id: Id<"companies">; name: string }> | undefined - return ( -
- - - Alertas enviados - - Histórico dos e-mails de alerta de uso de horas disparados automaticamente. - - -
- - - -
-
-
- - {!alerts ? ( -
- {Array.from({ length: 5 }).map((_, i) => ( - - ))} -
- ) : alerts.length === 0 ? ( -

- Nenhum alerta enviado ainda. -

- ) : ( -
- - - - - - - - - - - - - - {alerts.map((a) => ( - - - - - - - - - - ))} - -
QuandoEmpresaUsoLimitePeríodoDestinatáriosEntregues
- {new Date(a.createdAt).toLocaleString("pt-BR")} - {a.companyName}{a.usagePct.toFixed(1)}%{a.threshold}%{a.range}{a.recipients.join(", ")}{a.deliveredCount}
-
- )} -
-
-
+ + } + > +
+ +
+
) } diff --git a/src/components/admin/alerts/admin-alerts-manager.tsx b/src/components/admin/alerts/admin-alerts-manager.tsx new file mode 100644 index 0000000..e9afc04 --- /dev/null +++ b/src/components/admin/alerts/admin-alerts-manager.tsx @@ -0,0 +1,127 @@ +"use client" + +import { useMemo, useState } from "react" +import { useQuery } from "convex/react" + +import { api } from "@/convex/_generated/api" +import type { Id, Doc } 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 { Skeleton } from "@/components/ui/skeleton" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Button } from "@/components/ui/button" + +export function AdminAlertsManager() { + const [companyId, setCompanyId] = useState("all") + const [range, setRange] = useState("30d") + const { session, convexUserId } = useAuth() + const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID + const now = new Date() + const days = range === "7d" ? 7 : range === "30d" ? 30 : range === "90d" ? 90 : null + const end = now.getTime() + const start = days ? end - days * 24 * 60 * 60 * 1000 : undefined + + const alertsRaw = useQuery( + api.alerts.list, + convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" + ) as Doc<"alerts">[] | undefined + + const alerts = useMemo(() => { + let list = alertsRaw ?? [] + if (companyId !== "all") list = list.filter((a) => String(a.companyId) === companyId) + if (typeof start === "number") list = list.filter((a) => a.createdAt >= start) + if (typeof end === "number") list = list.filter((a) => a.createdAt < end) + return list.sort((a, b) => b.createdAt - a.createdAt) + }, [alertsRaw, companyId, start, end]) + + const companies = useQuery( + api.companies.list, + convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" + ) as Array<{ id: Id<"companies">; name: string }> | undefined + + return ( + + + Alertas enviados + + Histórico dos e-mails de alerta de uso de horas disparados automaticamente. + + +
+ + + +
+
+
+ + {!alerts ? ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+ ) : alerts.length === 0 ? ( +

+ Nenhum alerta enviado ainda. +

+ ) : ( +
+ + + + + + + + + + + + + + {alerts.map((a) => ( + + + + + + + + + + ))} + +
QuandoEmpresaUsoLimitePeríodoDestinatáriosEntregues
+ {new Date(a.createdAt).toLocaleString("pt-BR")} + {a.companyName}{a.usagePct.toFixed(1)}%{a.threshold}%{a.range}{a.recipients.join(", ")}{a.deliveredCount}
+
+ )} +
+
+ ) +} +