feat(i18n): internationalize sidebar and dashboards
This commit is contained in:
parent
3e81ae40e7
commit
33e7bbb334
9 changed files with 508 additions and 268 deletions
|
|
@ -1,110 +0,0 @@
|
|||
export const dashboardTranslations = {
|
||||
pt: {
|
||||
title: "Dashboard",
|
||||
subtitle: "Visão geral do portal de vagas",
|
||||
stats: {
|
||||
activeJobs: "Vagas Ativas",
|
||||
activeJobsDesc: "Total de vagas publicadas",
|
||||
candidates: "Candidatos",
|
||||
candidatesDesc: "Usuários registrados",
|
||||
applications: "Candidaturas",
|
||||
applicationsDesc: "Em andamento",
|
||||
hiringRate: "Taxa de Contratação",
|
||||
hiringRateDesc: "Candidaturas por vaga"
|
||||
},
|
||||
jobs: {
|
||||
title: "Gerenciamento de Vagas",
|
||||
add: "Nova Vaga",
|
||||
table: {
|
||||
title: "Título",
|
||||
company: "Empresa",
|
||||
status: "Status",
|
||||
created: "Criado em",
|
||||
actions: "Ações"
|
||||
},
|
||||
empty: "Nenhuma vaga encontrada."
|
||||
},
|
||||
candidates: {
|
||||
title: "Gerenciamento de Candidatos",
|
||||
table: {
|
||||
name: "Nome",
|
||||
email: "Email",
|
||||
location: "Localização",
|
||||
actions: "Ações"
|
||||
},
|
||||
empty: "Nenhum candidato encontrado."
|
||||
}
|
||||
},
|
||||
en: {
|
||||
title: "Dashboard",
|
||||
subtitle: "Overview of the jobs portal",
|
||||
stats: {
|
||||
activeJobs: "Active Jobs",
|
||||
activeJobsDesc: "Total posted jobs",
|
||||
candidates: "Total Candidates",
|
||||
candidatesDesc: "Registered users",
|
||||
applications: "Active Applications",
|
||||
applicationsDesc: "Current pipeline",
|
||||
hiringRate: "Hiring Rate",
|
||||
hiringRateDesc: "Applications per job"
|
||||
},
|
||||
jobs: {
|
||||
title: "Job Management",
|
||||
add: "Add Job",
|
||||
table: {
|
||||
title: "Title",
|
||||
company: "Company",
|
||||
status: "Status",
|
||||
created: "Created At",
|
||||
actions: "Actions"
|
||||
},
|
||||
empty: "No jobs found."
|
||||
},
|
||||
candidates: {
|
||||
title: "Candidate Management",
|
||||
table: {
|
||||
name: "Name",
|
||||
email: "Email",
|
||||
location: "Location",
|
||||
actions: "Actions"
|
||||
},
|
||||
empty: "No candidates found."
|
||||
}
|
||||
},
|
||||
es: {
|
||||
title: "Panel de Control",
|
||||
subtitle: "Visión general del portal de empleos",
|
||||
stats: {
|
||||
activeJobs: "Empleos Activos",
|
||||
activeJobsDesc: "Total publicados",
|
||||
candidates: "Candidatos Total",
|
||||
candidatesDesc: "Usuarios registrados",
|
||||
applications: "Aplicaciones Activas",
|
||||
applicationsDesc: "En proceso",
|
||||
hiringRate: "Tasa de Contratación",
|
||||
hiringRateDesc: "Aplicaciones por empleo"
|
||||
},
|
||||
jobs: {
|
||||
title: "Gestión de Empleos",
|
||||
add: "Nuevo Empleo",
|
||||
table: {
|
||||
title: "Título",
|
||||
company: "Empresa",
|
||||
status: "Estado",
|
||||
created: "Creado en",
|
||||
actions: "Acciones"
|
||||
},
|
||||
empty: "No se encontraron empleos."
|
||||
},
|
||||
candidates: {
|
||||
title: "Gestión de Candidatos",
|
||||
table: {
|
||||
name: "Nombre",
|
||||
email: "Correo",
|
||||
location: "Ubicación",
|
||||
actions: "Acciones"
|
||||
},
|
||||
empty: "No se encontraron candidatos."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -249,7 +249,7 @@ export default function AdminUsersPage() {
|
|||
{/* Header */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">{t('admin.users.title')} (v2)</h1>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">{t('admin.users.title')}</h1>
|
||||
<p className="text-sm sm:text-base text-muted-foreground mt-1">{t('admin.users.subtitle')}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
|
|
|||
|
|
@ -23,14 +23,12 @@ import { Briefcase, Users, TrendingUp, FileText, Plus, MoreHorizontal, Loader2 }
|
|||
import { motion } from "framer-motion"
|
||||
import { adminJobsApi, adminCandidatesApi, type AdminJob, type AdminCandidate, type AdminCandidateStats } from "@/lib/api"
|
||||
import { toast } from "sonner"
|
||||
import { useLanguageStore } from "@/lib/store/language-store"
|
||||
import { dashboardTranslations } from "@/app/dashboard/translations"
|
||||
import { useTranslation } from "@/lib/i18n"
|
||||
|
||||
export function AdminDashboardContent() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const { language } = useLanguageStore()
|
||||
const t = dashboardTranslations[language]
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [stats, setStats] = useState({
|
||||
activeJobs: 0,
|
||||
|
|
@ -104,8 +102,8 @@ export function AdminDashboardContent() {
|
|||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<h1 className="text-3xl font-bold mb-2">{t.title}</h1>
|
||||
<p className="text-muted-foreground">{t.subtitle}</p>
|
||||
<h1 className="text-3xl font-bold mb-2">{t('admin.dashboard.title')}</h1>
|
||||
<p className="text-muted-foreground">{t('admin.dashboard.subtitle')}</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Stats */}
|
||||
|
|
@ -116,28 +114,28 @@ export function AdminDashboardContent() {
|
|||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
|
||||
>
|
||||
<StatsCard
|
||||
title={t.stats.activeJobs}
|
||||
title={t('admin.dashboard.stats.activeJobs')}
|
||||
value={stats.activeJobs}
|
||||
icon={Briefcase}
|
||||
description={t.stats.activeJobsDesc}
|
||||
description={t('admin.dashboard.stats.activeJobsDesc')}
|
||||
/>
|
||||
<StatsCard
|
||||
title={t.stats.candidates}
|
||||
title={t('admin.dashboard.stats.candidates')}
|
||||
value={stats.totalCandidates}
|
||||
icon={Users}
|
||||
description={t.stats.candidatesDesc}
|
||||
description={t('admin.dashboard.stats.candidatesDesc')}
|
||||
/>
|
||||
<StatsCard
|
||||
title={t.stats.applications}
|
||||
title={t('admin.dashboard.stats.applications')}
|
||||
value={stats.newApplications}
|
||||
icon={FileText}
|
||||
description={t.stats.applicationsDesc}
|
||||
description={t('admin.dashboard.stats.applicationsDesc')}
|
||||
/>
|
||||
<StatsCard
|
||||
title={t.stats.hiringRate}
|
||||
title={t('admin.dashboard.stats.hiringRate')}
|
||||
value={`${stats.conversionRate.toFixed(1)}%`}
|
||||
icon={TrendingUp}
|
||||
description={t.stats.hiringRateDesc}
|
||||
description={t('admin.dashboard.stats.hiringRateDesc')}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
|
|
@ -149,12 +147,12 @@ export function AdminDashboardContent() {
|
|||
>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>{t.jobs.title}</CardTitle>
|
||||
<CardTitle>{t('admin.dashboard.jobs.title')}</CardTitle>
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t.jobs.add}
|
||||
{t('admin.dashboard.jobs.add')}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-2xl">
|
||||
|
|
@ -199,17 +197,17 @@ export function AdminDashboardContent() {
|
|||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t.jobs.table.title}</TableHead>
|
||||
<TableHead>{t.jobs.table.company}</TableHead>
|
||||
<TableHead>{t.jobs.table.status}</TableHead>
|
||||
<TableHead>{t.jobs.table.created}</TableHead>
|
||||
<TableHead className="text-right">{t.jobs.table.actions}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.jobs.table.title')}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.jobs.table.company')}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.jobs.table.status')}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.jobs.table.created')}</TableHead>
|
||||
<TableHead className="text-right">{t('admin.dashboard.jobs.table.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{recentJobs.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="text-center text-muted-foreground">{t.jobs.empty}</TableCell>
|
||||
<TableCell colSpan={5} className="text-center text-muted-foreground">{t('admin.dashboard.jobs.empty')}</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
recentJobs.map((job) => (
|
||||
|
|
@ -242,22 +240,22 @@ export function AdminDashboardContent() {
|
|||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t.candidates.title}</CardTitle>
|
||||
<CardTitle>{t('admin.dashboard.candidates.title')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t.candidates.table.name}</TableHead>
|
||||
<TableHead>{t.candidates.table.email}</TableHead>
|
||||
<TableHead>{t.candidates.table.location}</TableHead>
|
||||
<TableHead className="text-right">{t.candidates.table.actions}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.candidates.table.name')}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.candidates.table.email')}</TableHead>
|
||||
<TableHead>{t('admin.dashboard.candidates.table.location')}</TableHead>
|
||||
<TableHead className="text-right">{t('admin.dashboard.candidates.table.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{recentCandidates.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center text-muted-foreground">No candidates found.</TableCell>
|
||||
<TableCell colSpan={4} className="text-center text-muted-foreground">{t('admin.dashboard.candidates.empty')}</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
recentCandidates.map((candidate) => (
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ import {
|
|||
} from "lucide-react"
|
||||
import { motion } from "framer-motion"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { useTranslation } from "@/lib/i18n"
|
||||
|
||||
export function CandidateDashboardContent() {
|
||||
const { t } = useTranslation()
|
||||
const user = getCurrentUser()
|
||||
const recommendedJobs = mockJobs.slice(0, 3)
|
||||
const unreadNotifications = mockNotifications.filter((n) => !n.read)
|
||||
|
|
@ -37,35 +39,35 @@ export function CandidateDashboardContent() {
|
|||
return (
|
||||
<Badge variant="secondary">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Under review
|
||||
{t('candidate.dashboard.status.under_review')}
|
||||
</Badge>
|
||||
)
|
||||
case "reviewing":
|
||||
return (
|
||||
<Badge variant="secondary">
|
||||
<AlertCircle className="h-3 w-3 mr-1" />
|
||||
Under review
|
||||
{t('candidate.dashboard.status.under_review')}
|
||||
</Badge>
|
||||
)
|
||||
case "interview":
|
||||
return (
|
||||
<Badge className="bg-blue-500 hover:bg-blue-600">
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Interview
|
||||
{t('candidate.dashboard.status.interview')}
|
||||
</Badge>
|
||||
)
|
||||
case "accepted":
|
||||
return (
|
||||
<Badge className="bg-green-500 hover:bg-green-600">
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Accepted
|
||||
{t('candidate.dashboard.status.accepted')}
|
||||
</Badge>
|
||||
)
|
||||
case "rejected":
|
||||
return (
|
||||
<Badge variant="destructive">
|
||||
<XCircle className="h-3 w-3 mr-1" />
|
||||
Rejected
|
||||
{t('candidate.dashboard.status.rejected')}
|
||||
</Badge>
|
||||
)
|
||||
default:
|
||||
|
|
@ -85,12 +87,12 @@ export function CandidateDashboardContent() {
|
|||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div className="flex-1">
|
||||
<h1 className="text-2xl font-bold mb-2">Hi, {user?.name || "Candidate"}!</h1>
|
||||
<h1 className="text-2xl font-bold mb-2">{t('candidate.dashboard.welcome', { name: user?.name || "Candidate" })}</h1>
|
||||
<p className="text-muted-foreground">{user?.area || "Engineering"}</p>
|
||||
</div>
|
||||
<Button className="cursor-pointer">
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit profile
|
||||
{t('candidate.dashboard.edit_profile')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
@ -105,26 +107,26 @@ export function CandidateDashboardContent() {
|
|||
className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"
|
||||
>
|
||||
<StatsCard
|
||||
title="Applications"
|
||||
title={t('candidate.dashboard.stats.applications')}
|
||||
value={mockApplications.length}
|
||||
icon={FileText}
|
||||
description="Total jobs applied to"
|
||||
description={t('candidate.dashboard.stats.applications_desc')}
|
||||
/>
|
||||
<StatsCard
|
||||
title="In progress"
|
||||
title={t('candidate.dashboard.stats.in_progress')}
|
||||
value={
|
||||
mockApplications.filter(
|
||||
(a) => a.status === "reviewing" || a.status === "interview"
|
||||
).length
|
||||
}
|
||||
icon={Clock}
|
||||
description="Awaiting a response"
|
||||
description={t('candidate.dashboard.stats.in_progress_desc')}
|
||||
/>
|
||||
<StatsCard
|
||||
title="Notifications"
|
||||
title={t('candidate.dashboard.stats.notifications')}
|
||||
value={unreadNotifications.length}
|
||||
icon={Bell}
|
||||
description="New updates"
|
||||
description={t('candidate.dashboard.stats.notifications_desc')}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
|
|
@ -139,7 +141,7 @@ export function CandidateDashboardContent() {
|
|||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recommended jobs</CardTitle>
|
||||
<CardTitle>{t('candidate.dashboard.recommended.title')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{recommendedJobs.map((job) => (
|
||||
|
|
@ -157,16 +159,16 @@ export function CandidateDashboardContent() {
|
|||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>My applications</CardTitle>
|
||||
<CardTitle>{t('candidate.dashboard.applications.title')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Role</TableHead>
|
||||
<TableHead>Company</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>{t('candidate.dashboard.applications.table.role')}</TableHead>
|
||||
<TableHead>{t('candidate.dashboard.applications.table.company')}</TableHead>
|
||||
<TableHead>{t('candidate.dashboard.applications.table.status')}</TableHead>
|
||||
<TableHead>{t('candidate.dashboard.applications.table.date')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
|
|
|||
|
|
@ -25,12 +25,14 @@ import { jobsApi, applicationsApi, ApiJob } from "@/lib/api"
|
|||
import { User } from "@/lib/auth"
|
||||
import { formatDistanceToNow } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { useTranslation } from "@/lib/i18n"
|
||||
|
||||
interface CompanyDashboardContentProps {
|
||||
user: User
|
||||
}
|
||||
|
||||
export function CompanyDashboardContent({ user }: CompanyDashboardContentProps) {
|
||||
const { t } = useTranslation()
|
||||
const [stats, setStats] = useState({
|
||||
activeJobs: 0,
|
||||
totalApplications: 0,
|
||||
|
|
@ -102,16 +104,16 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<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 mb-2">
|
||||
Dashboard
|
||||
{t('company.dashboard.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Olá, {user.name}! 👋
|
||||
{t('company.dashboard.welcome', { name: user.name })} 👋
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/dashboard/my-jobs">
|
||||
<Button size="lg" className="w-full sm:w-auto">
|
||||
<Plus className="h-5 w-5 mr-2" />
|
||||
Nova Vaga
|
||||
{t('company.dashboard.new_job')}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -121,7 +123,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Vagas Ativas
|
||||
{t('company.dashboard.stats.active_jobs')}
|
||||
</CardTitle>
|
||||
<Briefcase className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
|
|
@ -130,7 +132,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
{stats.activeJobs}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Publicadas
|
||||
{t('company.dashboard.stats.posted')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -138,7 +140,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Candidaturas
|
||||
{t('company.dashboard.stats.applications')}
|
||||
</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
|
|
@ -147,7 +149,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
{stats.totalApplications}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
+{stats.thisMonth} este mês
|
||||
{t('company.dashboard.stats.this_month', { count: stats.thisMonth })}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -155,7 +157,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Visualizações
|
||||
{t('company.dashboard.stats.views')}
|
||||
</CardTitle>
|
||||
<Eye className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
|
|
@ -164,7 +166,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
-
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Em breve
|
||||
{t('company.dashboard.stats.soon')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -172,14 +174,14 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Conversão
|
||||
{t('company.dashboard.stats.conversion')}
|
||||
</CardTitle>
|
||||
<TrendingUp className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">-</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Em breve
|
||||
{t('company.dashboard.stats.soon')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -192,21 +194,21 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Vagas Recentes</CardTitle>
|
||||
<CardTitle>{t('company.dashboard.recent_jobs.title')}</CardTitle>
|
||||
<CardDescription>
|
||||
Suas últimas vagas publicadas
|
||||
{t('company.dashboard.recent_jobs.subtitle')}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Link href="/dashboard/my-jobs">
|
||||
<Button variant="ghost" size="sm">
|
||||
Ver todas
|
||||
{t('company.dashboard.recent_jobs.view_all')}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{recentJobs.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm">Nenhuma vaga encontrada.</p>
|
||||
<p className="text-muted-foreground text-sm">{t('company.dashboard.recent_jobs.empty')}</p>
|
||||
) : recentJobs.map((job) => (
|
||||
<div
|
||||
key={job.id}
|
||||
|
|
@ -239,7 +241,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<span className="flex items-center gap-1">
|
||||
<Users className="h-4 w-4" />
|
||||
{/* Mocking app count if not available */}
|
||||
{job.applicationCount || 0} applications
|
||||
{t('company.dashboard.recent_jobs.applications_count', { count: job.applicationCount || 0 })}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-4 w-4" />
|
||||
|
|
@ -267,19 +269,19 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Candidaturas</CardTitle>
|
||||
<CardDescription>Candidatos recentes</CardDescription>
|
||||
<CardTitle>{t('company.dashboard.recent_applications.title')}</CardTitle>
|
||||
<CardDescription>{t('company.dashboard.recent_applications.subtitle')}</CardDescription>
|
||||
</div>
|
||||
<Link href="/dashboard/candidates">
|
||||
<Button variant="ghost" size="sm">
|
||||
Ver todas
|
||||
{t('company.dashboard.recent_applications.view_all')}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{recentApplications.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm">Nenhuma candidatura recente.</p>
|
||||
<p className="text-muted-foreground text-sm">{t('company.dashboard.recent_applications.empty')}</p>
|
||||
) : recentApplications.map((application) => (
|
||||
<div
|
||||
key={application.id}
|
||||
|
|
@ -308,7 +310,7 @@ export function CompanyDashboardContent({ user }: CompanyDashboardContentProps)
|
|||
{application.name}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{application.jobTitle || "Vaga desconhecida"}
|
||||
{application.jobTitle || t('company.dashboard.recent_applications.unknown_job')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{formatDistanceToNow(new Date(application.created_at), { addSuffix: true, locale: ptBR })}
|
||||
|
|
|
|||
|
|
@ -6,99 +6,110 @@ import { usePathname } from "next/navigation"
|
|||
import { cn } from "@/lib/utils"
|
||||
import { LayoutDashboard, Briefcase, Users, MessageSquare, Building2, FileText, HelpCircle, Ticket } from "lucide-react"
|
||||
import { getCurrentUser, isAdminUser } from "@/lib/auth"
|
||||
import { useTranslation } from "@/lib/i18n"
|
||||
|
||||
const adminItems = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
href: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: "Jobs",
|
||||
href: "/dashboard/jobs",
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: "Candidates",
|
||||
href: "/dashboard/candidates",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
href: "/dashboard/users",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: "Companies",
|
||||
href: "/dashboard/companies",
|
||||
icon: Building2,
|
||||
},
|
||||
{
|
||||
title: "Backoffice",
|
||||
href: "/dashboard/backoffice",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: "Messages",
|
||||
href: "/dashboard/messages",
|
||||
icon: MessageSquare,
|
||||
},
|
||||
{
|
||||
title: "Tickets",
|
||||
href: "/dashboard/tickets",
|
||||
icon: Ticket,
|
||||
},
|
||||
]
|
||||
|
||||
const companyItems = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
href: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: "My jobs",
|
||||
href: "/dashboard/my-jobs",
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: "Applications",
|
||||
href: "/dashboard/applications",
|
||||
icon: Users,
|
||||
},
|
||||
]
|
||||
|
||||
const candidateItems = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
href: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: "Jobs",
|
||||
href: "/jobs", // Public search
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: "My applications",
|
||||
href: "/dashboard/my-applications",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: "Support",
|
||||
href: "/dashboard/support/tickets",
|
||||
icon: HelpCircle,
|
||||
},
|
||||
]
|
||||
|
||||
export function Sidebar() {
|
||||
const Sidebar = () => {
|
||||
const { t } = useTranslation()
|
||||
const pathname = usePathname()
|
||||
const user = getCurrentUser()
|
||||
const isSuperadmin = user?.role === "superadmin"
|
||||
|
||||
const adminItems = [
|
||||
{
|
||||
title: t('sidebar.dashboard'),
|
||||
href: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.jobs'),
|
||||
href: "/dashboard/jobs",
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.candidates'),
|
||||
href: "/dashboard/candidates",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.users'),
|
||||
href: "/dashboard/users",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.companies'),
|
||||
href: "/dashboard/companies",
|
||||
icon: Building2,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.backoffice'),
|
||||
href: "/dashboard/backoffice",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.messages'),
|
||||
href: "/dashboard/messages",
|
||||
icon: MessageSquare,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.tickets'),
|
||||
href: "/dashboard/tickets",
|
||||
icon: Ticket,
|
||||
},
|
||||
]
|
||||
|
||||
const companyItems = [
|
||||
{
|
||||
title: t('sidebar.dashboard'),
|
||||
href: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.my_jobs'),
|
||||
href: "/dashboard/my-jobs",
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.applications'),
|
||||
href: "/dashboard/applications",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.messages'),
|
||||
href: "/dashboard/messages",
|
||||
icon: MessageSquare,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.support'),
|
||||
href: "/dashboard/support",
|
||||
icon: HelpCircle,
|
||||
},
|
||||
]
|
||||
|
||||
const candidateItems = [
|
||||
{
|
||||
title: t('sidebar.dashboard'),
|
||||
href: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.jobs'),
|
||||
href: "/jobs",
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.my_applications'),
|
||||
href: "/dashboard/my-applications",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.support'),
|
||||
href: "/dashboard/support/tickets",
|
||||
icon: HelpCircle,
|
||||
},
|
||||
]
|
||||
|
||||
let items = candidateItems
|
||||
if (isAdminUser(user)) {
|
||||
// For Admin (not Superadmin), filter out Backoffice
|
||||
items = isSuperadmin
|
||||
? adminItems
|
||||
: adminItems.filter(item => item.href !== "/dashboard/backoffice" && item.href !== "/dashboard/companies")
|
||||
|
|
@ -108,7 +119,6 @@ export function Sidebar() {
|
|||
|
||||
return (
|
||||
<aside className="w-64 shrink-0 border-r border-border bg-muted/30 min-h-screen flex flex-col">
|
||||
{/* Branding Header with CORRECT Padding (pl-6) */}
|
||||
<div className="flex h-16 shrink-0 items-center px-6 gap-3 border-b border-border">
|
||||
<Link href="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
||||
<Image
|
||||
|
|
@ -122,7 +132,6 @@ export function Sidebar() {
|
|||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 p-4 space-y-2 overflow-y-auto">
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon
|
||||
|
|
@ -146,7 +155,6 @@ export function Sidebar() {
|
|||
})}
|
||||
</nav>
|
||||
|
||||
{/* Footer / User Info could go here */}
|
||||
<div className="p-4 border-t border-border mt-auto">
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
v1.0.0
|
||||
|
|
@ -155,3 +163,6 @@ export function Sidebar() {
|
|||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
export { Sidebar }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,18 @@
|
|||
{
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"jobs": "Jobs",
|
||||
"candidates": "Candidates",
|
||||
"users": "Users",
|
||||
"companies": "Companies",
|
||||
"backoffice": "Backoffice",
|
||||
"messages": "Messages",
|
||||
"tickets": "Tickets",
|
||||
"my_jobs": "My Jobs",
|
||||
"applications": "Applications",
|
||||
"my_applications": "My Applications",
|
||||
"support": "Support"
|
||||
},
|
||||
"nav": {
|
||||
"jobs": "Jobs",
|
||||
"about": "About",
|
||||
|
|
@ -662,6 +676,42 @@
|
|||
}
|
||||
},
|
||||
"admin": {
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"subtitle": "Overview of the jobs portal",
|
||||
"stats": {
|
||||
"activeJobs": "Active Jobs",
|
||||
"activeJobsDesc": "Total posted jobs",
|
||||
"candidates": "Total Candidates",
|
||||
"candidatesDesc": "Registered users",
|
||||
"applications": "Active Applications",
|
||||
"applicationsDesc": "Current pipeline",
|
||||
"hiringRate": "Hiring Rate",
|
||||
"hiringRateDesc": "Applications per job"
|
||||
},
|
||||
"jobs": {
|
||||
"title": "Job Management",
|
||||
"add": "Add Job",
|
||||
"table": {
|
||||
"title": "Title",
|
||||
"company": "Company",
|
||||
"status": "Status",
|
||||
"created": "Created At",
|
||||
"actions": "Actions"
|
||||
},
|
||||
"empty": "No jobs found."
|
||||
},
|
||||
"candidates": {
|
||||
"title": "Candidate Management",
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"location": "Location",
|
||||
"actions": "Actions"
|
||||
},
|
||||
"empty": "No candidates found."
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "User management",
|
||||
"subtitle": "Manage all platform users",
|
||||
|
|
@ -718,5 +768,67 @@
|
|||
"load_error": "Failed to load users"
|
||||
}
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"welcome": "Hello, {name}!",
|
||||
"new_job": "New Job",
|
||||
"stats": {
|
||||
"active_jobs": "Active Jobs",
|
||||
"posted": "Posted",
|
||||
"applications": "Applications",
|
||||
"this_month": "+{count} this month",
|
||||
"views": "Views",
|
||||
"conversion": "Conversion",
|
||||
"soon": "Coming soon"
|
||||
},
|
||||
"recent_jobs": {
|
||||
"title": "Recent Jobs",
|
||||
"subtitle": "Your latest posted jobs",
|
||||
"view_all": "View all",
|
||||
"empty": "No jobs found.",
|
||||
"applications_count": "{count} applications"
|
||||
},
|
||||
"recent_applications": {
|
||||
"title": "Applications",
|
||||
"subtitle": "Recent candidates",
|
||||
"view_all": "View all",
|
||||
"empty": "No recent applications.",
|
||||
"unknown_job": "Unknown job"
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidate": {
|
||||
"dashboard": {
|
||||
"welcome": "Hi, {name}!",
|
||||
"edit_profile": "Edit profile",
|
||||
"stats": {
|
||||
"applications": "Applications",
|
||||
"applications_desc": "Total jobs applied to",
|
||||
"in_progress": "In progress",
|
||||
"in_progress_desc": "Awaiting a response",
|
||||
"notifications": "Notifications",
|
||||
"notifications_desc": "New updates"
|
||||
},
|
||||
"recommended": {
|
||||
"title": "Recommended jobs"
|
||||
},
|
||||
"applications": {
|
||||
"title": "My applications",
|
||||
"table": {
|
||||
"role": "Role",
|
||||
"company": "Company",
|
||||
"status": "Status",
|
||||
"date": "Date"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"under_review": "Under review",
|
||||
"interview": "Interview",
|
||||
"accepted": "Accepted",
|
||||
"rejected": "Rejected"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,18 @@
|
|||
{
|
||||
"sidebar": {
|
||||
"dashboard": "Panel de Control",
|
||||
"jobs": "Empleos",
|
||||
"candidates": "Candidatos",
|
||||
"users": "Usuarios",
|
||||
"companies": "Empresas",
|
||||
"backoffice": "Backoffice",
|
||||
"messages": "Mensajes",
|
||||
"tickets": "Tickets",
|
||||
"my_jobs": "Mis Empleos",
|
||||
"applications": "Postulaciones",
|
||||
"my_applications": "Mis Postulaciones",
|
||||
"support": "Soporte"
|
||||
},
|
||||
"nav": {
|
||||
"jobs": "Empleos",
|
||||
"about": "Sobre",
|
||||
|
|
@ -662,6 +676,43 @@
|
|||
}
|
||||
},
|
||||
"admin": {
|
||||
"dashboard": {
|
||||
"title": "Panel de Control",
|
||||
"subtitle": "Visión general del portal de empleos",
|
||||
"stats": {
|
||||
"activeJobs": "Empleos Activos",
|
||||
"activeJobsDesc": "Total publicados",
|
||||
"candidates": "Candidatos Total",
|
||||
"candidatesDesc": "Usuarios registrados",
|
||||
"applications": "Aplicaciones Activas",
|
||||
"applicationsDesc": "En proceso",
|
||||
"hiringRate": "Tasa de Contratación",
|
||||
"hiringRateDesc": "Aplicaciones por empleo"
|
||||
},
|
||||
"jobs": {
|
||||
"title": "Gestión de Empleos",
|
||||
"add": "Nuevo Empleo",
|
||||
"table": {
|
||||
"title": "Título",
|
||||
"company": "Empresa",
|
||||
"status": "Estado",
|
||||
"created": "Creado en",
|
||||
"actions": "Acciones"
|
||||
},
|
||||
"empty": "No se encontraron empleos."
|
||||
},
|
||||
"candidates": {
|
||||
"title": "Gestión de Candidatos",
|
||||
"table": {
|
||||
"title": "Nombre",
|
||||
"name": "Nombre",
|
||||
"email": "Correo",
|
||||
"location": "Ubicación",
|
||||
"actions": "Acciones"
|
||||
},
|
||||
"empty": "No se encontraron candidatos."
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "Gestión de Usuarios",
|
||||
"subtitle": "Gestione todos los usuarios de la plataforma",
|
||||
|
|
@ -718,5 +769,67 @@
|
|||
"load_error": "Error al cargar usuarios"
|
||||
}
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"dashboard": {
|
||||
"title": "Panel de Control",
|
||||
"welcome": "¡Hola, {name}!",
|
||||
"new_job": "Nuevo Empleo",
|
||||
"stats": {
|
||||
"active_jobs": "Empleos Activos",
|
||||
"posted": "Publicados",
|
||||
"applications": "Postulaciones",
|
||||
"this_month": "+{count} este mes",
|
||||
"views": "Vistas",
|
||||
"conversion": "Conversión",
|
||||
"soon": "Próximamente"
|
||||
},
|
||||
"recent_jobs": {
|
||||
"title": "Empleos Recientes",
|
||||
"subtitle": "Tus últimos empleos publicados",
|
||||
"view_all": "Ver todos",
|
||||
"empty": "No se encontraron empleos.",
|
||||
"applications_count": "{count} postulaciones"
|
||||
},
|
||||
"recent_applications": {
|
||||
"title": "Postulaciones",
|
||||
"subtitle": "Candidatos recientes",
|
||||
"view_all": "Ver todas",
|
||||
"empty": "No hay postulaciones recientes.",
|
||||
"unknown_job": "Empleo desconocido"
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidate": {
|
||||
"dashboard": {
|
||||
"welcome": "¡Hola, {name}!",
|
||||
"edit_profile": "Editar perfil",
|
||||
"stats": {
|
||||
"applications": "Postulaciones",
|
||||
"applications_desc": "Total de empleos aplicados",
|
||||
"in_progress": "En proceso",
|
||||
"in_progress_desc": "Esperando respuesta",
|
||||
"notifications": "Notificaciones",
|
||||
"notifications_desc": "Nuevas actualizaciones"
|
||||
},
|
||||
"recommended": {
|
||||
"title": "Empleos recomendados"
|
||||
},
|
||||
"applications": {
|
||||
"title": "Mis postulaciones",
|
||||
"table": {
|
||||
"role": "Puesto",
|
||||
"company": "Empresa",
|
||||
"status": "Estado",
|
||||
"date": "Fecha"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"under_review": "En revisión",
|
||||
"interview": "Entrevista",
|
||||
"accepted": "Aceptado",
|
||||
"rejected": "Rechazado"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,18 @@
|
|||
{
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"jobs": "Vagas",
|
||||
"candidates": "Candidatos",
|
||||
"users": "Usuários",
|
||||
"companies": "Empresas",
|
||||
"backoffice": "Backoffice",
|
||||
"messages": "Mensagens",
|
||||
"tickets": "Tickets",
|
||||
"my_jobs": "Minhas Vagas",
|
||||
"applications": "Candidaturas",
|
||||
"my_applications": "Minhas Candidaturas",
|
||||
"support": "Suporte"
|
||||
},
|
||||
"nav": {
|
||||
"jobs": "Vagas",
|
||||
"about": "Sobre",
|
||||
|
|
@ -662,6 +676,42 @@
|
|||
}
|
||||
},
|
||||
"admin": {
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"subtitle": "Visão geral do portal de vagas",
|
||||
"stats": {
|
||||
"activeJobs": "Vagas Ativas",
|
||||
"activeJobsDesc": "Total de vagas publicadas",
|
||||
"candidates": "Candidatos",
|
||||
"candidatesDesc": "Usuários registrados",
|
||||
"applications": "Candidaturas",
|
||||
"applicationsDesc": "Em andamento",
|
||||
"hiringRate": "Taxa de Contratação",
|
||||
"hiringRateDesc": "Candidaturas por vaga"
|
||||
},
|
||||
"jobs": {
|
||||
"title": "Gerenciamento de Vagas",
|
||||
"add": "Nova Vaga",
|
||||
"table": {
|
||||
"title": "Título",
|
||||
"company": "Empresa",
|
||||
"status": "Status",
|
||||
"created": "Criado em",
|
||||
"actions": "Ações"
|
||||
},
|
||||
"empty": "Nenhuma vaga encontrada."
|
||||
},
|
||||
"candidates": {
|
||||
"title": "Gerenciamento de Candidatos",
|
||||
"table": {
|
||||
"name": "Nome",
|
||||
"email": "Email",
|
||||
"location": "Localização",
|
||||
"actions": "Ações"
|
||||
},
|
||||
"empty": "Nenhum candidato encontrado."
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "Gestão de Usuários",
|
||||
"subtitle": "Gerencie todos os usuários da plataforma",
|
||||
|
|
@ -718,5 +768,67 @@
|
|||
"load_error": "Falha ao carregar usuários"
|
||||
}
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"welcome": "Olá, {name}!",
|
||||
"new_job": "Nova Vaga",
|
||||
"stats": {
|
||||
"active_jobs": "Vagas Ativas",
|
||||
"posted": "Publicadas",
|
||||
"applications": "Candidaturas",
|
||||
"this_month": "+{count} este mês",
|
||||
"views": "Visualizações",
|
||||
"conversion": "Conversão",
|
||||
"soon": "Em breve"
|
||||
},
|
||||
"recent_jobs": {
|
||||
"title": "Vagas Recentes",
|
||||
"subtitle": "Suas últimas vagas publicadas",
|
||||
"view_all": "Ver todas",
|
||||
"empty": "Nenhuma vaga encontrada.",
|
||||
"applications_count": "{count} candidaturas"
|
||||
},
|
||||
"recent_applications": {
|
||||
"title": "Candidaturas",
|
||||
"subtitle": "Candidatos recentes",
|
||||
"view_all": "Ver todas",
|
||||
"empty": "Nenhuma candidatura recente.",
|
||||
"unknown_job": "Vaga desconhecida"
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidate": {
|
||||
"dashboard": {
|
||||
"welcome": "Olá, {name}!",
|
||||
"edit_profile": "Editar perfil",
|
||||
"stats": {
|
||||
"applications": "Candidaturas",
|
||||
"applications_desc": "Total de vagas aplicadas",
|
||||
"in_progress": "Em andamento",
|
||||
"in_progress_desc": "Aguardando resposta",
|
||||
"notifications": "Notificações",
|
||||
"notifications_desc": "Novas atualizações"
|
||||
},
|
||||
"recommended": {
|
||||
"title": "Vagas recomendadas"
|
||||
},
|
||||
"applications": {
|
||||
"title": "Minhas candidaturas",
|
||||
"table": {
|
||||
"role": "Vaga",
|
||||
"company": "Empresa",
|
||||
"status": "Status",
|
||||
"date": "Data"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"under_review": "Em análise",
|
||||
"interview": "Entrevista",
|
||||
"accepted": "Aprovado",
|
||||
"rejected": "Reprovado"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue