From bf1ba8947bcc38edf154e4e6486b6775eafbfffe Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Sat, 6 Sep 2025 11:46:01 +0200 Subject: [PATCH] add of Filter components --- README.md | 5 +- src/components/Filter/Filter.css | 127 ++++++++++++++++++++++ src/components/Filter/Filter.jsx | 154 +++++++++++++++++++++++++++ src/components/NavBar/NavBar.css | 2 +- src/components/Projects/Projects.jsx | 80 +++++++++++--- src/index.css | 16 +-- 6 files changed, 358 insertions(+), 26 deletions(-) create mode 100644 src/components/Filter/Filter.css create mode 100644 src/components/Filter/Filter.jsx diff --git a/README.md b/README.md index 861243c..08310a4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ # To do -- Régler problème de scoll entre page -- Rajouter les images manquantes des projects +- Refaire les images qui vont pas - Faire une page explicative par projet - Faire du responsive ## Plus tard +- Régler problème de scoll entre page - Refaire la section des skills -- Ajouter un fonction de filtrage des projects (en fonction de la date, des skills, school ou non) ## Usefull commands diff --git a/src/components/Filter/Filter.css b/src/components/Filter/Filter.css new file mode 100644 index 0000000..f1e1937 --- /dev/null +++ b/src/components/Filter/Filter.css @@ -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); +} \ No newline at end of file diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx new file mode 100644 index 0000000..58764b1 --- /dev/null +++ b/src/components/Filter/Filter.jsx @@ -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
Error retrieving data: {error}
; + } + + + + return ( +
+ + + {isFilterVisible ?
: null} + +
+
+

Filter Projects

+ +
+
+
+ + +
+

Category

+
+ + +
+

Technology

+
+ {skills.map(skill => ( + + ))} +
+

YEAR

+
+ + +
+
+
+
+ ); +} + +export default Filter; \ No newline at end of file diff --git a/src/components/NavBar/NavBar.css b/src/components/NavBar/NavBar.css index 03acbf4..f0d13ec 100644 --- a/src/components/NavBar/NavBar.css +++ b/src/components/NavBar/NavBar.css @@ -5,7 +5,7 @@ backdrop-filter: blur(12px); border-radius: 25px; padding: 6px 10px; - z-index: 1000; + z-index: 100; border: solid rgba(100,100,100,0.5) 0.001rem; } diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx index 7a02e8c..8157a6c 100644 --- a/src/components/Projects/Projects.jsx +++ b/src/components/Projects/Projects.jsx @@ -3,12 +3,20 @@ import "./Projects.css" import React, {useEffect, useState} from "react"; import {Link, useLocation} from "react-router-dom"; import NavBar from "../NavBar/NavBar.jsx"; +import Filter from "../Filter/Filter.jsx"; function Projects() { const [projects, setProjects] = useState([]); const [error, setError] = useState(null); const location = useLocation(); + const [filters, setFilters] = useState({ + category: [], + technology: [], + yearOrder: "asc", + }); + + useEffect(() => { const fetchProjects = async () => { try { @@ -38,17 +46,17 @@ function Projects() { {projects .filter(project => project.id <= 3) .map(project => ( - - ))} + + ))}
Show more @@ -62,9 +70,54 @@ function Projects() {

All Projects

Here you can find a collection of my work.

+
- {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 => ( ) - }else { - return
Page inexistante
; } - } diff --git a/src/index.css b/src/index.css index 896d8d5..b0d79f8 100644 --- a/src/index.css +++ b/src/index.css @@ -20,13 +20,6 @@ body { min-height: 100vh; } - - -h1{ - font-size: 4rem; - color: white; -} - .section-title { font-size: 2.5rem; color: var(--title-color); @@ -47,7 +40,16 @@ h1{ } +h1{ + font-size: 4rem; + color: white; +} h2{ color : var(--title-color); +} + +h3 { + margin: 0; + color: var(--title-color); } \ No newline at end of file