import sidebar
This commit is contained in:
parent
661f7bfdfa
commit
e1409cb8ca
7 changed files with 1091 additions and 115 deletions
6
backend/package-lock.json
generated
6
backend/package-lock.json
generated
|
@ -239,9 +239,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.15.26",
|
"version": "22.15.27",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.26.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.27.tgz",
|
||||||
"integrity": "sha512-lgISkNrqdQ5DAzjBhnDNGKDuXDNo7/1V4FhNzsKREhWLZTOELQAptuAnJMzHtUl1qyEBBy9lNBKQ9WjyiSloTw==",
|
"integrity": "sha512-5fF+eu5mwihV2BeVtX5vijhdaZOfkQTATrePEaXTcKqI16LhJ7gi2/Vhd9OZM0UojcdmiOCVg5rrax+i1MdoQQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
741
frontend/package-lock.json
generated
741
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,6 +10,10 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mui/icons-material": "^7.1.0",
|
||||||
|
"@mui/material": "^7.1.0",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|
|
@ -1,18 +1,129 @@
|
||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; // Importe les composants de routage
|
```typescript
|
||||||
import JobSearch from './components/JobSearch';
|
// job/frontend/src/App.tsx
|
||||||
import JobDetail from './components/JobDetail'; // Importe le nouveau composant de détail
|
import React from 'react';
|
||||||
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
|
import JobList from './components/JobList';
|
||||||
|
import JobDetail from './components/JobDetail';
|
||||||
|
// import Navbar from './components/Navbar'; // <-- SUPPRIMER CET IMPORT
|
||||||
|
import Sidebar from './components/Sidebar'; // Garde cet import
|
||||||
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
function App() {
|
// Largeur de la sidebar
|
||||||
|
const drawerWidth = 240; // en pixels, comme dans Notion
|
||||||
|
|
||||||
|
// --- Gardez votre thème Material UI 'Notion-like' inchangé ici ---
|
||||||
|
const customTheme = createTheme({
|
||||||
|
palette: {
|
||||||
|
mode: 'light',
|
||||||
|
primary: { main: '#333333', light: '#555555', dark: '#1a1a1a', contrastText: '#FFFFFF' },
|
||||||
|
secondary: { main: '#6A5ACD', light: '#8C7FD9', dark: '#4C3B9B', contrastText: '#FFFFFF' },
|
||||||
|
background: { default: '#F7F7F7', paper: '#FFFFFF' },
|
||||||
|
text: { primary: '#212121', secondary: '#616161', disabled: '#BDBDBD' },
|
||||||
|
error: { main: '#EF9A9A' }, warning: { main: '#FFCC80' }, info: { main: '#90CAF9' }, success: { main: '#A5D6A7' },
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
fontFamily: [
|
||||||
|
'Inter', 'Roboto', 'Arial', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"',
|
||||||
|
].join(','),
|
||||||
|
h1: { fontSize: '2.5rem', fontWeight: 600, color: '#212121' },
|
||||||
|
h2: { fontSize: '2rem', fontWeight: 600, color: '#212121' },
|
||||||
|
h3: { fontSize: '1.75rem', fontWeight: 600, color: '#212121' },
|
||||||
|
body1: { fontSize: '1rem', lineHeight: 1.6, color: '#212121' },
|
||||||
|
body2: { fontSize: '0.875rem', lineHeight: 1.5, color: '#616161' },
|
||||||
|
button: { textTransform: 'none' },
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiAppBar: { // Optionnel : Si vous voulez garder une AppBar très minimale pour des actions spécifiques (sans navigation)
|
||||||
|
styleOverrides: {
|
||||||
|
colorPrimary: {
|
||||||
|
backgroundColor: '#FFFFFF', color: '#212121', boxShadow: '0px 1px 4px rgba(0, 0, 0, 0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: { borderRadius: '6px', textTransform: 'none' },
|
||||||
|
containedPrimary: {
|
||||||
|
backgroundColor: '#ECEFF1', color: '#212121', boxShadow: 'none',
|
||||||
|
'&:hover': { backgroundColor: '#CFD8DC', boxShadow: 'none' },
|
||||||
|
'&:active': { backgroundColor: '#B0BEC5' },
|
||||||
|
},
|
||||||
|
containedSecondary: {
|
||||||
|
backgroundColor: '#6A5ACD', color: '#FFFFFF', boxShadow: 'none',
|
||||||
|
'&:hover': { backgroundColor: '#4C3B9B', boxShadow: 'none' },
|
||||||
|
},
|
||||||
|
outlined: {
|
||||||
|
borderColor: '#CFD8DC', color: '#616161',
|
||||||
|
'&:hover': { borderColor: '#B0BEC5', backgroundColor: 'rgba(0, 0, 0, 0.02)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiPaper: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
borderRadius: '8px', boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.05)', backgroundColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTextField: {
|
||||||
|
defaultProps: { variant: 'outlined' },
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: '6px !important', backgroundColor: '#FFFFFF',
|
||||||
|
'&.Mui-focused fieldset': { borderColor: '#B0BEC5' },
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root': { color: '#616161' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiInputBase: {
|
||||||
|
styleOverrides: { input: { padding: '12px 14px' } },
|
||||||
|
},
|
||||||
|
MuiSelect: {
|
||||||
|
styleOverrides: { select: { borderRadius: '6px !important', padding: '12px 14px' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// --- Fin du thème Material UI 'Notion-like' ---
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Router> {/* Enveloppe toute l'application dans le routeur */}
|
<ThemeProvider theme={customTheme}>
|
||||||
<div className="App">
|
<CssBaseline />
|
||||||
<Routes> {/* Définit les routes */}
|
<Router>
|
||||||
<Route path="/" element={<JobSearch />} /> {/* Route pour la recherche d'offres */}
|
<Box sx={{ display: 'flex', minHeight: '100vh', backgroundColor: 'background.default' }}> {/* Conteneur principal flex */}
|
||||||
<Route path="/jobs/:id" element={<JobDetail />} /> {/* Route pour le détail d'une offre */}
|
|
||||||
|
{/* Sidebar : elle prend sa largeur fixe */}
|
||||||
|
<Sidebar drawerWidth={drawerWidth} />
|
||||||
|
|
||||||
|
{/* Contenu principal : prend l'espace restant et a une marge à gauche */}
|
||||||
|
<Box
|
||||||
|
component="main"
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1, // Prend l'espace restant
|
||||||
|
p: { xs: 2, md: 4 }, // Padding général pour le contenu (réactif)
|
||||||
|
width: { sm: `calc(100% - ${drawerWidth}px)` }, // Prend toute la largeur moins la sidebar sur desktop
|
||||||
|
ml: { sm: `${drawerWidth}px` }, // Marge à gauche égale à la largeur de la sidebar sur desktop
|
||||||
|
// Pas besoin de mt (margin-top) car il n'y a plus de Navbar en haut.
|
||||||
|
// S'assure que le contenu peut défiler indépendamment si trop long
|
||||||
|
overflowY: 'auto',
|
||||||
|
backgroundColor: 'background.default', // Couleur de fond du contenu
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Ici seront rendues vos routes de contenu */}
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<JobList />} />
|
||||||
|
<Route path="/jobs/:id" element={<JobDetail />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</Box>
|
||||||
|
</Box>
|
||||||
</Router>
|
</Router>
|
||||||
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
```
|
|
@ -1,9 +1,18 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link as RouterLink } from 'react-router-dom'; // Renommez Link pour éviter les conflits avec MUI Button
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import {
|
||||||
|
Box, // Conteneur générique pour le style
|
||||||
|
Typography, // Titres, paragraphes
|
||||||
|
Button, // Bouton "Postuler"
|
||||||
|
CircularProgress, // Indicateur de chargement
|
||||||
|
Paper, // Pour encadrer le contenu de l'offre
|
||||||
|
} from '@mui/material';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; // Icône pour le bouton retour
|
||||||
|
|
||||||
import type { JobOffer } from '../types'; // Use type-only import
|
import type { JobOffer } from '../types'; // Use type-only import
|
||||||
|
|
||||||
const API_BASE_URL = 'http://localhost:3000/api/jobs';
|
const API_BASE_URL = 'http://localhost:3000/api/jobs'; // Assurez-vous que c'est la bonne URL de votre API backend
|
||||||
|
|
||||||
const JobDetail: React.FC = () => {
|
const JobDetail: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
@ -19,11 +28,11 @@ const JobDetail: React.FC = () => {
|
||||||
const response = await axios.get<JobOffer>(`${API_BASE_URL}/${id}`);
|
const response = await axios.get<JobOffer>(`${API_BASE_URL}/${id}`);
|
||||||
setJob(response.data);
|
setJob(response.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erreur lors de la récupération des détails de l\'offre:', err);
|
console.error('Error fetching job detail:', err);
|
||||||
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
||||||
setError('Offre d\'emploi non trouvée.');
|
setError('Job offer not found.');
|
||||||
} else {
|
} else {
|
||||||
setError('Impossible de charger les détails de l\'offre. Veuillez réessayer.');
|
setError('Failed to load job offer details. Please try again.');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
@ -36,86 +45,129 @@ const JobDetail: React.FC = () => {
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const handleApplyClick = () => {
|
const handleApplyClick = () => {
|
||||||
const applyUrl = job?.urlOffre;
|
if (job?.urlOffre) {
|
||||||
|
window.open(job.urlOffre, '_blank'); // Ouvre l'URL dans un nouvel onglet
|
||||||
if (applyUrl) {
|
|
||||||
window.open(applyUrl, '_blank', 'noopener,noreferrer');
|
|
||||||
} else {
|
} else {
|
||||||
alert("Aucune URL de candidature disponible pour cette offre.");
|
// Utilisation d'un alert MUI ou d'un SnackBar serait mieux ici,
|
||||||
|
// mais pour l'instant, gardons l'alert JS pour la simplicité.
|
||||||
|
alert("Désolé, l'URL de candidature n'est pas disponible pour cette offre.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <p className="info-message">Chargement des détails de l'offre...</p>;
|
return (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80vh' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="body1" sx={{ ml: 2, color: 'text.secondary' }}>
|
||||||
|
Chargement des détails de l'offre...
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="job-detail-container">
|
<Box sx={{ textAlign: 'center', py: 4, maxWidth: '800px', mx: 'auto' }}>
|
||||||
<p className="info-message" style={{ color: 'red' }}>Erreur : {error}</p>
|
<Typography variant="h6" color="error" sx={{ mb: 2 }}>Erreur : {error}</Typography>
|
||||||
<Link to="/" style={{ display: 'block', textAlign: 'center', marginTop: '20px' }}>Retour à la recherche</Link>
|
<Button component={RouterLink} to="/" variant="outlined">
|
||||||
</div>
|
Retour à la recherche
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
return (
|
return (
|
||||||
<div className="job-detail-container">
|
<Box sx={{ textAlign: 'center', py: 4, maxWidth: '800px', mx: 'auto' }}>
|
||||||
<p className="info-message">Aucun détail d'offre disponible.</p>
|
<Typography variant="h6" sx={{ mb: 2, color: 'text.secondary' }}>Aucun détail d'offre disponible.</Typography>
|
||||||
<Link to="/" style={{ display: 'block', textAlign: 'center', marginTop: '20px' }}>Retour à la recherche</Link>
|
<Button component={RouterLink} to="/" variant="outlined">
|
||||||
</div>
|
Retour à la recherche
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="job-detail-container" style={{ padding: '20px', maxWidth: '800px', margin: '20px auto', backgroundColor: '#fff', borderRadius: '8px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)' }}>
|
<Box sx={{
|
||||||
<Link to="/" style={{ textDecoration: 'none', color: '#3498db', fontWeight: 'bold', marginBottom: '20px', display: 'inline-block' }}>
|
padding: { xs: 2, md: 4 }, // Padding réactif
|
||||||
← Retour à la recherche
|
maxWidth: '800px',
|
||||||
</Link>
|
margin: '20px auto',
|
||||||
<h1 style={{ color: '#2c3e50', marginBottom: '10px' }}>{job.title}</h1>
|
backgroundColor: 'background.default', // Fond de la page (non du Paper)
|
||||||
<p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}>
|
}}>
|
||||||
<strong>Entreprise :</strong> {job.companyName || 'Non spécifié'}
|
<Button
|
||||||
</p>
|
component={RouterLink} // Utilisez RouterLink pour la navigation interne
|
||||||
<p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}>
|
to="/"
|
||||||
<strong>Localisation :</strong> {job.locationLabel || job.cityName || 'Non spécifié'}
|
startIcon={<ArrowBackIcon />}
|
||||||
</p>
|
variant="outlined" // Style de bouton 'outlined' comme dans Notion
|
||||||
<p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}>
|
sx={{ mb: 3 }} // Marge en bas
|
||||||
<strong>Type de contrat :</strong> {job.contractLabel || job.contractType || 'Non spécifié'}
|
>
|
||||||
</p>
|
Retour à la recherche d'offres
|
||||||
<p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}>
|
</Button>
|
||||||
<strong>Date de publication :</strong> {new Date(job.publicationDate).toLocaleDateString('fr-FR')}
|
|
||||||
</p>
|
|
||||||
{job.romeLabel && <p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}><strong>Code ROME :</strong> {job.romeLabel}</p>}
|
|
||||||
{job.postalCode && <p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}><strong>Code postal :</strong> {job.postalCode}</p>}
|
|
||||||
{job.departmentCode && <p style={{ fontSize: '1.1em', color: '#555', marginBottom: '15px' }}><strong>Département :</strong> {job.departmentCode}</p>}
|
|
||||||
|
|
||||||
<h2 style={{ color: '#2c3e50', marginTop: '30px', marginBottom: '10px' }}>Description</h2>
|
<Paper elevation={0} sx={{ // Utilisation de Paper pour le bloc de contenu de l'offre
|
||||||
<p style={{ lineHeight: '1.6', whiteSpace: 'pre-wrap' }}>{job.description}</p>
|
p: { xs: 3, md: 4 }, // Padding
|
||||||
|
borderRadius: '12px', // Bords arrondis
|
||||||
|
boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.05)', // Ombre douce
|
||||||
|
backgroundColor: 'background.paper', // Fond blanc du Paper
|
||||||
|
}}>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom sx={{ color: 'text.primary' }}>
|
||||||
|
{job.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>Entreprise :</strong> {job.companyName || 'N/A'}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>Localisation :</strong> {job.locationLabel || job.cityName || 'N/A'}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>Contrat :</strong> {job.contractLabel || job.contractType || 'N/A'}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>Publiée le :</strong> {new Date(job.publicationDate).toLocaleDateString()}
|
||||||
|
</Typography>
|
||||||
|
{job.romeLabel && (
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>ROME :</strong> {job.romeLabel}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{job.postalCode && (
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>Code postal :</strong> {job.postalCode}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{job.departmentCode && (
|
||||||
|
<Typography variant="body1" sx={{ mb: 1.5, color: 'text.secondary' }}>
|
||||||
|
<strong>Département :</strong> {job.departmentCode}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" sx={{ mt: 4, mb: 2, color: 'text.primary' }}>
|
||||||
|
Description
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ lineHeight: 1.7, whiteSpace: 'pre-wrap', color: 'text.primary' }}>
|
||||||
|
{job.description}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
{/* Bouton "Postuler" - s'affiche uniquement si job.urlOffre existe */}
|
|
||||||
{job.urlOffre && (
|
{job.urlOffre && (
|
||||||
<button
|
<Box sx={{ mt: 4, textAlign: 'center' }}>
|
||||||
|
<Button
|
||||||
|
variant="contained" // Utilisez le style 'contained' du thème
|
||||||
|
color="secondary" // Utilisez la couleur secondaire pour le bouton "Postuler"
|
||||||
onClick={handleApplyClick}
|
onClick={handleApplyClick}
|
||||||
style={{
|
sx={{
|
||||||
backgroundColor: '#28a745', // Vert pour le bouton "Postuler"
|
px: 4, // Padding horizontal
|
||||||
color: 'white',
|
py: 1.5, // Padding vertical
|
||||||
border: 'none',
|
fontSize: '1.05em',
|
||||||
borderRadius: '5px',
|
borderRadius: '8px', // Bords arrondis
|
||||||
padding: '12px 25px',
|
|
||||||
fontSize: '1.1em',
|
|
||||||
cursor: 'pointer',
|
|
||||||
marginTop: '30px',
|
|
||||||
display: 'block',
|
|
||||||
width: 'fit-content',
|
|
||||||
margin: '30px auto 0 auto', // Centre le bouton
|
|
||||||
transition: 'background-color 0.3s ease',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Postuler maintenant
|
Postuler maintenant
|
||||||
</button>
|
</Button>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Paper>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default JobDetail;
|
export default JobDetail;
|
||||||
|
|
33
frontend/src/components/Navbar.tsx
Normal file
33
frontend/src/components/Navbar.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// job/frontend/src/components/Navbar.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import AppBar from '@mui/material/AppBar';
|
||||||
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
|
||||||
|
const Navbar: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<AppBar position="static">
|
||||||
|
<Toolbar>
|
||||||
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||||
|
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||||
|
JobFinder
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
<Button color="inherit" component={Link} to="/">
|
||||||
|
Rechercher
|
||||||
|
</Button>
|
||||||
|
{/* FUTURE: Ajoutez d'autres boutons ici pour Favoris, Connexion, etc. */}
|
||||||
|
{/* <Button color="inherit" component={Link} to="/favorites">
|
||||||
|
Mes Favoris
|
||||||
|
</Button> */}
|
||||||
|
{/* <Button color="inherit" component={Link} to="/login">
|
||||||
|
Connexion
|
||||||
|
</Button> */}
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
97
frontend/src/components/Sidebar.tsx
Normal file
97
frontend/src/components/Sidebar.tsx
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// job/frontend/src/components/Sidebar.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Drawer, // Composant MUI pour la barre latérale
|
||||||
|
Toolbar, // Pour aligner le contenu après la Navbar
|
||||||
|
List, // Conteneur de liste
|
||||||
|
ListItem, // Élément de liste
|
||||||
|
ListItemButton, // Bouton cliquable pour l'élément de liste
|
||||||
|
ListItemIcon, // Pour les icônes à gauche du texte
|
||||||
|
ListItemText, // Pour le texte de l'élément
|
||||||
|
Typography, // Pour les titres ou textes
|
||||||
|
Divider, // Séparateur visuel
|
||||||
|
Box // Conteneur générique
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
// Importation des icônes
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
// FUTURES ICÔNES POUR LES FAVORIS, COMPTE, ETC.
|
||||||
|
// import StarIcon from '@mui/icons-material/Star';
|
||||||
|
// import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||||
|
|
||||||
|
// Propriétés attendues par le composant Sidebar
|
||||||
|
interface SidebarProps {
|
||||||
|
drawerWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sidebar: React.FC<SidebarProps> = ({ drawerWidth }) => {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
sx={{
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
'& .MuiDrawer-paper': { // Style du "papier" (fond) de la sidebar
|
||||||
|
width: drawerWidth,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
backgroundColor: 'background.paper', // Fond blanc du thème
|
||||||
|
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.05)', // Ombre douce
|
||||||
|
borderRadius: '0 8px 8px 0', // Bords arrondis seulement à droite
|
||||||
|
overflowX: 'hidden', // Empêche le débordement horizontal
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
variant="permanent" // La sidebar est toujours visible
|
||||||
|
anchor="left" // Positionnée à gauche
|
||||||
|
>
|
||||||
|
{/* Un Toolbar pour s'aligner avec la Navbar en haut */}
|
||||||
|
{/* Peut être utilisé pour un logo ou un titre dans la sidebar si vous enlevez le titre de la Navbar */}
|
||||||
|
<Toolbar sx={{
|
||||||
|
backgroundColor: 'primary.main', // Assurez-vous que cette couleur correspond à la Navbar ou est transparente
|
||||||
|
minHeight: { xs: '56px', sm: '64px' }, // Hauteur de la Navbar
|
||||||
|
}}>
|
||||||
|
{/* Laissez vide ou ajoutez un logo/titre de l'application ici pour une disposition Notion-like */}
|
||||||
|
<Typography variant="h6" noWrap component="div" sx={{ color: 'primary.contrastText', display: 'flex', alignItems: 'center' }}>
|
||||||
|
<img src="/logo-jobfinder.png" alt="JobFinder Logo" style={{ height: '30px', marginRight: '10px' }} /> {/* Si vous avez un logo */}
|
||||||
|
JobFinder
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
<Divider /> {/* Ligne de séparation */}
|
||||||
|
|
||||||
|
<Box sx={{ overflow: 'auto' }}> {/* Permet le défilement si le contenu dépasse */}
|
||||||
|
<List>
|
||||||
|
{/* Lien vers la page de recherche */}
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton component={RouterLink} to="/">
|
||||||
|
<ListItemIcon>
|
||||||
|
<SearchIcon sx={{ color: 'text.secondary' }} /> {/* Icône de recherche */}
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Rechercher" sx={{ color: 'text.primary' }} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{/* FUTURS LIENS DE LA PHASE 3 */}
|
||||||
|
{/* <ListItem disablePadding>
|
||||||
|
<ListItemButton component={RouterLink} to="/favorites">
|
||||||
|
<ListItemIcon>
|
||||||
|
<StarIcon sx={{ color: 'text.secondary' }} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Mes Favoris" sx={{ color: 'text.primary' }} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton component={RouterLink} to="/account">
|
||||||
|
<ListItemIcon>
|
||||||
|
<AccountCircleIcon sx={{ color: 'text.secondary' }} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Mon Compte" sx={{ color: 'text.primary' }} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem> */}
|
||||||
|
</List>
|
||||||
|
<Divider />
|
||||||
|
{/* Vous pouvez ajouter d'autres sections de liens ici */}
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
Loading…
Add table
Add a link
Reference in a new issue