add of Filter components

This commit is contained in:
Giovanni-Josserand 2025-09-06 11:46:01 +02:00
parent 24426da015
commit bf1ba8947b
6 changed files with 358 additions and 26 deletions

View File

@ -1,13 +1,12 @@
# To do # To do
- Régler problème de scoll entre page - Refaire les images qui vont pas
- Rajouter les images manquantes des projects
- Faire une page explicative par projet - Faire une page explicative par projet
- Faire du responsive - Faire du responsive
## Plus tard ## Plus tard
- Régler problème de scoll entre page
- Refaire la section des skills - Refaire la section des skills
- Ajouter un fonction de filtrage des projects (en fonction de la date, des skills, school ou non)
## Usefull commands ## Usefull commands

View File

@ -0,0 +1,127 @@
.filter-btn {
position: absolute;
top: 2em;
right: 20px;
border: none;
background: none;
padding: 14px;
cursor: pointer;
z-index: 10;
transition: background-color 0.3s ease;
}
.filter-btn svg {
color: var(--text-color);
transition: all 0.3s ease;
}
.filter-btn svg:hover {
color: var(--title-color);
transform: translateY(-2px);
}
.filter-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(5px);
z-index: 100;
}
.filter-sidebar {
position: fixed;
top: 0;
right: -100%;
width: 100%;
max-width: 350px;
height: 100%;
background: rgba(42, 42, 42, 0.5);
backdrop-filter: blur(15px);
border-left: solid rgba(100,100,100,0.5) 1px;
z-index: 101;
display: flex;
flex-direction: column;
transition: right 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.filter-sidebar.visible {
right: 0;
}
.filter-sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid rgba(100, 100, 100, 0.5);
}
.close-filter-btn {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.3s ease;
}
.close-filter-btn:hover {
color: var(--title-color);
}
.filter-sidebar-content {
padding: 1.5rem;
overflow-y: auto;
}
.filter-sidebar-content h4 {
color: var(--text-color);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 0;
margin-bottom: 1rem;
display: flex;
justify-content: start;
}
.filter-group {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-bottom: 2rem;
}
.filter-tag {
background-color: transparent;
border: 1px solid var(--text-color);
color: var(--text-color);
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
}
.filter-tag:hover {
background-color: var(--text-color);
color: #1E1E1E;
transform: translateY(-2px);
}
.filter-tag.active {
background-color: rgba(217, 95, 70, 0.3);
color: var(--important-color);
border-color: var(--important-color);
}

View File

@ -0,0 +1,154 @@
import './Filter.css';
import React, {useEffect, useState} from "react";
function Filter({ filters, setFilters }) {
const [isFilterVisible, setIsFilterVisible] = useState(false);
const showFilters = () => setIsFilterVisible(true);
const hideFilters = () => setIsFilterVisible(false);
const [skills, setSkills] = useState([]);
const [error, setError] = useState(null);
const handleCategoryFilter = (category) => {
setFilters(prev => {
const alreadySelected = prev.category.includes(category);
return {
...prev,
category: alreadySelected
? prev.category.filter(c => c !== category)
: [...prev.category, category],
};
});
};
const toggleTechnologyFilter = (tech) => {
setFilters(prev => {
const alreadySelected = prev.technology.includes(tech);
return {
...prev,
technology: alreadySelected
? prev.technology.filter(t => t !== tech)
: [...prev.technology, tech],
};
});
};
const setYearOrder = (order) => {
setFilters(prev => ({
...prev,
yearOrder: prev.yearOrder === order ? null : order
}));
};
const resetFilters = () => {
setFilters({
category: [],
technology: [],
yearOrder: filters.yearOrder,
});
};
useEffect(() => {
if (isFilterVisible) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isFilterVisible]);
useEffect(() => {
const fetchSkills = async () => {
try {
const response = await fetch('/api/skills/');
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
const data = await response.json();
setSkills(data.data);
} catch (err) {
setError(err.message);
}
};
fetchSkills();
}, []);
if (error) {
return <div>Error retrieving data: {error}</div>;
}
return (
<div>
<button className="filter-btn" onClick={showFilters}>
<svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 -960 960 960" width="30" fill="currentColor">
<path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/>
</svg>
</button>
{isFilterVisible ? <div className="filter-overlay" onClick={hideFilters}></div> : null}
<div className={`filter-sidebar ${isFilterVisible ? 'visible' : ''}`}>
<div className="filter-sidebar-header">
<h3>Filter Projects</h3>
<button className="close-filter-btn" onClick={hideFilters}>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor">
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>
</svg>
</button>
</div>
<div className="filter-sidebar-content">
<div className="filter-group">
<button
className={`filter-tag ${filters.category.length === 0 && filters.technology.length === 0 ? "active" : ""}`}
onClick={resetFilters}
>All</button>
</div>
<h4>Category</h4>
<div className="filter-group">
<button
className={`filter-tag ${filters.category.includes("school") ? "active" : ""}`}
onClick={() => handleCategoryFilter("school")}
>School</button>
<button
className={`filter-tag ${filters.category.includes("personal") ? "active" : ""}`}
onClick={() => handleCategoryFilter("personal")}
>Personal</button>
</div>
<h4>Technology</h4>
<div className="filter-group">
{skills.map(skill => (
<button
className={`filter-tag ${filters.technology.includes(skill.name) ? "active" : ""}`}
onClick={() => toggleTechnologyFilter(skill.name)}
>{skill.name}</button>
))}
</div>
<h4>YEAR</h4>
<div className="filter-group">
<button
className={`filter-tag ${filters.yearOrder === "asc" ? "active" : ""}`}
onClick={() => setYearOrder("asc")}
>Ascending</button>
<button
className={`filter-tag ${filters.yearOrder === "desc" ? "active" : ""}`}
onClick={() => setYearOrder("desc")}
>Descending</button>
</div>
</div>
</div>
</div>
);
}
export default Filter;

