gohorsejobs/frontend/src/app/dashboard/applications/page.tsx

348 lines
17 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Input } from "@/components/ui/input"
import {
Search,
Filter,
Mail,
Phone,
FileText,
Calendar,
MessageSquare,
MoreVertical,
Eye,
Check,
X,
Clock,
Star,
} from "lucide-react"
// Mock data - in production this would come from API
const mockApplications = [
{
id: "1",
candidateName: "Ana Silva",
email: "ana.silva@email.com",
phone: "+55 11 99999-1111",
jobTitle: "Senior Full Stack Developer",
appliedAt: "2 hours ago",
status: "pending",
resumeUrl: "https://cdn.gohorsejobs.com/docs/ana_silva_cv.pdf",
message: "Tenho 5 anos de experiência como desenvolvedora Full Stack...",
},
{
id: "2",
candidateName: "Carlos Santos",
email: "carlos.santos@email.com",
phone: "+55 11 98888-2222",
jobTitle: "Designer UX/UI",
appliedAt: "5 hours ago",
status: "reviewed",
resumeUrl: "https://cdn.gohorsejobs.com/docs/carlos_santos_portfolio.pdf",
message: "Designer UX/UI com 3 anos de experiência...",
},
{
id: "3",
candidateName: "Maria Oliveira",
email: "maria.oliveira@email.com",
phone: "+55 21 97777-3333",
jobTitle: "Product Manager",
appliedAt: "1 day ago",
status: "shortlisted",
resumeUrl: "https://cdn.gohorsejobs.com/docs/maria_oliveira_resume.pdf",
message: "Product Manager com 4 anos de experiência...",
},
{
id: "4",
candidateName: "Pedro Costa",
email: "pedro.costa@email.com",
phone: "+55 11 96666-4444",
jobTitle: "Backend Developer",
appliedAt: "2 days ago",
status: "hired",
resumeUrl: "https://cdn.gohorsejobs.com/docs/pedro_costa_cv.pdf",
message: "Desenvolvedor Backend com experiência em Go...",
},
{
id: "5",
candidateName: "Juliana Ferreira",
email: "juliana.ferreira@email.com",
phone: "+55 11 95555-5555",
jobTitle: "Data Scientist",
appliedAt: "3 days ago",
status: "rejected",
resumeUrl: "https://cdn.gohorsejobs.com/docs/juliana_ferreira_cv.pdf",
message: "Data Scientist com mestrado em Machine Learning...",
},
]
const statusConfig = {
pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800 border-yellow-200", icon: Clock },
reviewed: { label: "Reviewed", color: "bg-blue-100 text-blue-800 border-blue-200", icon: Eye },
shortlisted: { label: "Shortlisted", color: "bg-purple-100 text-purple-800 border-purple-200", icon: Star },
hired: { label: "Hired", color: "bg-green-100 text-green-800 border-green-200", icon: Check },
rejected: { label: "Rejected", color: "bg-red-100 text-red-800 border-red-200", icon: X },
}
export default function ApplicationsPage() {
const [applications, setApplications] = useState(mockApplications)
const [statusFilter, setStatusFilter] = useState("all")
const [searchTerm, setSearchTerm] = useState("")
const [selectedApp, setSelectedApp] = useState<typeof mockApplications[0] | null>(null)
const filteredApplications = applications.filter((app) => {
const matchesStatus = statusFilter === "all" || app.status === statusFilter
const matchesSearch =
app.candidateName.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.jobTitle.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.email.toLowerCase().includes(searchTerm.toLowerCase())
return matchesStatus && matchesSearch
})
const stats = {
total: applications.length,
pending: applications.filter((a) => a.status === "pending").length,
shortlisted: applications.filter((a) => a.status === "shortlisted").length,
hired: applications.filter((a) => a.status === "hired").length,
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">
Applications
</h1>
<p className="text-muted-foreground">
Manage applications for your job postings
</p>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{stats.total}</div>
<p className="text-xs text-muted-foreground">Total Applications</p>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold text-yellow-600">{stats.pending}</div>
<p className="text-xs text-muted-foreground">Pending Review</p>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold text-purple-600">{stats.shortlisted}</div>
<p className="text-xs text-muted-foreground">Shortlisted</p>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold text-green-600">{stats.hired}</div>
<p className="text-xs text-muted-foreground">Hired</p>
</CardContent>
</Card>
</div>
{/* Filters */}
<div className="flex flex-col sm:flex-row 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" />
<Input
placeholder="Search by name, job, or email..."
className="pl-10"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<Filter className="h-4 w-4 mr-2" />
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="reviewed">Reviewed</SelectItem>
<SelectItem value="shortlisted">Shortlisted</SelectItem>
<SelectItem value="hired">Hired</SelectItem>
<SelectItem value="rejected">Rejected</SelectItem>
</SelectContent>
</Select>
</div>
{/* Applications List */}
<div className="grid gap-4">
{filteredApplications.length === 0 ? (
<Card>
<CardContent className="p-8 text-center text-muted-foreground">
No applications found matching your filters.
</CardContent>
</Card>
) : (
filteredApplications.map((app) => {
const StatusIcon = statusConfig[app.status as keyof typeof statusConfig].icon
return (
<Card
key={app.id}
className="hover:border-primary/50 transition-colors cursor-pointer"
onClick={() => setSelectedApp(app)}
>
<CardContent className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row gap-4">
{/* Avatar and Info */}
<div className="flex items-start gap-4 flex-1">
<Avatar className="h-12 w-12">
<AvatarFallback className="bg-primary/10 text-primary">
{app.candidateName
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h3 className="font-semibold">{app.candidateName}</h3>
<Badge
variant="outline"
className={statusConfig[app.status as keyof typeof statusConfig].color}
>
<StatusIcon className="h-3 w-3 mr-1" />
{statusConfig[app.status as keyof typeof statusConfig].label}
</Badge>
</div>
<p className="text-sm text-muted-foreground mt-1">
Applied for: <span className="font-medium text-foreground">{app.jobTitle}</span>
</p>
<div className="flex flex-wrap gap-4 mt-2 text-sm text-muted-foreground">
<span className="flex items-center gap-1">
<Mail className="h-4 w-4" />
{app.email}
</span>
<span className="flex items-center gap-1">
<Phone className="h-4 w-4" />
{app.phone}
</span>
<span className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
{app.appliedAt}
</span>
</div>
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-2 sm:flex-col">
<Button variant="outline" size="sm" className="flex-1 sm:flex-none">
<FileText className="h-4 w-4 mr-2" />
Resume
</Button>
<Button variant="outline" size="sm" className="flex-1 sm:flex-none">
<MessageSquare className="h-4 w-4 mr-2" />
Message
</Button>
</div>
</div>
</CardContent>
</Card>
)
})
)}
</div>
{/* Selected Application Modal/Drawer would go here */}
{selectedApp && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" onClick={() => setSelectedApp(null)}>
<Card className="w-full max-w-lg" onClick={(e) => e.stopPropagation()}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Application Details</CardTitle>
<Button variant="ghost" size="icon" onClick={() => setSelectedApp(null)}>
<X className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Avatar className="h-16 w-16">
<AvatarFallback className="bg-primary/10 text-primary text-lg">
{selectedApp.candidateName
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</AvatarFallback>
</Avatar>
<div>
<h3 className="font-semibold text-lg">{selectedApp.candidateName}</h3>
<p className="text-sm text-muted-foreground">{selectedApp.jobTitle}</p>
<Badge
variant="outline"
className={statusConfig[selectedApp.status as keyof typeof statusConfig].color + " mt-2"}
>
{statusConfig[selectedApp.status as keyof typeof statusConfig].label}
</Badge>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<Mail className="h-4 w-4 text-muted-foreground" />
<a href={`mailto:${selectedApp.email}`} className="text-primary hover:underline">
{selectedApp.email}
</a>
</div>
<div className="flex items-center gap-2 text-sm">
<Phone className="h-4 w-4 text-muted-foreground" />
<a href={`tel:${selectedApp.phone}`} className="text-primary hover:underline">
{selectedApp.phone}
</a>
</div>
</div>
<div>
<h4 className="font-medium mb-2">Cover Message</h4>
<p className="text-sm text-muted-foreground bg-muted p-3 rounded-lg">
{selectedApp.message}
</p>
</div>
<div className="flex gap-2">
<Button className="flex-1" variant="outline">
<FileText className="h-4 w-4 mr-2" />
View Resume
</Button>
<Button className="flex-1">
<MessageSquare className="h-4 w-4 mr-2" />
Contact
</Button>
</div>
</CardContent>
</Card>
</div>
)}
</div>
)
}