champs search

This commit is contained in:
quasar 2025-05-30 13:59:52 +02:00
parent 344fccac45
commit 7a4950ea83
2 changed files with 213 additions and 102 deletions

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
Container, Container,
Typography, Typography,
@ -10,6 +10,11 @@ import {
Button, Button,
Box, Box,
Chip, Chip,
TextField,
Select,
MenuItem,
Paper,
SelectChangeEvent,
} from '@mui/material'; } from '@mui/material';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import axios from 'axios'; import axios from 'axios';
@ -21,12 +26,23 @@ const JobList: React.FC = () => {
const [jobs, setJobs] = useState<JobOffer[]>([]); const [jobs, setJobs] = useState<JobOffer[]>([]);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [searchTerm, setSearchTerm] = useState<string>('');
const [locationTerm, setLocationTerm] = useState<string>('');
const [contractType, setContractType] = useState<string>('');
const [sortBy, setSortBy] = useState<string>('publicationDate');
const [sortOrder, setSortOrder] = useState<string>('desc');
useEffect(() => {
const fetchJobs = async () => { const fetchJobs = async () => {
try { try {
setLoading(true); setLoading(true);
const response = await axios.get<JobSearchResponse>(API_BASE_URL); const params = {
keyword: searchTerm,
location: locationTerm,
contractType: contractType,
sortBy: sortBy,
sortOrder: sortOrder,
};
const response = await axios.get<JobSearchResponse>(API_BASE_URL, { params });
setJobs(response.data.jobs); setJobs(response.data.jobs);
setError(null); setError(null);
} catch (err) { } catch (err) {
@ -38,8 +54,21 @@ const JobList: React.FC = () => {
} }
}; };
useEffect(() => {
fetchJobs(); fetchJobs();
}, []); }, [searchTerm, locationTerm, contractType, sortBy, sortOrder]);
const handleSearchSubmit = (e: React.FormEvent) => {
e.preventDefault();
fetchJobs();
};
const handleSortChange = (event: React.ChangeEvent<{ value: unknown }>) => {
const value = event.target.value as string;
const [newSortBy, newSortOrder] = value.split(':');
setSortBy(newSortBy);
setSortOrder(newSortOrder);
};
return ( return (
<Container maxWidth="lg" sx={{ mt: 2, mb: 4 }}> <Container maxWidth="lg" sx={{ mt: 2, mb: 4 }}>
@ -47,6 +76,83 @@ const JobList: React.FC = () => {
Découvrez les dernières offres d'emploi Découvrez les dernières offres d'emploi
</Typography> </Typography>
{/* Formulaire de recherche et filtres */}
<Paper
elevation={0}
sx={{
p: 3,
mb: 4,
borderRadius: '12px',
boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.05)',
backgroundColor: 'background.paper',
}}
>
<Box
component="form"
onSubmit={handleSearchSubmit}
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2,
alignItems: 'center',
}}
>
<TextField
label="Mots-clés"
variant="outlined"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
sx={{ flexGrow: 1, minWidth: { xs: '100%', sm: '200px' } }}
/>
<TextField
label="Localisation"
variant="outlined"
value={locationTerm}
onChange={(e) => setLocationTerm(e.target.value)}
sx={{ flexGrow: 1, minWidth: { xs: '100%', sm: '180px' } }}
/>
<Select
value={contractType}
onChange={(e) => setContractType(e.target.value as string)}
displayEmpty
sx={{ minWidth: { xs: '100%', sm: '160px' } }}
>
<MenuItem value="">Tous les contrats</MenuItem>
<MenuItem value="CDI">CDI</MenuItem>
<MenuItem value="CDD">CDD</MenuItem>
<MenuItem value="INTERIM">Intérim</MenuItem>
<MenuItem value="SAISONNIER">Saisonnier</MenuItem>
<MenuItem value="STAGE">Stage</MenuItem>
<MenuItem value="ALTERNANCE">Alternance</MenuItem>
</Select>
<Select
value={`${sortBy}:${sortOrder}`}
onChange={handleSortChange}
displayEmpty
sx={{ minWidth: { xs: '100%', sm: '220px' } }}
>
<MenuItem value="publicationDate:desc">Plus récent</MenuItem>
<MenuItem value="publicationDate:asc">Plus ancien</MenuItem>
<MenuItem value="title:asc">Titre (A-Z)</MenuItem>
<MenuItem value="title:desc">Titre (Z-A)</MenuItem>
</Select>
<Button
type="submit"
variant="contained"
color="secondary"
sx={{
flexGrow: 1,
minWidth: { xs: '100%', sm: '120px' },
py: 1.5,
}}
>
Rechercher
</Button>
</Box>
</Paper>
{loading && ( {loading && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}> <Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
<CircularProgress /> <CircularProgress />
@ -69,6 +175,7 @@ const JobList: React.FC = () => {
<Grid container spacing={3}> <Grid container spacing={3}>
{jobs.map((job) => ( {jobs.map((job) => (
<Grid item xs={12} sm={6} md={4} key={job.id}> <Grid item xs={12} sm={6} md={4} key={job.id}>
<Box sx={{ height: '100%' }}>
<Card <Card
sx={{ sx={{
height: '100%', height: '100%',
@ -145,6 +252,7 @@ const JobList: React.FC = () => {
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
</Box>
</Grid> </Grid>
))} ))}
</Grid> </Grid>

View file

@ -13,6 +13,7 @@ import {
Divider, // Séparateur visuel Divider, // Séparateur visuel
Box // Conteneur générique Box // Conteneur générique
} from '@mui/material'; } from '@mui/material';
import type { SxProps, Theme } from '@mui/material/styles';
// Importation des icônes // Importation des icônes
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
@ -26,9 +27,7 @@ interface SidebarProps {
} }
const Sidebar: React.FC<SidebarProps> = ({ drawerWidth }) => { const Sidebar: React.FC<SidebarProps> = ({ drawerWidth }) => {
return ( const drawerStyles: SxProps<Theme> = {
<Drawer
sx={{
width: drawerWidth, width: drawerWidth,
flexShrink: 0, flexShrink: 0,
'& .MuiDrawer-paper': { // Style du "papier" (fond) de la sidebar '& .MuiDrawer-paper': { // Style du "papier" (fond) de la sidebar
@ -40,7 +39,11 @@ const Sidebar: React.FC<SidebarProps> = ({ drawerWidth }) => {
overflowX: 'hidden', // Empêche le débordement horizontal overflowX: 'hidden', // Empêche le débordement horizontal
borderRadius: '0 12px 12px 0', // Bords arrondis seulement à droite borderRadius: '0 12px 12px 0', // Bords arrondis seulement à droite
}, },
}} };
return (
<Drawer
sx={drawerStyles}
variant="permanent" // La sidebar est toujours visible variant="permanent" // La sidebar est toujours visible
anchor="left" // Positionnée à gauche anchor="left" // Positionnée à gauche
> >