joblist
This commit is contained in:
parent
3883f0cef6
commit
08bb8deec5
3 changed files with 170 additions and 14 deletions
|
@ -1,7 +1,7 @@
|
|||
// job/frontend/src/App.tsx
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import JobSearch from './components/JobSearch';
|
||||
import JobList from './components/JobList';
|
||||
import JobDetail from './components/JobDetail';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
|
@ -99,14 +99,16 @@ const App: React.FC = () => {
|
|||
sx={{
|
||||
flexGrow: 1,
|
||||
p: { xs: 2, md: 4 },
|
||||
width: { sm: '100%' },
|
||||
ml: { sm: drawerWidth },
|
||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { sm: `${drawerWidth}px` },
|
||||
overflowY: 'auto',
|
||||
height: '100vh',
|
||||
boxSizing: 'border-box',
|
||||
backgroundColor: 'background.default',
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/" element={<JobSearch />} />
|
||||
<Route path="/" element={<JobList />} />
|
||||
<Route path="/jobs/:id" element={<JobDetail />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
|
|
156
frontend/src/components/JobList.tsx
Normal file
156
frontend/src/components/JobList.tsx
Normal file
|
@ -0,0 +1,156 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
Grid,
|
||||
Card,
|
||||
CardContent,
|
||||
Button,
|
||||
Box,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import type { JobOffer } from '../types';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3000/api/jobs';
|
||||
|
||||
const JobList: React.FC = () => {
|
||||
const [jobs, setJobs] = useState<JobOffer[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchJobs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get<JobOffer[]>(API_BASE_URL);
|
||||
setJobs(response.data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error("Erreur lors de la récupération des offres:", err);
|
||||
setError("Impossible de charger les offres pour le moment. Veuillez réessayer plus tard.");
|
||||
setJobs([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchJobs();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ mt: 2, mb: 4 }}>
|
||||
<Typography variant="h4" component="h1" gutterBottom sx={{ mb: 4, fontWeight: 600 }}>
|
||||
Découvrez les dernières offres d'emploi
|
||||
</Typography>
|
||||
|
||||
{loading && (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mt: 4 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!loading && !error && jobs.length === 0 && (
|
||||
<Alert severity="info" sx={{ mt: 4 }}>
|
||||
Aucune offre d'emploi disponible pour le moment.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!loading && !error && jobs.length > 0 && (
|
||||
<Grid container spacing={3}>
|
||||
{jobs.map((job) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={job.id}>
|
||||
<Card
|
||||
sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant="h6" component="h2" sx={{ mb: 1, fontWeight: 600 }}>
|
||||
{job.title}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
{job.companyName || 'Entreprise non spécifiée'}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 2, display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{job.contractType && (
|
||||
<Chip
|
||||
label={job.contractType}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: 'primary.light',
|
||||
color: 'primary.contrastText',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{job.locationLabel && (
|
||||
<Chip
|
||||
label={job.locationLabel}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: 'secondary.light',
|
||||
color: 'secondary.contrastText',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
mb: 2,
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{job.description}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mt: 'auto' }}>
|
||||
<Button
|
||||
component={Link}
|
||||
to={`/jobs/${job.id}`}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 2,
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
Voir les détails
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobList;
|
|
@ -1,19 +1,17 @@
|
|||
export interface JobOffer {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
publicationDate: string; // Ou Date, si vous traitez les dates côté frontend
|
||||
romeCode?: string;
|
||||
romeLabel?: string;
|
||||
locationLabel?: string;
|
||||
postalCode?: string;
|
||||
departmentCode?: string;
|
||||
cityName?: string;
|
||||
companyName?: string;
|
||||
locationLabel?: string;
|
||||
cityName?: string;
|
||||
contractType?: string;
|
||||
contractLabel?: string;
|
||||
urlOffre?: string; // <-- AJOUTEZ CETTE LIGNE
|
||||
// Ajoutez d'autres champs si votre modèle Prisma JobOffer en contient
|
||||
description: string;
|
||||
publicationDate: string;
|
||||
urlOffre?: string;
|
||||
romeLabel?: string;
|
||||
postalCode?: string;
|
||||
departmentCode?: string;
|
||||
}
|
||||
|
||||
export interface JobSearchResponse {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue