From 655f779b11f1bd5dde6bc6e7bbaee5dab259827d Mon Sep 17 00:00:00 2001 From: Yamamoto Date: Sat, 3 Jan 2026 10:16:34 -0300 Subject: [PATCH] feat(dashboard): add seeder page for database seed/reset - Add /dashboard/seeder page with Seed and Reset buttons - Add Seeder item to sidebar (superadmin only) - Use seeder API endpoints POST /seed and POST /reset - Add confirmation dialogs for destructive actions --- frontend/src/app/dashboard/seeder/page.tsx | 185 +++++++++++++++++++++ frontend/src/components/sidebar.tsx | 16 +- 2 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/dashboard/seeder/page.tsx diff --git a/frontend/src/app/dashboard/seeder/page.tsx b/frontend/src/app/dashboard/seeder/page.tsx new file mode 100644 index 0000000..dee6b28 --- /dev/null +++ b/frontend/src/app/dashboard/seeder/page.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { AlertTriangle, Database, Loader2, RefreshCw, Trash2 } from "lucide-react"; +import { getSeederApiUrl } from "@/lib/config"; +import { toast } from "sonner"; + +export default function SeederPage() { + const [isSeeding, setIsSeeding] = useState(false); + const [isResetting, setIsResetting] = useState(false); + const [lastResult, setLastResult] = useState(null); + + const handleSeed = async () => { + if (!confirm("Tem certeza que deseja popular o banco de dados com dados de teste?")) { + return; + } + + setIsSeeding(true); + setLastResult(null); + + try { + const response = await fetch(`${getSeederApiUrl()}/seed`, { + method: "POST", + }); + + const data = await response.json(); + + if (response.ok) { + toast.success("Banco populado com sucesso!"); + setLastResult(JSON.stringify(data, null, 2)); + } else { + toast.error(data.message || "Erro ao popular banco"); + setLastResult(JSON.stringify(data, null, 2)); + } + } catch (error) { + toast.error("Erro de conexão com o seeder"); + setLastResult(String(error)); + } finally { + setIsSeeding(false); + } + }; + + const handleReset = async () => { + if (!confirm("⚠️ ATENÇÃO: Isso vai APAGAR todos os dados exceto o SuperAdmin. Tem certeza?")) { + return; + } + + if (!confirm("🚨 ÚLTIMA CONFIRMAÇÃO: Esta ação é IRREVERSÍVEL. Continuar?")) { + return; + } + + setIsResetting(true); + setLastResult(null); + + try { + const response = await fetch(`${getSeederApiUrl()}/reset`, { + method: "POST", + }); + + const data = await response.json(); + + if (response.ok) { + toast.success("Banco resetado com sucesso!"); + setLastResult(JSON.stringify(data, null, 2)); + } else { + toast.error(data.message || "Erro ao resetar banco"); + setLastResult(JSON.stringify(data, null, 2)); + } + } catch (error) { + toast.error("Erro de conexão com o seeder"); + setLastResult(String(error)); + } finally { + setIsResetting(false); + } + }; + + return ( +
+
+

🌱 Seeder

+

+ Popular ou resetar o banco de dados de desenvolvimento +

+
+ +
+ {/* Seed Card */} + + + + + Popular Banco + + + Adiciona dados de teste: empresas, usuários, vagas, candidaturas + + + +
    +
  • ✅ Empresas de exemplo
  • +
  • ✅ Usuários (admin, recruiter, candidate)
  • +
  • ✅ Vagas de emprego
  • +
  • ✅ Candidaturas
  • +
  • ✅ Tags/Categorias
  • +
+ +
+
+ + {/* Reset Card */} + + + + + Resetar Banco + + + Remove TODOS os dados exceto o usuário SuperAdmin + + + +
    +
  • ❌ Apaga todas as empresas
  • +
  • ❌ Apaga todos os usuários (exceto superadmin)
  • +
  • ❌ Apaga todas as vagas
  • +
  • ❌ Apaga todas as candidaturas
  • +
  • ✅ Mantém o SuperAdmin
  • +
+ +
+
+
+ + {/* Result Output */} + {lastResult && ( + + + Resultado + + +
+                            {lastResult}
+                        
+
+
+ )} +
+ ); +} diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index d332bf2..71c8884 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -4,7 +4,7 @@ import Link from "next/link" import Image from "next/image" import { usePathname } from "next/navigation" import { cn } from "@/lib/utils" -import { LayoutDashboard, Briefcase, Users, MessageSquare, Building2, FileText, HelpCircle, Ticket, Settings } from "lucide-react" +import { LayoutDashboard, Briefcase, Users, MessageSquare, Building2, FileText, HelpCircle, Ticket, Settings, Sprout } from "lucide-react" import { getCurrentUser, isAdminUser } from "@/lib/auth" import { useTranslation } from "@/lib/i18n" @@ -45,6 +45,12 @@ const Sidebar = () => { href: "/dashboard/backoffice", icon: FileText, }, + { + title: "Seeder", + href: "/dashboard/seeder", + icon: Sprout, + superadminOnly: true, + }, { title: t('sidebar.messages'), href: "/dashboard/messages", @@ -56,7 +62,7 @@ const Sidebar = () => { icon: Ticket, }, { - title: t('sidebar.settings') || "Settings", // Fallback if translation missing + title: t('sidebar.settings') || "Settings", href: "/dashboard/settings", icon: Settings, }, @@ -117,7 +123,11 @@ const Sidebar = () => { if (isAdminUser(user)) { items = isSuperadmin ? adminItems - : adminItems.filter(item => item.href !== "/dashboard/backoffice" && item.href !== "/dashboard/companies") + : adminItems.filter(item => + item.href !== "/dashboard/backoffice" && + item.href !== "/dashboard/companies" && + !('superadminOnly' in item && item.superadminOnly) + ) } else if (user?.role === "company") { items = companyItems }