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}
-
{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 (
+
+
+
+
+ {searchResults.length > 0 ? (
+
+ ) : null}
+
+
+
+ {searchResults.map((result, i) => (
+
+
+
+ {result.image == null ? (
+

+ ) : (
+

+ )}
+
+
{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;
+ }
+}