View File

@ -5,7 +5,7 @@
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
border-radius: 25px; border-radius: 25px;
padding: 6px 10px; padding: 6px 10px;
z-index: 1000; z-index: 100;
border: solid rgba(100,100,100,0.5) 0.001rem; border: solid rgba(100,100,100,0.5) 0.001rem;
} }

View File

@ -3,12 +3,20 @@ import "./Projects.css"
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {Link, useLocation} from "react-router-dom"; import {Link, useLocation} from "react-router-dom";
import NavBar from "../NavBar/NavBar.jsx"; import NavBar from "../NavBar/NavBar.jsx";
import Filter from "../Filter/Filter.jsx";
function Projects() { function Projects() {
const [projects, setProjects] = useState([]); const [projects, setProjects] = useState([]);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const location = useLocation(); const location = useLocation();
const [filters, setFilters] = useState({
category: [],
technology: [],
yearOrder: "asc",
});
useEffect(() => { useEffect(() => {
const fetchProjects = async () => { const fetchProjects = async () => {
try { try {
@ -38,17 +46,17 @@ function Projects() {
{projects {projects
.filter(project => project.id <= 3) .filter(project => project.id <= 3)
.map(project => ( .map(project => (
<SingleProject <SingleProject
image={project.image_name} image={project.image_name}
title={project.title} title={project.title}
description={project.short_description} description={project.short_description}
skills={project.skills} skills={project.skills}
id={project.id} id={project.id}
school={project.school} school={project.school}
beginningYear={project.beginning_year} beginningYear={project.beginning_year}
endYear={project.end_year} endYear={project.end_year}
/> />
))} ))}
</div> </div>
<div className="show-more-container"> <div className="show-more-container">
<Link to="/projects" className="projects-link">Show more</Link> <Link to="/projects" className="projects-link">Show more</Link>
@ -62,9 +70,54 @@ function Projects() {
<NavBar /> <NavBar />
<h1 className="section-title">All Projects</h1> <h1 className="section-title">All Projects</h1>
<p className="projects-section-subtitle">Here you can find a collection of my work.</p> <p className="projects-section-subtitle">Here you can find a collection of my work.</p>
<Filter filters={filters} setFilters={setFilters} />
</div> </div>
<div className="projects-section-list"> <div className="projects-section-list">
{projects.map(project => ( {projects
.filter(project => {
const categoryFilters = filters.category;
if (categoryFilters.length > 0) {
const isSchool = project.school === 1;
const isFilterSchool = categoryFilters.includes("school");
const isFilterPersonal = categoryFilters.includes("personal");
if ((isSchool && !isFilterSchool) || (!isSchool && !isFilterPersonal)) {
return false;
}
}
if (filters.technology.length > 0) {
const isSkill = filters.technology.every(tech => project.skills.includes(tech));
if(!isSkill){
return false
}
}
return true;
})
.sort((a, b) => {
const aInProgress = !a.end_year;
const bInProgress = !b.end_year;
if (filters.yearOrder === "asc") {
const yearDiff = a.beginning_year - b.beginning_year;
if (yearDiff !== 0) return yearDiff;
if (!aInProgress && bInProgress) return -1;
if (aInProgress && !bInProgress) return 1;
return 0;
} else if (filters.yearOrder === "desc") {
const yearDiff = b.beginning_year - a.beginning_year;
if (yearDiff !== 0) return yearDiff;
if (aInProgress && !bInProgress) return -1;
if (!aInProgress && bInProgress) return 1;
return 0;
} else {
return 0;
}
})
.map(project => (
<SingleProject <SingleProject
image={project.image_name} image={project.image_name}
title={project.title} title={project.title}
@ -83,10 +136,7 @@ function Projects() {
</div> </div>
</section> </section>
) )
}else {
return <div>Page inexistante</div>;
} }
} }

View File

@ -20,13 +20,6 @@ body {
min-height: 100vh; min-height: 100vh;
} }
h1{
font-size: 4rem;
color: white;
}
.section-title { .section-title {
font-size: 2.5rem; font-size: 2.5rem;
color: var(--title-color); color: var(--title-color);
@ -47,7 +40,16 @@ h1{
} }
h1{
font-size: 4rem;
color: white;
}
h2{ h2{
color : var(--title-color); color : var(--title-color);
}
h3 {
margin: 0;
color: var(--title-color);
} }