"use client" import { useState } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { useMutation, useQuery } from "convex/react" import { formatDistanceToNow } from "date-fns" import { ptBR } from "date-fns/locale" import { LayoutTemplate, MonitorPlay, Plus, Share2, Sparkles, Trash2 } from "lucide-react" import type { Id } from "@/convex/_generated/dataModel" import { api } from "@/convex/_generated/api" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { useAuth } from "@/lib/auth-client" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Skeleton } from "@/components/ui/skeleton" import { Badge } from "@/components/ui/badge" import { toast } from "sonner" type DashboardSummary = { id: Id<"dashboards"> tenantId: string name: string description: string | null aspectRatio: string theme: string filters: Record layout: Array<{ i: string; x: number; y: number; w: number; h: number }> sections: Array<{ id: string title?: string description?: string widgetKeys: string[] durationSeconds?: number }> tvIntervalSeconds: number readySelector: string | null createdBy: Id<"users"> updatedBy: Id<"users"> | null createdAt: number updatedAt: number isArchived: boolean widgetsCount: number } type CreateDashboardDialogProps = { onCreate: (name: string, description: string | null) => Promise isLoading: boolean } function CreateDashboardDialog({ onCreate, isLoading }: CreateDashboardDialogProps) { const [open, setOpen] = useState(false) const [name, setName] = useState("") const [description, setDescription] = useState("") async function handleSubmit(event: React.FormEvent) { event.preventDefault() const trimmedName = name.trim() if (!trimmedName) { toast.error("Informe um nome para o dashboard.") return } await onCreate(trimmedName, description.trim() ? description.trim() : null) setOpen(false) setName("") setDescription("") } return ( <> Criar dashboard
setName(event.target.value)} placeholder="Ex.: Operações - Visão Geral" required />
setDescription(event.target.value)} placeholder="Contextualize para a equipe" />
) } export function DashboardListView() { const router = useRouter() const { session, convexUserId, isStaff } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const createDashboard = useMutation(api.dashboards.create) const archiveDashboard = useMutation(api.dashboards.archive) const [isCreating, setIsCreating] = useState(false) const [dashboardToDelete, setDashboardToDelete] = useState(null) const [isDeleting, setIsDeleting] = useState(false) const dashboards = useQuery( api.dashboards.list, isStaff && convexUserId ? ({ tenantId, viewerId: convexUserId as Id<"users">, } as const) : "skip" ) as DashboardSummary[] | undefined async function handleCreate(name: string, description: string | null) { if (!convexUserId) return setIsCreating(true) try { const result = await createDashboard({ tenantId, actorId: convexUserId as Id<"users">, name, description: description ?? undefined, }) toast.success("Dashboard criado com sucesso!") router.push(`/dashboards/${result.id}`) } catch (error) { console.error(error) toast.error("Não foi possível criar o dashboard.") } finally { setIsCreating(false) } } async function handleConfirmDelete() { if (!dashboardToDelete || !convexUserId) return setIsDeleting(true) try { await archiveDashboard({ tenantId, actorId: convexUserId as Id<"users">, dashboardId: dashboardToDelete.id, archived: true, }) toast.success("Dashboard removido.") setDashboardToDelete(null) } catch (error) { console.error(error) toast.error("Não foi possível remover o dashboard.") } finally { setIsDeleting(false) } } if (!isStaff) { return ( Acesso restrito Somente a equipe interna pode visualizar e montar dashboards. ) } if (!dashboards) { return (
{Array.from({ length: 3 }).map((_, index) => ( ))}
) } const activeDashboards = dashboards.filter((dashboard) => !dashboard.isArchived) const renderCreateButton = () => ( ) return (

Painéis customizados

Combine KPIs, gráficos, tabelas e texto em painéis dinâmicos com filtros globais.

{activeDashboards.length > 0 ? renderCreateButton() : null}
{activeDashboards.length === 0 ? (

Nenhum painel ainda

Use KPIs, filas e texto para contar a história da operação.

  • Escolha widgets arrastando no canvas e organize por seções.
  • Compartilhe com a equipe, salve filtros padrão e gere PDFs/PNGs.
  • Entre no modo apresentação/TV para um loop automático em tela cheia.
{renderCreateButton()}
) : (
{activeDashboards.map((dashboard) => { const updatedAt = formatDistanceToNow(dashboard.updatedAt, { addSuffix: true, locale: ptBR, }) return ( {dashboard.name} {dashboard.widgetsCount} widget{dashboard.widgetsCount === 1 ? "" : "s"} {dashboard.description ? ( {dashboard.description} ) : null}
Atualizado {updatedAt}
Formato {dashboard.aspectRatio} {dashboard.theme && dashboard.theme.toLowerCase() !== "system" ? ( Tema {dashboard.theme} ) : null}
) })}
)} { if (!open && !isDeleting) { setDashboardToDelete(null) } }} > Excluir painel Essa ação remove o painel para toda a equipe. Confirme para continuar.
) }