diff --git a/package-lock.json b/package-lock.json index 4b8ff7e..8daf889 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1109,6 +1109,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@mdx-js/react": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", @@ -2684,6 +2690,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -4421,6 +4439,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-docgen": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.2.tgz", diff --git a/public/search.svg b/public/search.svg new file mode 100644 index 0000000..f0d166f --- /dev/null +++ b/public/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Event/Event.jsx b/src/components/Event/Event.jsx index cc01258..6a75fd1 100644 --- a/src/components/Event/Event.jsx +++ b/src/components/Event/Event.jsx @@ -42,17 +42,20 @@ function Event({event}){

{event.name}

{event.description}

- +
+ ) : null} {windowSize.width > 768 ? ( diff --git a/src/components/Event/Event.module.css b/src/components/Event/Event.module.css index e294c74..22c5fe8 100644 --- a/src/components/Event/Event.module.css +++ b/src/components/Event/Event.module.css @@ -8,6 +8,7 @@ .eventContainerTop { display: flex; + justify-content: space-between; } .eventContainerBottom{ diff --git a/src/components/SearchBar/SearchBar.jsx b/src/components/SearchBar/SearchBar.jsx new file mode 100644 index 0000000..3241c0c --- /dev/null +++ b/src/components/SearchBar/SearchBar.jsx @@ -0,0 +1,75 @@ +import styles from "./SearchBar.module.css" +import {Link} from "react-router"; +import {useEffect, useState} from "react"; +import searchEvents from "../../utils/searchEvents.js"; + +function SearchBar() { + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); + + useEffect(() => { + const performSearch = async (query) => { + if (query.trim() === '') { + setSearchResults([]); + return; + } + + const data = await searchEvents(query); + if (data) { + setSearchResults(data); + } else { + setSearchResults([]); + } + }; + + + const handler = setTimeout(() => { + performSearch(searchQuery); + }, 300); + + return () => { + clearTimeout(handler); + }; + }, [searchQuery]); + + return ( +
+
event.preventDefault()}> + + + + setSearchQuery(event.target.value)} + /> +
+ +
+ {searchResults.length > 0 ? ( +
+ ) : null} +
+ + + {searchResults.map((result, i) => ( + +
+
+ {result.image == null ? ( + Pas de photo de l'événement + ) : ( + Photo de l'événement + )} +
+

{result.name}

+
+ + ))} +
+ ); +} + +export default SearchBar; \ No newline at end of file diff --git a/src/components/SearchBar/SearchBar.module.css b/src/components/SearchBar/SearchBar.module.css new file mode 100644 index 0000000..ff2830e --- /dev/null +++ b/src/components/SearchBar/SearchBar.module.css @@ -0,0 +1,73 @@ +.searchBarContainer{ + position: fixed; + top: 10rem; + z-index: 100; + width: 60%; + background: #fff; +} + +.searchBarForm{ + display: flex; +} + + + +.eventImageDiv { + display: flex; + align-items: center; + justify-content: center; + margin-right: 1rem; + width: auto; + height: 2rem; + flex-shrink: 0; +} + +.searchedEvent{ + display: flex; + padding: 0.5rem; + border-radius: 15px; + align-items: center; +} + +.searchedEvent:hover { + background: #afafaf; + cursor: pointer; +} + +.searchedEvent p { + display: flex; + align-items: center; + text-decoration: none; + color: black; +} + + +.lineContainer{ + display: flex; + justify-content: center; + align-items: center; +} + +.line{ + height: 0; + width: 96%; + border: #606060 solid 1px; + margin: 0.5rem 0; +} + +.eventImage { + max-width: 100%; + max-height: 100%; + object-fit: contain; + border-radius: 20px; +} + +.searchBarForm input{ + background: none; + box-shadow: none; + height: 2em; +} + +.searchBarForm input:focus{ + outline: none; +} diff --git a/src/components/ToolBar/ToolBar.jsx b/src/components/ToolBar/ToolBar.jsx new file mode 100644 index 0000000..7788f59 --- /dev/null +++ b/src/components/ToolBar/ToolBar.jsx @@ -0,0 +1,66 @@ +import styles from "./ToolBar.module.css" +import {Link} from "react-router"; +import Filter from "../Filter/Filter.jsx"; +import {useEffect, useState} from "react"; +import SearchBar from "../SearchBar/SearchBar.jsx"; + + +function ToolBar({setFilters, filters}) { + const [isFilterVisible, setIsFilterVisible] = useState(false); + const [isSearchVisible, setIsSearchVisible] = useState(false); + const toggleFilters = () => { + setIsFilterVisible(prev => !prev); + }; + + + useEffect(() => { + if (isSearchVisible) { + document.documentElement.style.overflow = 'hidden'; + } else { + document.documentElement.style.overflow = ''; + } + + return () => { + document.documentElement.style.overflow = ''; + }; + }, [isSearchVisible]); + + + + return ( +
+ {isSearchVisible ? ( +
+
setIsSearchVisible(false)}>
+ +
+ ) : null} +
+ + + + + + + +
+ +
+ +
+
+
+
+ ); +} + +export default ToolBar; \ No newline at end of file diff --git a/src/components/ToolBar/ToolBar.module.css b/src/components/ToolBar/ToolBar.module.css new file mode 100644 index 0000000..d67cd44 --- /dev/null +++ b/src/components/ToolBar/ToolBar.module.css @@ -0,0 +1,97 @@ +.eventsButtons { + overflow: visible; + position: fixed; + top: 10rem; + right: 2rem; + display: flex; + flex-direction: column; + align-items: flex-end; + width: fit-content; + height: fit-content; + gap: 1rem; +} + + +.filterContainer{ + padding: 0; + border-radius: 30px; + overflow: visible; +} + +.filterMenu{ + padding: 5px 5px 5px 5px; +} + +.createButton, .searchButton, .sortButton { + display: flex; + align-items: center; + justify-content: center; + border: none; + border-radius: 50%; + width: 55px; + height: 55px; + transition: all 0.5s ease; +} + +.createButton { + font-size: 2rem; + color: #019AFF; +} + +.createButton:hover, .searchButton:hover, .mobileFilter:hover { + transform: translateY(-5px); + cursor: pointer; +} + + +.searchBarOverlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100; + background-color: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(4px); +} +.searchBarContainer{ + display: flex; + justify-content: center; +} + +.sortButton svg, .searchButton svg { + transform: scale(0.8); + fill: #019AFF; +} + + +.sortButton:focus, .searchButton:focus { + outline: none; +} + + + +@media screen and (max-width: 768px) { + .eventsButtons { + flex-direction: row; + position: relative; + top: 0; + right: 0; + align-items: flex-start; + width: 100%; + margin-top: 16px; + } + + .filterContainer{ + display: flex; + width: fit-content; + } + + .filterMenu{ + width: fit-content; + } + + .createButton:hover, .searchButton:hover, .mobileFilter:hover { + transform: none !important; + } +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 71532f3..6246007 100644 --- a/src/index.css +++ b/src/index.css @@ -1,10 +1,13 @@ * { margin: 0; padding: 0; - width: 100%; box-sizing: border-box; } +html { + overflow-x: hidden; +} + :root { font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -19,12 +22,14 @@ @media screen and (max-width: 768px) { .content{ padding: 0.7rem; - z-index:1; } } + + .content { padding: 1.5rem; z-index: 1; + width: 100%; } a { diff --git a/src/pages/Events/EventsPage.jsx b/src/pages/Events/EventsPage.jsx index 5e84496..2b458f4 100644 --- a/src/pages/Events/EventsPage.jsx +++ b/src/pages/Events/EventsPage.jsx @@ -1,52 +1,21 @@ import { useEffect, useState } from "react"; import Event from "../../components/Event/Event.jsx"; -import { Link } from "react-router"; import styles from "./EventsPage.module.css"; -import Filter from "../../components/Filter/Filter.jsx"; +import ToolBar from "../../components/ToolBar/ToolBar.jsx"; +import filter from "../../utils/filter.js"; import getAllEvents from "../../utils/event/getAllEvents.js"; - function EventsPage(){ - const [isFilterVisible, setIsFilterVisible] = useState(false); - const toggleFilters = () => { - setIsFilterVisible(prev => !prev); - }; - const [filters, setFilters] = useState({ alphabetical: "asc", yearOrder: "asc", }); + const [events, setEvents] = useState([]); - /* - { - id: 1, - name: "Marathon de la Ville", - description: "Participez au marathon annuel et soutenez les associations locales.", - image: null, - tasks: [ - { - name: "Inscription des participants", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Centre sportif", - description: "Accueillir et enregistrer tous les coureurs.", - volunteers_count: 5 - }, - { - name: "Distribution des dossards", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Salle principale", - description: "Remettre les dossards et goodies aux participants.", - volunteers_count: 3 - }, - ] - } - */ useEffect(() => { (async () => { @@ -55,71 +24,12 @@ function EventsPage(){ }) () }, []); - const compareDate = (dateA, dateB) => { - const dA = new Date(dateA); - const dB = new Date(dateB); - - if (filters.yearOrder === "asc") { - return dB - dA; - } else { - return dA - dB; - } - }; - - /* - - const sortedEvents = [...events].sort((a, b) => { - const sortedATasks = a.tasks.sort((taskA, taskB) => - compareDate(taskA.start, taskB.start) - ); - const sortedBTasks = b.tasks.sort((taskA, taskB) => - compareDate(taskA.start, taskB.start) - ); - - const timeComparison = compareDate( - sortedATasks[0].start, - sortedBTasks[0].start - ); - - if (timeComparison !== 0) { - return timeComparison; - } - - if (filters.alphabetical !== null) { - return filters.alphabetical === "asc" - ? a.name.localeCompare(b.name) - : b.name.localeCompare(a.name); - } - - return 0; - }); - - */ - - - return (
-
- - + - -
- -
- -
-
-
+
- {events.map((event, index) => ( + {filter(events, filters).map((event, index) => ( ))}
diff --git a/src/pages/Events/EventsPage.module.css b/src/pages/Events/EventsPage.module.css index 9bff2ec..7666a73 100644 --- a/src/pages/Events/EventsPage.module.css +++ b/src/pages/Events/EventsPage.module.css @@ -1,5 +1,6 @@ .eventsContainer { width: 100%; + position: relative; } @@ -7,93 +8,4 @@ display: flex; align-items: center; flex-direction: column; -} - -.eventsButtons { - overflow: visible; - position: fixed; - top: 10rem; - right: 2rem; - display: flex; - flex-direction: column; - align-items: flex-end; - width: fit-content; - height: fit-content; - gap: 1rem; -} - - -.filterContainer{ - padding: 0; - border-radius: 30px; - overflow: visible; -} - -.filterMenu{ - padding: 5px 5px 5px 5px; -} - - -.createButton, .sortButton { - display: flex; - align-items: center; - justify-content: center; - border: none; - border-radius: 50%; - width: 55px; - height: 55px; - transition: all 0.5s ease; -} - -.createButton { - font-size: 2rem; - color: #019AFF; -} - -.sortButton svg { - transform: scale(0.8); - fill: #019AFF; -} - -.sortButton:hover { - cursor: pointer; -} - -.createButton:hover, .mobileFilter:hover { - transform: translateY(-5px); -} - - -.sortButton:focus { - outline: none; -} -.sortButton:focus-visible { - outline: 2px solid black; -} - - -@media screen and (max-width: 768px) { - .eventsButtons { - flex-direction: row; - position: relative; - top: 0; - right: 0; - align-items: flex-start; - width: 100%; - margin-top: 16px; - } - - .filterContainer{ - display: flex; - width: fit-content; - } - - .filterMenu{ - width: fit-content; - } - - .createButton:hover, .mobileFilter:hover { - transform: none !important; - } -} - +} \ No newline at end of file diff --git a/src/utils/filter.js b/src/utils/filter.js new file mode 100644 index 0000000..93f126f --- /dev/null +++ b/src/utils/filter.js @@ -0,0 +1,42 @@ +function filter(elements, filters) { + const compareDate = (dateA, dateB) => { + const dA = new Date(dateA); + const dB = new Date(dateB); + + if (filters.yearOrder === "asc") { + return dB - dA; + } else { + return dA - dB; + } + }; + + + return [...elements].sort((a, b) => { + const sortedATasks = a.tasks.sort((taskA, taskB) => + compareDate(taskA.start, taskB.start) + ); + const sortedBTasks = b.tasks.sort((taskA, taskB) => + compareDate(taskA.start, taskB.start) + ); + + const timeComparison = compareDate( + sortedATasks[0].start, + sortedBTasks[0].start + ); + + if (timeComparison !== 0) { + return timeComparison; + } + + if (filters.alphabetical !== null) { + return filters.alphabetical === "asc" + ? a.name.localeCompare(b.name) + : b.name.localeCompare(a.name); + } + + return 0; + }); +} + + +export default filter; \ No newline at end of file diff --git a/src/utils/getUserNotifications.js b/src/utils/getUserNotifications.js index 69d2431..1b93395 100644 --- a/src/utils/getUserNotifications.js +++ b/src/utils/getUserNotifications.js @@ -1,7 +1,7 @@ export default async function getUserNotifications() { try { const response = await fetch( - 'http://localhost:8000/api/users/1/notifications', + 'http://localhost:80/api/users/1/notifications', { method: 'GET', credentials: 'include', diff --git a/src/utils/searchEvents.js b/src/utils/searchEvents.js new file mode 100644 index 0000000..db4a67f --- /dev/null +++ b/src/utils/searchEvents.js @@ -0,0 +1,22 @@ +export default async function searchEvents(query) { + try { + const encodedQuery = encodeURIComponent(query); + const response = await fetch( + `http://localhost:80/api/events/search?query=${encodedQuery}`, + { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + } + ); + const data = await response.json(); + return data.hits.map(hit => hit.document); + + } catch (err) { + console.error("Erreur :", err); + return null; + } +}