gohorsejobs/frontend/src/app/dashboard/empresa/candidaturas/page.tsx
Tiago Yamamoto 1c7ef95c1a first commit
2025-12-09 19:04:48 -03:00

575 lines
20 KiB
TypeScript

"use client";
import { useState } from "react";
import { DashboardHeader } from "@/components/dashboard-header";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Search,
Filter,
Eye,
Mail,
Phone,
MapPin,
Calendar,
Briefcase,
CheckCircle2,
XCircle,
Clock,
MessageSquare,
FileText,
ExternalLink,
} from "lucide-react";
import Link from "next/link";
export default function CompanyApplicationsPage() {
const [searchTerm, setSearchTerm] = useState("");
const [jobFilter, setJobFilter] = useState("all");
const [statusFilter, setStatusFilter] = useState("all");
const applications = [
{
id: "1",
candidate: {
id: "1",
name: "Ana Silva",
email: "ana.silva@email.com",
phone: "(11) 98765-4321",
location: "São Paulo, SP",
avatar: "",
experience: "5 anos",
title: "Desenvolvedora Full Stack",
},
jobId: "1",
jobTitle: "Desenvolvedor Full Stack Sênior",
status: "pending",
appliedAt: "2025-11-18T10:30:00",
resumeUrl: "#",
},
{
id: "2",
candidate: {
id: "2",
name: "Carlos Santos",
email: "carlos.santos@email.com",
phone: "(11) 91234-5678",
location: "Rio de Janeiro, RJ",
avatar: "",
experience: "3 anos",
title: "Designer UX/UI",
},
jobId: "2",
jobTitle: "Designer UX/UI",
status: "reviewing",
appliedAt: "2025-11-18T09:15:00",
resumeUrl: "#",
},
{
id: "3",
candidate: {
id: "3",
name: "Maria Oliveira",
email: "maria.oliveira@email.com",
phone: "(21) 99876-5432",
location: "Belo Horizonte, MG",
avatar: "",
experience: "7 anos",
title: "Engenheira de Dados",
},
jobId: "3",
jobTitle: "Product Manager",
status: "interview",
appliedAt: "2025-11-17T14:20:00",
resumeUrl: "#",
},
{
id: "4",
candidate: {
id: "4",
name: "Pedro Costa",
email: "pedro.costa@email.com",
phone: "(31) 98765-1234",
location: "Curitiba, PR",
avatar: "",
experience: "6 anos",
title: "Product Manager",
},
jobId: "1",
jobTitle: "Desenvolvedor Full Stack Sênior",
status: "rejected",
appliedAt: "2025-11-16T16:45:00",
resumeUrl: "#",
},
{
id: "5",
candidate: {
id: "5",
name: "Juliana Ferreira",
email: "juliana.ferreira@email.com",
phone: "(41) 91234-8765",
location: "Porto Alegre, RS",
avatar: "",
experience: "4 anos",
title: "DevOps Engineer",
},
jobId: "2",
jobTitle: "Designer UX/UI",
status: "accepted",
appliedAt: "2025-11-15T11:00:00",
resumeUrl: "#",
},
];
const statusConfig = {
pending: {
label: "Pendente",
color: "bg-yellow-500",
variant: "secondary" as const,
},
reviewing: {
label: "Em Análise",
color: "bg-blue-500",
variant: "default" as const,
},
interview: {
label: "Entrevista",
color: "bg-purple-500",
variant: "default" as const,
},
accepted: {
label: "Aceito",
color: "bg-green-500",
variant: "default" as const,
},
rejected: {
label: "Rejeitado",
color: "bg-red-500",
variant: "destructive" as const,
},
};
const filteredApplications = applications.filter((app) => {
const matchesSearch =
app.candidate.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.jobTitle.toLowerCase().includes(searchTerm.toLowerCase());
const matchesJob = jobFilter === "all" || app.jobId === jobFilter;
const matchesStatus = statusFilter === "all" || app.status === statusFilter;
return matchesSearch && matchesJob && matchesStatus;
});
const groupedByStatus = {
pending: filteredApplications.filter((a) => a.status === "pending"),
reviewing: filteredApplications.filter((a) => a.status === "reviewing"),
interview: filteredApplications.filter((a) => a.status === "interview"),
accepted: filteredApplications.filter((a) => a.status === "accepted"),
rejected: filteredApplications.filter((a) => a.status === "rejected"),
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffHours / 24);
if (diffHours < 1) return "Agora há pouco";
if (diffHours < 24) return `${diffHours}h`;
if (diffDays === 1) return "Ontem";
if (diffDays < 7) return `${diffDays} dias`;
return date.toLocaleDateString("pt-BR");
};
const ApplicationCard = ({ app }: { app: (typeof applications)[0] }) => (
<Card className="hover:shadow-md transition-shadow">
<CardContent className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex items-start gap-3 flex-1 min-w-0">
<Avatar className="h-12 w-12 shrink-0">
<AvatarImage src={app.candidate.avatar} />
<AvatarFallback className="bg-primary/10 text-primary">
{app.candidate.name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2 mb-2">
<div className="min-w-0">
<h3 className="font-semibold text-base sm:text-lg truncate">
{app.candidate.name}
</h3>
<p className="text-sm text-muted-foreground truncate">
{app.candidate.title}
</p>
</div>
<Badge
variant={
statusConfig[app.status as keyof typeof statusConfig]
.variant
}
className="shrink-0"
>
{statusConfig[app.status as keyof typeof statusConfig].label}
</Badge>
</div>
<div className="space-y-1.5 text-sm text-muted-foreground mb-3">
<div className="flex items-center gap-2">
<Briefcase className="h-4 w-4 shrink-0" />
<span className="truncate">{app.jobTitle}</span>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1">
<div className="flex items-center gap-1">
<MapPin className="h-4 w-4 shrink-0" />
<span className="truncate">{app.candidate.location}</span>
</div>
<div className="flex items-center gap-1">
<Calendar className="h-4 w-4 shrink-0" />
<span className="whitespace-nowrap">
{formatDate(app.appliedAt)}
</span>
</div>
<div className="flex items-center gap-1">
<Clock className="h-4 w-4 shrink-0" />
<span className="whitespace-nowrap">
{app.candidate.experience}
</span>
</div>
</div>
</div>
<div className="flex flex-wrap gap-2">
<Dialog>
<DialogTrigger asChild>
<Button size="sm" variant="outline">
<Eye className="h-4 w-4 mr-1" />
Ver Perfil
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Perfil do Candidato</DialogTitle>
<DialogDescription>
Informações detalhadas sobre {app.candidate.name}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="flex items-start gap-4">
<Avatar className="h-20 w-20">
<AvatarImage src={app.candidate.avatar} />
<AvatarFallback className="bg-primary/10 text-primary text-xl">
{app.candidate.name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<h3 className="font-bold text-xl">
{app.candidate.name}
</h3>
<p className="text-muted-foreground">
{app.candidate.title}
</p>
<Badge
className="mt-2"
variant={
statusConfig[
app.status as keyof typeof statusConfig
].variant
}
>
{
statusConfig[
app.status as keyof typeof statusConfig
].label
}
</Badge>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-4 border-t">
<div>
<Label className="text-xs text-muted-foreground">
Email
</Label>
<p className="text-sm font-medium">
{app.candidate.email}
</p>
</div>
<div>
<Label className="text-xs text-muted-foreground">
Telefone
</Label>
<p className="text-sm font-medium">
{app.candidate.phone}
</p>
</div>
<div>
<Label className="text-xs text-muted-foreground">
Localização
</Label>
<p className="text-sm font-medium">
{app.candidate.location}
</p>
</div>
<div>
<Label className="text-xs text-muted-foreground">
Experiência
</Label>
<p className="text-sm font-medium">
{app.candidate.experience}
</p>
</div>
</div>
<div className="pt-4 border-t">
<Label className="text-xs text-muted-foreground mb-2 block">
Vaga aplicada
</Label>
<p className="text-sm font-medium">{app.jobTitle}</p>
<p className="text-xs text-muted-foreground mt-1">
Candidatura enviada em {formatDate(app.appliedAt)}
</p>
</div>
<div className="flex flex-col sm:flex-row gap-2 pt-4">
<Button className="flex-1" asChild>
<a href={`mailto:${app.candidate.email}`}>
<Mail className="h-4 w-4 mr-2" />
Enviar Email
</a>
</Button>
<Button variant="outline" className="flex-1" asChild>
<a
href={app.resumeUrl}
target="_blank"
rel="noopener noreferrer"
>
<FileText className="h-4 w-4 mr-2" />
Ver Currículo
</a>
</Button>
</div>
</div>
</DialogContent>
</Dialog>
<Button size="sm" variant="outline" asChild>
<a href={`mailto:${app.candidate.email}`}>
<Mail className="h-4 w-4 mr-1" />
Contato
</a>
</Button>
{app.status === "pending" && (
<>
<Button size="sm" variant="default">
<CheckCircle2 className="h-4 w-4 mr-1" />
Aprovar
</Button>
<Button size="sm" variant="destructive">
<XCircle className="h-4 w-4 mr-1" />
Rejeitar
</Button>
</>
)}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
return (
<div className="min-h-screen bg-background">
<DashboardHeader />
<main className="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">
Candidaturas
</h1>
<p className="text-muted-foreground">
Gerencie todas as candidaturas recebidas
</p>
</div>
{/* Stats */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
<Card>
<CardContent className="pt-6">
<div className="text-2xl font-bold text-foreground">
{applications.length}
</div>
<p className="text-xs text-muted-foreground">Total</p>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="text-2xl font-bold text-yellow-600">
{groupedByStatus.pending.length}
</div>
<p className="text-xs text-muted-foreground">Pendentes</p>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="text-2xl font-bold text-blue-600">
{groupedByStatus.reviewing.length}
</div>
<p className="text-xs text-muted-foreground">Em Análise</p>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="text-2xl font-bold text-purple-600">
{groupedByStatus.interview.length}
</div>
<p className="text-xs text-muted-foreground">Entrevistas</p>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="text-2xl font-bold text-green-600">
{groupedByStatus.accepted.length}
</div>
<p className="text-xs text-muted-foreground">Aceitos</p>
</CardContent>
</Card>
</div>
{/* Filters */}
<Card>
<CardContent className="pt-6">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="relative sm:col-span-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Buscar candidato ou vaga..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<Select value={jobFilter} onValueChange={setJobFilter}>
<SelectTrigger>
<SelectValue placeholder="Filtrar por vaga" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todas as vagas</SelectItem>
<SelectItem value="1">
Desenvolvedor Full Stack Sênior
</SelectItem>
<SelectItem value="2">Designer UX/UI</SelectItem>
<SelectItem value="3">Product Manager</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger>
<SelectValue placeholder="Filtrar por status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todos os status</SelectItem>
<SelectItem value="pending">Pendente</SelectItem>
<SelectItem value="reviewing">Em Análise</SelectItem>
<SelectItem value="interview">Entrevista</SelectItem>
<SelectItem value="accepted">Aceito</SelectItem>
<SelectItem value="rejected">Rejeitado</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* Applications by Status */}
<Tabs defaultValue="all" className="space-y-4">
<TabsList className="grid w-full grid-cols-3 sm:grid-cols-6">
<TabsTrigger value="all">Todas</TabsTrigger>
<TabsTrigger value="pending">Pendentes</TabsTrigger>
<TabsTrigger value="reviewing">Em Análise</TabsTrigger>
<TabsTrigger value="interview">Entrevista</TabsTrigger>
<TabsTrigger value="accepted">Aceitos</TabsTrigger>
<TabsTrigger value="rejected">Rejeitados</TabsTrigger>
</TabsList>
<TabsContent value="all" className="space-y-4">
{filteredApplications.map((app) => (
<ApplicationCard key={app.id} app={app} />
))}
</TabsContent>
{Object.entries(groupedByStatus).map(([status, apps]) => (
<TabsContent key={status} value={status} className="space-y-4">
{apps.length > 0 ? (
apps.map((app) => <ApplicationCard key={app.id} app={app} />)
) : (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<MessageSquare className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground">
Nenhuma candidatura com status "
{
statusConfig[status as keyof typeof statusConfig]
.label
}
"
</p>
</CardContent>
</Card>
)}
</TabsContent>
))}
</Tabs>
</div>
</main>
</div>
);
}
function Label({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return <label className={className}>{children}</label>;
}