95 lines
4.3 KiB
JavaScript
95 lines
4.3 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { Search, Plus } from 'lucide-react';
|
|
import { useJobPostings, useApplications } from '@/lib/krowHooks';
|
|
import { base44 } from '@/api/base44Client';
|
|
import KROWJobPostingCard from '@/components/krow/KROWJobPostingCard';
|
|
import CertificationManager from '@/components/krow/CertificationManager';
|
|
|
|
export default function Positions() {
|
|
const navigate = useNavigate();
|
|
const { data: postings = [], isLoading } = useJobPostings();
|
|
const { data: applications = [] } = useApplications();
|
|
const [search, setSearch] = useState('');
|
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
const [isAdmin, setIsAdmin] = useState(false);
|
|
|
|
useEffect(() => {
|
|
base44.auth.me().then(u => setIsAdmin(u?.role === 'admin')).catch(() => {});
|
|
}, []);
|
|
|
|
const filtered = postings.filter(p => {
|
|
const matchesSearch = !search || p.title.toLowerCase().includes(search.toLowerCase()) || p.role_category?.toLowerCase().includes(search.toLowerCase());
|
|
const matchesStatus = statusFilter === 'all' || p.status === statusFilter;
|
|
return matchesSearch && matchesStatus;
|
|
});
|
|
|
|
const statsForPosting = (postingId) => {
|
|
const apps = applications.filter(a => a.job_posting_id === postingId);
|
|
return {
|
|
applied: apps.length,
|
|
screened: apps.filter(a => a.status !== 'applied').length,
|
|
hired: apps.filter(a => a.status === 'hired').length,
|
|
};
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-5">
|
|
<div>
|
|
<h1 className="text-[22px] font-bold text-[#111827]">Positions</h1>
|
|
<p className="text-[13px] text-[#6B7280] mt-0.5">{postings.length} job postings</p>
|
|
</div>
|
|
|
|
{/* Action Bar */}
|
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-[#9CA3AF]" />
|
|
<input
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
placeholder="Search positions..."
|
|
className="w-full h-9 pl-10 pr-4 rounded-full border border-[#E5E7EB] bg-white text-[13px] text-[#111827] shadow-2xs hover:border-[#D1D5DB] focus:border-[#2563EB] focus:ring-2 focus:ring-[#2563EB]/20 focus:outline-none transition-all"
|
|
/>
|
|
</div>
|
|
<select
|
|
value={statusFilter}
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
className="h-9 px-3.5 rounded-full border border-[#E5E7EB] bg-white text-[13px] text-[#1F2937] shadow-2xs hover:border-[#D1D5DB] focus:border-[#2563EB] focus:outline-none transition-all cursor-pointer"
|
|
>
|
|
<option value="all">All</option>
|
|
<option value="active">Hiring</option>
|
|
<option value="draft">Draft</option>
|
|
<option value="paused">Paused</option>
|
|
<option value="closed">Closed</option>
|
|
</select>
|
|
{isAdmin && <CertificationManager />}
|
|
<button
|
|
onClick={() => navigate('/positions/new')}
|
|
className="inline-flex items-center justify-center gap-1.5 bg-[#111827] hover:bg-[#1F2937] text-white text-[13px] font-medium px-4 h-9 rounded-full shadow-xs transition-colors whitespace-nowrap"
|
|
>
|
|
<Plus className="w-4 h-4" /> New Position
|
|
</button>
|
|
</div>
|
|
|
|
{/* Grid */}
|
|
{isLoading ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{[...Array(6)].map((_, i) => <div key={i} className="h-48 bg-white border border-[#E5E7EB] rounded-xl animate-pulse" />)}
|
|
</div>
|
|
) : filtered.length === 0 ? (
|
|
<div className="text-center py-16">
|
|
<p className="text-[14px] text-[#6B7280] mb-3">No positions found</p>
|
|
<button onClick={() => navigate('/positions/new')} className="inline-flex items-center gap-1.5 bg-[#2563EB] text-white text-[13px] font-medium px-4 py-2 rounded-lg">
|
|
<Plus className="w-4 h-4" /> Create your first position
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{filtered.map(posting => (
|
|
<KROWJobPostingCard key={posting.id} posting={posting} stats={statsForPosting(posting.id)} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |