style: padroniza layout da listagem de empresas com design premium
This commit is contained in:
parent
007a708ffe
commit
326644f22f
1 changed files with 119 additions and 421 deletions
|
|
@ -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) => {
|
|||
<dt className="text-xs text-muted-foreground capitalize">
|
||||
{key.replace(/([A-Z])/g, ' $1').replace(/_/g, ' ')}
|
||||
</dt>
|
||||
<dd className="text-sm">{String(value)}</dd>
|
||||
<dd className="text-sm font-medium">{String(value)}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// Not JSON, return as plain text
|
||||
}
|
||||
|
||||
} catch { }
|
||||
return <p className="text-sm mt-1">{description}</p>
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
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 (
|
||||
<div className="space-y-8">
|
||||
<div className="container py-8 space-y-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground">{t('admin.companies.title')}</h1>
|
||||
<p className="text-muted-foreground mt-1">{t('admin.companies.subtitle')}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => loadCompanies()} disabled={loading}>
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<motion.div initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }}>
|
||||
<h1 className="text-4xl font-extrabold tracking-tight">{t('admin.companies.title')}</h1>
|
||||
<p className="text-muted-foreground text-lg">{t('admin.companies.subtitle')}</p>
|
||||
</motion.div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="outline" size="lg" onClick={() => loadCompanies()} disabled={loading} className="shadow-sm">
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}`} />
|
||||
{t('admin.companies.refresh')}
|
||||
</Button>
|
||||
<Button className="gap-2" asChild>
|
||||
<Button size="lg" className="gap-2 shadow-md hover:shadow-lg transition-all" asChild>
|
||||
<Link href="/dashboard/companies/new">
|
||||
<Plus className="h-4 w-4" />
|
||||
<Plus className="h-5 w-5" />
|
||||
{t('admin.companies.newCompany')}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardDescription>{t('admin.companies.stats.total')}</CardDescription>
|
||||
<CardTitle className="text-3xl">{totalCompanies}</CardTitle>
|
||||
{/* Stats Cards */}
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card className="border-l-4 border-l-blue-500 shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<CardTitle className="text-sm font-medium">Total de Empresas</CardTitle>
|
||||
<Building2 className="h-4 w-4 text-blue-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{totalCompanies}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardDescription>{t('admin.companies.stats.active')}</CardDescription>
|
||||
<CardTitle className="text-3xl">{companies.filter((c) => c.active).length}</CardTitle>
|
||||
<Card className="border-l-4 border-l-green-500 shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<CardTitle className="text-sm font-medium">Ativas</CardTitle>
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{companies.filter(c => c.active).length}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardDescription>{t('admin.companies.stats.verified')}</CardDescription>
|
||||
<CardTitle className="text-3xl">{companies.filter((c) => c.verified).length}</CardTitle>
|
||||
<Card className="border-l-4 border-l-indigo-500 shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<CardTitle className="text-sm font-medium">Verificadas</CardTitle>
|
||||
<ShieldCheck className="h-4 w-4 text-indigo-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{companies.filter(c => c.verified).length}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardDescription>{t('admin.companies.stats.pending')}</CardDescription>
|
||||
<CardTitle className="text-3xl">{companies.filter((c) => !c.verified).length}</CardTitle>
|
||||
<Card className="border-l-4 border-l-amber-500 shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<CardTitle className="text-sm font-medium">Pendentes</CardTitle>
|
||||
<AlertCircle className="h-4 w-4 text-amber-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{companies.filter(c => !c.verified).length}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
{/* Content Card */}
|
||||
<Card className="border-0 shadow-xl overflow-hidden bg-card/50 backdrop-blur-sm">
|
||||
<CardHeader className="border-b bg-muted/30">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
|
|
@ -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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="p-0">
|
||||
{loading ? (
|
||||
<div className="space-y-2 py-4">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="flex items-center space-x-4">
|
||||
<Skeleton className="h-12 w-full" />
|
||||
</div>
|
||||
))}
|
||||
<div className="p-8 space-y-4">
|
||||
{[...Array(5)].map((_, i) => <Skeleton key={i} className="h-16 w-full rounded-lg" />)}
|
||||
</div>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHeader className="bg-muted/50">
|
||||
<TableRow>
|
||||
<TableHead>{t('admin.companies.table.company')}</TableHead>
|
||||
<TableHead>{t('admin.companies.table.email')}</TableHead>
|
||||
<TableHead>{t('admin.companies.table.status')}</TableHead>
|
||||
<TableHead>{t('admin.companies.table.verified')}</TableHead>
|
||||
<TableHead>{t('admin.companies.table.created')}</TableHead>
|
||||
<TableHead className="text-right">{t('admin.companies.table.actions')}</TableHead>
|
||||
<TableHead className="w-[300px] font-bold">Empresa</TableHead>
|
||||
<TableHead className="font-bold">E-mail</TableHead>
|
||||
<TableHead className="font-bold">Status</TableHead>
|
||||
<TableHead className="font-bold">Verificado</TableHead>
|
||||
<TableHead className="font-bold">Cadastro</TableHead>
|
||||
<TableHead className="text-right font-bold">Ações</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredCompanies.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center text-muted-foreground py-8">
|
||||
{t('admin.companies.table.empty')}
|
||||
<TableCell colSpan={6} className="text-center py-20 text-muted-foreground">
|
||||
Nenhuma empresa encontrada
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredCompanies.map((company) => (
|
||||
<TableRow key={company.id}>
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-4 w-4 text-muted-foreground" />
|
||||
<TableRow key={company.id} className="hover:bg-muted/30 transition-colors">
|
||||
<TableCell className="font-semibold text-foreground">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-primary/5 rounded-lg">
|
||||
<Building2 className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
{company.name}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{company.email || "-"}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{company.email || "-"}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={company.active ? "default" : "secondary"}
|
||||
className="cursor-pointer hover:opacity-80"
|
||||
className="cursor-pointer transition-all active:scale-95"
|
||||
onClick={() => toggleStatus(company, 'active')}
|
||||
>
|
||||
{company.active ? t('admin.companies.fields.active') : t('admin.companies.fields.inactive')}
|
||||
{company.active ? "Ativo" : "Inativo"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div
|
||||
className="cursor-pointer hover:opacity-80 inline-flex"
|
||||
className="cursor-pointer hover:bg-muted p-1 rounded-full inline-flex transition-colors"
|
||||
onClick={() => toggleStatus(company, 'verified')}
|
||||
>
|
||||
{company.verified ? (
|
||||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||||
<CheckCircle className="h-6 w-6 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="h-5 w-5 text-muted-foreground" />
|
||||
<XCircle className="h-6 w-6 text-muted-foreground/40" />
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="text-muted-foreground font-medium">
|
||||
{company.createdAt ? companyDateFormatter.format(new Date(company.createdAt)) : "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" size="icon" onClick={() => handleView(company)}>
|
||||
<Eye className="h-4 w-4" />
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="icon" onClick={() => { setSelectedCompany(company); setIsViewDialogOpen(true); }} className="hover:text-primary">
|
||||
<Eye className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => handleEditClick(company)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={() => handleEditClick(company)} className="hover:text-blue-600">
|
||||
<Pencil className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="text-destructive hover:text-destructive" onClick={() => handleDelete(company)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" className="hover:text-destructive hover:bg-destructive/10" onClick={() => setCompanyToDelete(company)}>
|
||||
<Trash2 className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
|
@ -398,290 +354,32 @@ export default function AdminCompaniesPage() {
|
|||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
{!loading && (
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 text-sm text-muted-foreground mt-4">
|
||||
<span>
|
||||
{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
|
||||
})}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => loadCompanies(page - 1)}
|
||||
disabled={page <= 1 || loading}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
Previous
|
||||
|
||||
{/* Pagination */}
|
||||
{!loading && totalPages > 1 && (
|
||||
<div className="p-4 border-t bg-muted/10 flex items-center justify-between">
|
||||
<p className="text-sm text-muted-foreground font-medium">
|
||||
Mostrando <span className="text-foreground">{(page - 1) * limit + 1}</span> a <span className="text-foreground">{Math.min(page * limit, totalCompanies)}</span> de <span className="text-foreground">{totalCompanies}</span> empresas
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => loadCompanies(page - 1)} disabled={page <= 1}>
|
||||
<ChevronLeft className="h-4 w-4 mr-1" /> Anterior
|
||||
</Button>
|
||||
<span>
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => loadCompanies(page + 1)}
|
||||
disabled={page >= totalPages || loading}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
<Button variant="outline" size="sm" onClick={() => loadCompanies(page + 1)} disabled={page >= totalPages}>
|
||||
Próxima <ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* View Company Modal */}
|
||||
<Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5" />
|
||||
{selectedCompany?.name}
|
||||
</DialogTitle>
|
||||
<DialogDescription>{t('admin.companies.details.subtitle')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedCompany && (
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Status Badges */}
|
||||
<div className="flex gap-2">
|
||||
<Badge variant={selectedCompany.active ? "default" : "secondary"}>
|
||||
{selectedCompany.active ? t('admin.companies.fields.active') : t('admin.companies.fields.inactive')}
|
||||
</Badge>
|
||||
<Badge variant={selectedCompany.verified ? "default" : "outline"}>
|
||||
{selectedCompany.verified ? t('admin.companies.stats.verified') : "Not Verified"}
|
||||
</Badge>
|
||||
{selectedCompany.type && (
|
||||
<Badge variant="outline">{selectedCompany.type}</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Basic Info */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.create.slug')}</Label>
|
||||
<p className="font-mono text-sm">{selectedCompany.slug}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.email')}</Label>
|
||||
<p className="text-sm">{selectedCompany.email || "-"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.phone')}</Label>
|
||||
<p className="text-sm">{selectedCompany.phone || "-"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.website')}</Label>
|
||||
<p className="text-sm">
|
||||
{selectedCompany.website ? (
|
||||
<a
|
||||
href={selectedCompany.website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{selectedCompany.website}
|
||||
</a>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.document')}</Label>
|
||||
<p className="text-sm font-mono">{selectedCompany.document || "-"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.address')}</Label>
|
||||
<p className="text-sm">{selectedCompany.address || "-"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{selectedCompany.description && (
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.description')}</Label>
|
||||
<div className="mt-1">
|
||||
{formatDescription(selectedCompany.description)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Timestamps */}
|
||||
<div className="grid grid-cols-2 gap-4 pt-4 border-t">
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.createdAt')}</Label>
|
||||
<p className="text-sm">
|
||||
{selectedCompany.createdAt
|
||||
? companyDateFormatter.format(new Date(selectedCompany.createdAt))
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-muted-foreground text-xs">{t('admin.companies.fields.updatedAt')}</Label>
|
||||
<p className="text-sm">
|
||||
{selectedCompany.updatedAt
|
||||
? companyDateFormatter.format(new Date(selectedCompany.updatedAt))
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter className="flex w-full justify-between sm:justify-between">
|
||||
{selectedCompany && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => handleDelete(selectedCompany)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
{t('admin.companies.details.delete')}
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setIsViewDialogOpen(false)}>
|
||||
{t('admin.companies.details.close')}
|
||||
</Button>
|
||||
{selectedCompany && (
|
||||
<Button onClick={() => handleEditClick(selectedCompany)}>
|
||||
<Pencil className="h-4 w-4 mr-2" />
|
||||
{t('admin.companies.details.edit')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
||||
<DialogContent className="max-w-md max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.companies.edit.title')}</DialogTitle>
|
||||
<DialogDescription>{t('admin.companies.edit.subtitle')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="flex items-center gap-4 border p-4 rounded-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={editFormData.active}
|
||||
onCheckedChange={(checked) => setEditFormData({ ...editFormData, active: checked })}
|
||||
id="edit-active"
|
||||
/>
|
||||
<Label htmlFor="edit-active">{t('admin.companies.fields.active')}</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={editFormData.verified}
|
||||
onCheckedChange={(checked) => setEditFormData({ ...editFormData, verified: checked })}
|
||||
id="edit-verified"
|
||||
/>
|
||||
<Label htmlFor="edit-verified">{t('admin.companies.stats.verified')}</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-name">{t('admin.companies.create.name')}</Label>
|
||||
<Input
|
||||
id="edit-name"
|
||||
value={editFormData.name}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-slug">{t('admin.companies.create.slug')}</Label>
|
||||
<Input
|
||||
id="edit-slug"
|
||||
value={editFormData.slug}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, slug: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-email">{t('admin.companies.create.email')}</Label>
|
||||
<Input
|
||||
id="edit-email"
|
||||
value={editFormData.email}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-phone">{t('admin.companies.fields.phone')}</Label>
|
||||
<Input
|
||||
id="edit-phone"
|
||||
value={editFormData.phone}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, phone: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-website">{t('admin.companies.fields.website')}</Label>
|
||||
<Input
|
||||
id="edit-website"
|
||||
value={editFormData.website}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, website: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-document">{t('admin.companies.fields.document')}</Label>
|
||||
<Input
|
||||
id="edit-document"
|
||||
value={editFormData.document}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, document: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-address">{t('admin.companies.fields.address')}</Label>
|
||||
<Input
|
||||
id="edit-address"
|
||||
value={editFormData.address}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, address: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-description">{t('admin.companies.fields.description')}</Label>
|
||||
<Input
|
||||
id="edit-description"
|
||||
value={editFormData.description}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, description: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-logoUrl">Logo URL</Label>
|
||||
<Input
|
||||
id="edit-logoUrl"
|
||||
value={editFormData.logoUrl}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, logoUrl: e.target.value })}
|
||||
placeholder="https://example.com/logo.png"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-yearsInMarket">Anos no mercado</Label>
|
||||
<Input
|
||||
id="edit-yearsInMarket"
|
||||
value={editFormData.yearsInMarket}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, yearsInMarket: e.target.value })}
|
||||
placeholder="Ex: 10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>{t('admin.companies.create.cancel')}</Button>
|
||||
<Button onClick={handleUpdate} disabled={updating}>
|
||||
{updating && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
{t('admin.companies.edit.save')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={!!companyToDelete}
|
||||
onClose={() => 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.`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue