diff --git a/frontend/src/app/dashboard/companies/page.tsx b/frontend/src/app/dashboard/companies/page.tsx index 543bc24..a3a7e29 100644 --- a/frontend/src/app/dashboard/companies/page.tsx +++ b/frontend/src/app/dashboard/companies/page.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/navigation" import Link from "next/link" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" @@ -16,10 +15,25 @@ import { DialogFooter, DialogHeader, DialogTitle, - DialogTrigger, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" -import { Plus, Search, Loader2, RefreshCw, Building2, CheckCircle, XCircle, Eye, EyeOff, Trash2, Pencil, ChevronLeft, ChevronRight } from "lucide-react" +import { + Plus, + Search, + Loader2, + RefreshCw, + Building2, + CheckCircle, + XCircle, + Eye, + Trash2, + Pencil, + ChevronLeft, + ChevronRight, + Users, + ShieldCheck, + AlertCircle +} from "lucide-react" import { Switch } from "@/components/ui/switch" import { adminCompaniesApi, type AdminCompany } from "@/lib/api" import { getCurrentUser, isAdminUser } from "@/lib/auth" @@ -27,16 +41,14 @@ import { toast } from "sonner" import { ConfirmModal } from "@/components/confirm-modal" import { Skeleton } from "@/components/ui/skeleton" import { useTranslation } from "@/lib/i18n" +import { motion } from "framer-motion" -const companyDateFormatter = new Intl.DateTimeFormat("en-US", { +const companyDateFormatter = new Intl.DateTimeFormat("pt-BR", { dateStyle: "medium", - timeZone: "UTC", }) -// Helper to format description (handles JSON or plain text) const formatDescription = (description: string | undefined) => { if (!description) return null - try { const parsed = JSON.parse(description) if (typeof parsed === 'object' && parsed !== null) { @@ -47,39 +59,16 @@ const formatDescription = (description: string | undefined) => {
{key.replace(/([A-Z])/g, ' $1').replace(/_/g, ' ')}
-
{String(value)}
+
{String(value)}
))} ) } - } catch { - // Not JSON, return as plain text - } - + } catch { } return

{description}

} -// Format CNPJ: 00.000.000/0000-00 -const formatCNPJ = (value: string) => { - return value - .replace(/\D/g, "") - .replace(/^(\d{2})(\d)/, "$1.$2") - .replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3") - .replace(/\.(\d{3})(\d)/, ".$1/$2") - .replace(/(\d{4})(\d)/, "$1-$2") - .substring(0, 18) -} - -// Format Phone: (00) 00000-0000 -const formatPhone = (value: string) => { - return value - .replace(/\D/g, "") - .replace(/^(\d{2})(\d)/, "($1) $2") - .replace(/(\d{5})(\d)/, "$1-$2") - .substring(0, 15) -} - export default function AdminCompaniesPage() { const { t } = useTranslation() const router = useRouter() @@ -121,10 +110,7 @@ export default function AdminCompaniesPage() { const totalPages = Math.max(1, Math.ceil(totalCompanies / limit)) const loadCompanies = async (targetPage = page) => { - // If coming from onClick event, targetPage might be the event object - // Ensure it is a number const pageNum = typeof targetPage === 'number' ? targetPage : page - try { setLoading(true) const data = await adminCompaniesApi.list(undefined, pageNum, limit) @@ -133,58 +119,34 @@ export default function AdminCompaniesPage() { setPage(data.pagination.page) } catch (error) { console.error("Error loading companies:", error) - toast.error("Failed to load companies") + toast.error("Erro ao carregar empresas") } finally { setLoading(false) } } - const handleView = (company: AdminCompany) => { - setSelectedCompany(company) - setIsViewDialogOpen(true) - } - const toggleStatus = async (company: AdminCompany, field: 'active' | 'verified') => { const newValue = !company[field] - // Optimistic update const originalCompanies = [...companies] setCompanies(companies.map(c => c.id === company.id ? { ...c, [field]: newValue } : c)) - try { await adminCompaniesApi.updateStatus(company.id, { [field]: newValue }) toast.success(t('admin.companies.success.statusUpdated', { field })) } catch (error) { - toast.error(`Failed to update ${field}`) + toast.error(`Falha ao atualizar ${field}`) setCompanies(originalCompanies) } } - const generateSlug = (name: string) => { - return name - .toLowerCase() - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") - .replace(/[^a-z0-9]+/g, "-") - .replace(/(^-|-$)/g, "") - } - - const handleDelete = async (company: AdminCompany) => { - setCompanyToDelete(company) - } - const confirmDelete = async () => { if (!companyToDelete) return - try { await adminCompaniesApi.delete(companyToDelete.id) toast.success(t('admin.companies.success.deleted')) - if (selectedCompany?.id === companyToDelete.id) { - setIsViewDialogOpen(false) - } + setIsViewDialogOpen(false) loadCompanies() } catch (error) { - console.error("Error deleting company:", error) - toast.error("Failed to delete company") + toast.error("Falha ao deletar empresa") } finally { setCompanyToDelete(null) } @@ -213,34 +175,18 @@ export default function AdminCompaniesPage() { if (!selectedCompany) return try { setUpdating(true) - - // Check if status changed if (editFormData.active !== selectedCompany.active || editFormData.verified !== selectedCompany.verified) { await adminCompaniesApi.updateStatus(selectedCompany.id, { active: editFormData.active, verified: editFormData.verified }) } - - await adminCompaniesApi.update(selectedCompany.id, { - name: editFormData.name, - slug: editFormData.slug, - email: editFormData.email, - phone: editFormData.phone, - website: editFormData.website, - document: editFormData.document, - address: editFormData.address, - description: editFormData.description, - logoUrl: editFormData.logoUrl || undefined, - yearsInMarket: editFormData.yearsInMarket || undefined, - } as any) - - toast.success(t('admin.companies.success.updated')) + await adminCompaniesApi.update(selectedCompany.id, editFormData as any) + toast.success("Empresa atualizada com sucesso") setIsEditDialogOpen(false) loadCompanies() } catch (error) { - console.error("Error updating company:", error) - toast.error("Failed to update company") + toast.error("Erro ao atualizar empresa") } finally { setUpdating(false) } @@ -253,58 +199,70 @@ export default function AdminCompaniesPage() { ) return ( -
+
{/* Header */} -
-
-

