Initial commit
This commit is contained in:
95
src/pages/Positions.jsx
Normal file
95
src/pages/Positions.jsx
Normal file
@@ -0,0 +1,95 @@
|
||||
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 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-10 pl-10 pr-4 rounded-lg border border-[#E5E7EB] bg-white text-[13px] focus:border-[#2563EB] focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
className="h-10 px-4 rounded-lg border border-[#E5E7EB] bg-white text-[13px] focus:border-[#2563EB] focus:outline-none"
|
||||
>
|
||||
<option value="all">All Statuses</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-10 rounded-lg 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user