MEP frontend avec filtres
This commit is contained in:
parent
67a146c5dd
commit
0585ff56fd
19 changed files with 4233 additions and 0 deletions
148
frontend/src/components/JobSearch.tsx
Normal file
148
frontend/src/components/JobSearch.tsx
Normal file
|
@ -0,0 +1,148 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import type { JobOffer, JobSearchResponse } from '../types'; // Importe les types définis
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3000/api/jobs'; // L'URL de votre API backend
|
||||
|
||||
const JobSearch: React.FC = () => {
|
||||
const [jobs, setJobs] = useState<JobOffer[]>([]);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [limit, setLimit] = useState<number>(10);
|
||||
const [keyword, setKeyword] = useState<string>('');
|
||||
const [location, setLocation] = useState<string>('');
|
||||
const [contractType, setContractType] = useState<string>(''); // Nouvel état pour le type de contrat
|
||||
const [sortBy, setSortBy] = useState<string>('publicationDate'); // Nouvel état pour le tri par colonne
|
||||
const [sortOrder, setSortOrder] = useState<string>('desc'); // Nouvel état pour l'ordre de tri (asc/desc)
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchJobs = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const params = {
|
||||
page,
|
||||
limit,
|
||||
keyword: keyword || undefined,
|
||||
location: location || undefined,
|
||||
contractType: contractType || undefined, // Ajout du paramètre contractType
|
||||
sortBy: sortBy, // Ajout du paramètre sortBy
|
||||
sortOrder: sortOrder, // Ajout du paramètre sortOrder
|
||||
};
|
||||
const response = await axios.get<JobSearchResponse>(API_BASE_URL, { params });
|
||||
setJobs(response.data.jobs);
|
||||
setTotal(response.data.total);
|
||||
setPage(response.data.page);
|
||||
setLimit(response.data.limit);
|
||||
} catch (err) {
|
||||
console.error('Error fetching jobs:', err);
|
||||
setError('Failed to fetch job offers. Please try again later.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchJobs();
|
||||
}, [page, keyword, location, contractType, sortBy, sortOrder, limit]); // Mettre à jour les dépendances
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setPage(1); // Réinitialise à la première page lors d'une nouvelle recherche
|
||||
// La fonction fetchJobs sera appelée par useEffect car les états keyword ou location auront changé.
|
||||
};
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return (
|
||||
<div className="job-search-container">
|
||||
<h1>Job Offers</h1>
|
||||
|
||||
<form onSubmit={handleSearch}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Keyword (e.g., développeur)"
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Location (e.g., Paris)"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
/>
|
||||
{/* Nouveau select pour le type de contrat */}
|
||||
<select value={contractType} onChange={(e) => setContractType(e.target.value)}>
|
||||
<option value="">All Contract Types</option>
|
||||
<option value="CDI">CDI</option>
|
||||
<option value="CDD">CDD</option>
|
||||
<option value="MIS">Intérim / Mission</option>
|
||||
<option value="SAI">Saisonnier</option>
|
||||
<option value="APP">Apprentissage</option>
|
||||
<option value="PRO">Professionnalisation</option>
|
||||
</select>
|
||||
|
||||
{/* Nouveaux selects pour le tri */}
|
||||
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
|
||||
<option value="publicationDate">Sort by Date</option>
|
||||
<option value="title">Sort by Title</option>
|
||||
</select>
|
||||
<select value={sortOrder} onChange={(e) => setSortOrder(e.target.value)}>
|
||||
<option value="desc">Descending</option>
|
||||
<option value="asc">Ascending</option>
|
||||
</select>
|
||||
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
|
||||
{/* ... (le reste de votre JSX pour l'affichage des résultats et la pagination reste inchangé) ... */}
|
||||
|
||||
{loading && <p className="info-message">Loading job offers...</p>}
|
||||
{error && <p className="info-message" style={{ color: 'red' }}>Error: {error}</p>}
|
||||
|
||||
{!loading && !error && jobs.length === 0 && (
|
||||
<p className="info-message">No job offers found for your search criteria.</p>
|
||||
)}
|
||||
|
||||
{!loading && !error && jobs.length > 0 && (
|
||||
<div>
|
||||
<p className="info-message">Total offers: {total}</p>
|
||||
<div className="job-grid">
|
||||
{jobs.map((job) => (
|
||||
<div key={job.id} className="job-card">
|
||||
<h3>{job.title}</h3>
|
||||
<p><strong>Company:</strong> {job.companyName || 'N/A'}</p>
|
||||
<p><strong>Location:</strong> {job.locationLabel || job.cityName || 'N/A'}</p>
|
||||
<p><strong>Contract:</strong> {job.contractLabel || job.contractType || 'N/A'}</p>
|
||||
<p><strong>Published:</strong> {new Date(job.publicationDate).toLocaleDateString()}</p>
|
||||
<p className="description">
|
||||
{job.description?.substring(0, 150)}...
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="pagination-controls">
|
||||
<button
|
||||
onClick={() => setPage(prev => Math.max(1, prev - 1))}
|
||||
disabled={page === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span>Page {page} of {totalPages}</span>
|
||||
<button
|
||||
onClick={() => setPage(prev => Math.min(totalPages, prev + 1))}
|
||||
disabled={page === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobSearch;
|
Loading…
Add table
Add a link
Reference in a new issue