{t('admin.companies.title')}

-

{t('admin.companies.subtitle')}

-
-
- -
- {/* Stats */} -
- - - {t('admin.companies.stats.total')} - {totalCompanies} + {/* Stats Cards */} +
+ + + Total de Empresas + + +
{totalCompanies}
+
- - - {t('admin.companies.stats.active')} - {companies.filter((c) => c.active).length} + + + Ativas + + +
{companies.filter(c => c.active).length}
+
- - - {t('admin.companies.stats.verified')} - {companies.filter((c) => c.verified).length} + + + Verificadas + + +
{companies.filter(c => c.verified).length}
+
- - - {t('admin.companies.stats.pending')} - {companies.filter((c) => !c.verified).length} + + + Pendentes + + +
{companies.filter(c => !c.verified).length}
+
- {/* Table */} - - + {/* Content Card */} + +
@@ -312,83 +270,81 @@ export default function AdminCompaniesPage() { placeholder={t('admin.companies.searchPlaceholder')} value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - className="pl-10" + className="pl-10 h-11 border-muted-foreground/20 focus:ring-primary" />
- + {loading ? ( -
- {[...Array(5)].map((_, i) => ( -
- -
- ))} +
+ {[...Array(5)].map((_, i) => )}
) : ( - + - {t('admin.companies.table.company')} - {t('admin.companies.table.email')} - {t('admin.companies.table.status')} - {t('admin.companies.table.verified')} - {t('admin.companies.table.created')} - {t('admin.companies.table.actions')} + Empresa + E-mail + Status + Verificado + Cadastro + Ações {filteredCompanies.length === 0 ? ( - - {t('admin.companies.table.empty')} + + Nenhuma empresa encontrada ) : ( filteredCompanies.map((company) => ( - - -
- + + +
+
+ +
{company.name}
- {company.email || "-"} + {company.email || "-"} toggleStatus(company, 'active')} > - {company.active ? t('admin.companies.fields.active') : t('admin.companies.fields.inactive')} + {company.active ? "Ativo" : "Inativo"}
toggleStatus(company, 'verified')} > {company.verified ? ( - + ) : ( - + )}
- + {company.createdAt ? companyDateFormatter.format(new Date(company.createdAt)) : "-"} -
- - -
@@ -398,291 +354,33 @@ export default function AdminCompaniesPage() {
)} - {!loading && ( -
- - {totalCompanies === 0 - ? t('admin.companies.table.empty') - : t('admin.companies.table.showing', { - from: (page - 1) * limit + 1, - to: Math.min(page * limit, totalCompanies), - total: totalCompanies - })} - -
- - - Page {page} of {totalPages} - -
)} - {/* View Company Modal */} - - - - - - {selectedCompany?.name} - - {t('admin.companies.details.subtitle')} - - {selectedCompany && ( -
- {/* Status Badges */} -
- - {selectedCompany.active ? t('admin.companies.fields.active') : t('admin.companies.fields.inactive')} - - - {selectedCompany.verified ? t('admin.companies.stats.verified') : "Not Verified"} - - {selectedCompany.type && ( - {selectedCompany.type} - )} -
- - {/* Basic Info */} -
-
- -

{selectedCompany.slug}

-
-
- -

{selectedCompany.email || "-"}

-
-
- -

{selectedCompany.phone || "-"}

-
-
- -

- {selectedCompany.website ? ( - - {selectedCompany.website} - - ) : ( - "-" - )} -

-
-
- -

{selectedCompany.document || "-"}

-
-
- -

{selectedCompany.address || "-"}

-
-
- - {/* Description */} - {selectedCompany.description && ( -
- -
- {formatDescription(selectedCompany.description)} -
-
- )} - - {/* Timestamps */} -
-
- -

- {selectedCompany.createdAt - ? companyDateFormatter.format(new Date(selectedCompany.createdAt)) - : "-"} -

-
-
- -

- {selectedCompany.updatedAt - ? companyDateFormatter.format(new Date(selectedCompany.updatedAt)) - : "-"} -

-
-
-
- )} - - {selectedCompany && ( - - )} -
- - {selectedCompany && ( - - )} -
-
-
-
- - - - - {t('admin.companies.edit.title')} - {t('admin.companies.edit.subtitle')} - -
-
-
- setEditFormData({ ...editFormData, active: checked })} - id="edit-active" - /> - -
-
- setEditFormData({ ...editFormData, verified: checked })} - id="edit-verified" - /> - -
-
-
- - setEditFormData({ ...editFormData, name: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, slug: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, email: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, phone: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, website: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, document: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, address: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, description: e.target.value })} - /> -
-
- - setEditFormData({ ...editFormData, logoUrl: e.target.value })} - placeholder="https://example.com/logo.png" - /> -
-
- - setEditFormData({ ...editFormData, yearsInMarket: e.target.value })} - placeholder="Ex: 10" - /> -
-
- - - - -
-
setCompanyToDelete(null)} onConfirm={confirmDelete} - title={companyToDelete ? t('admin.companies.deleteConfirm', { name: companyToDelete.name }) : ""} - description="This action cannot be undone." + title="Excluir Empresa" + description={`Tem certeza que deseja excluir ${companyToDelete?.name}? Esta ação não pode ser desfeita.`} /> -
+
) }