comment implémentez coté React l’api endpoint flask qui me permet d’obtenir le réseaux sociaux des membres groupes, c’est à dire comment faire en sorte d’utiliser les données des réseaux sociaux des groupes si s’en est et des artistes si c’est des artistes ? @app.route(‘/getSocialLinks/int:id’)
def getRS(id):
connexion_bd = ConnexionBD()
lienRS = LienRS_BD(connexion_bd)
print(“test”)
res = lienRS.get_liensRS_membre_json(id)
print(res)
if res is None:
return jsonify({“error”: “Aucun artiste trouve”})
else:
return res

@app.route(‘/getMemberSocialLinks/<int:idMG>’)
def get_member_social_links(idMG):
connexion_bd = ConnexionBD()
lienRS = LienRS_BD(connexion_bd)
res = lienRS.get_liensRS_membre_par_id_json(idMG)
if res is None:
return jsonify({“error”: “Aucun lien de réseau social trouvé pour le membre”})
else:
return res
import { motion } from ‘framer-motion’;
import {useState} from ‘react’

type Props = {
href : string;
type: “soundcloud” | “spotify” | “instagram” | “twitter” | “youtube” | “inconnu”;
}

export default function BoutonReseau(props: Props) {
const [isHovered, setIsHovered] = useState(false)
const btnVariants = {
default:{
backgroundColor:“#E45A3B00”,
transition:{
duration: 0.3,
ease:“easeOut”,
}
},
hover:{
backgroundColor:“#E45A3B”,
transition:{
duration: 0.3,
ease:“easeOut”,
}
}
}

const svgVariants = {
default:{
fill:“#E45A3B”,
transition:{
duration:0.3,
ease: [1, 0, 0,1]
}
},
hover:{
fill:“#FFFBEE”,
transition:{
duration:0.3,
ease: [1, 0, 0,1]
}
}
}

return (
<motion.a href={props.href} className=‘btn-reseau’
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
variants={btnVariants}
initial=“default”
animate={isHovered ? “hover” : “default”}
>

{
props.type === “soundcloud” ? (
<svg width=“51” height=“22” viewBox=“0 0 51 22” fill=“none” xmlns=“http://www.w3.org/2000/svg”>
<motion.path d=“M24.0742 3.20787V21.779H44.0157C48.2271 21.4821 50.1166 18.878 50.1166 15.68C50.1166 12.2993 47.567 9.581 44.1523 9.581C43.2873 9.581 42.5588 9.76374 41.8076 10.0835C41.2612 4.73833 36.7312 0.558105 31.1539 0.558105C28.4905 0.558105 25.9637 1.56319 24.0742 3.20787ZM22.071 5.53783C21.388 5.12666 20.6596 4.80686 19.8628 4.64696V21.779H23.0271V4.28148C22.6856 4.64696 22.3669 5.10382 22.071 5.53783ZM16.7214 4.30432V21.779H18.8157V4.37285C18.3832 4.30432 17.9506 4.28148 17.4954 4.28148C17.2222 4.28148 16.9718 4.28148 16.7214 4.30432ZM12.5555 5.7891V21.779H14.6271V4.73833C13.8758 4.9896 13.1701 5.35509 12.5555 5.7891ZM8.75387 11.4998C8.61729 11.4998 8.4807 11.3627 8.32135 11.2942V21.779H10.4157V7.75358C9.57339 8.85003 9.00428 10.1521 8.75387 11.4998ZM4.10996 10.8602V21.5735C4.58801 21.7105 5.13435 21.779 5.72622 21.779H6.22704V10.6775C6.04492 10.6546 5.86281 10.6318 5.72622 10.6318C5.13435 10.6318 4.58801 10.7231 4.10996 10.8602ZM0.0351562 16.2054C0.0351562 17.9186 0.809142 19.4262 2.01565 20.4542V11.9795C0.809142 12.9846 0.0351562 14.515 0.0351562 16.2054Z”
variants={svgVariants}
initial=“default”
animate={isHovered ? “hover” : “default”}
/>
</svg>
)
: props.type === “spotify” ? (
<svg width=“40” height=“40” viewBox=“0 0 40 40” fill=“none” xmlns=“http://www.w3.org/2000/svg”>
<motion.path d=“M31.8 17.8C25.4 14 14.7 13.6 8.6 15.5C7.6 15.8 6.6 15.2 6.3 14.3C6 13.3 6.6 12.3 7.5 12C14.6 9.9 26.3 10.3 33.7 14.7C34.6 15.2 34.9 16.4 34.4 17.3C33.9 18 32.7 18.3 31.8 17.8ZM31.6 23.4C31.1 24.1 30.2 24.4 29.5 23.9C24.1 20.6 15.9 19.6 9.6 21.6C8.8 21.8 7.9 21.4 7.7 20.6C7.5 19.8 7.9 18.9 8.7 18.7C16 16.5 25 17.6 31.2 21.4C31.8 21.7 32.1 22.7 31.6 23.4ZM29.2 28.9C28.8 29.5 28.1 29.7 27.5 29.3C22.8 26.4 16.9 25.8 9.9 27.4C9.2 27.6 8.6 27.1 8.4 26.5C8.2 25.8 8.7 25.2 9.3 25C16.9 23.3 23.5 24 28.7 27.2C29.4 27.5 29.5 28.3 29.2 28.9ZM20 0C17.3736 0 14.7728 0.517315 12.3463 1.52241C9.91982 2.5275 7.71504 4.00069 5.85786 5.85786C2.10714 9.60859 0 14.6957 0 20C0 25.3043 2.10714 30.3914 5.85786 34.1421C7.71504 35.9993 9.91982 37.4725 12.3463 38.4776C14.7728 39.4827 17.3736 40 20 40C25.3043 40 30.3914 37.8929 34.1421 34.1421C37.8929 30.3914 40 25.3043 40 20C40 17.3736 39.4827 14.7728 38.4776 12.3463C37.4725 9.91982 35.9993 7.71504 34.1421 5.85786C32.285 4.00069 30.0802 2.5275 27.6537 1.52241C25.2272 0.517315 22.6264 0 20 0Z”
variants={svgVariants}
initial=“default”
animate={isHovered ? “hover” : “default”}
/>
</svg>
)
: props.type === “instagram” ? (
<svg width=“45” height=“45” viewBox=“0 0 45 45” fill=“none” xmlns=“http://www.w3.org/2000/svg”>
<motion.path d=“M13.0353 0H31.9141C39.106 0 44.9494 5.84342 44.9494 13.0353V31.9141C44.9494 35.3712 43.576 38.6868 41.1314 41.1314C38.6868 43.576 35.3712 44.9494 31.9141 44.9494H13.0353C5.84342 44.9494 0 39.106 0 31.9141V13.0353C0 9.57814 1.37336 6.26255 3.81796 3.81796C6.26255 1.37336 9.57814 0 13.0353 0ZM12.5858 4.49494C10.44 4.49494 8.38204 5.34737 6.8647 6.8647C5.34737 8.38204 4.49494 10.44 4.49494 12.5858V32.3635C4.49494 36.836 8.11336 40.4544 12.5858 40.4544H32.3635C34.5094 40.4544 36.5673 39.602 38.0847 38.0847C39.602 36.5673 40.4544 34.5094 40.4544 32.3635V12.5858C40.4544 8.11336 36.836 4.49494 32.3635 4.49494H12.5858ZM34.2739 7.86614C35.019 7.86614 35.7335 8.16212 36.2604 8.68898C36.7873 9.21583 37.0832 9.93039 37.0832 10.6755C37.0832 11.4206 36.7873 12.1351 36.2604 12.662C35.7335 13.1888 35.019 13.4848 34.2739 13.4848C33.5288 13.4848 32.8142 13.1888 32.2874 12.662C31.7605 12.1351 31.4646 11.4206 31.4646 10.6755C31.4646 9.93039 31.7605 9.21583 32.2874 8.68898C32.8142 8.16212 33.5288 7.86614 34.2739 7.86614ZM22.4747 11.2373C25.455 11.2373 28.3133 12.4213 30.4207 14.5287C32.5281 16.6361 33.712 19.4944 33.712 22.4747C33.712 25.455 32.5281 28.3133 30.4207 30.4207C28.3133 32.5281 25.455 33.712 22.4747 33.712C19.4944 33.712 16.6361 32.5281 14.5287 30.4207C12.4213 28.3133 11.2373 25.455 11.2373 22.4747C11.2373 19.4944 12.4213 16.6361 14.5287 14.5287C16.6361 12.4213 19.4944 11.2373 22.4747 11.2373ZM22.4747 15.7323C20.6865 15.7323 18.9715 16.4426 17.7071 17.7071C16.4426 18.9715 15.7323 20.6865 15.7323 22.4747C15.7323 24.2629 16.4426 25.9778 17.7071 27.2423C18.9715 28.5067 20.6865 29.2171 22.4747 29.2171C24.2629 29.2171 25.9778 28.5067 27.2423 27.2423C28.5067 25.9778 29.2171 24.2629 29.2171 22.4747C29.2171 20.6865 28.5067 18.9715 27.2423 17.7071C25.9778 16.4426 24.2629 15.7323 22.4747 15.7323Z”
variants={svgVariants}
initial=“default”
animate={isHovered ? “hover” : “default”}
/>
</svg>
)
: props.type === “twitter” ? (
<svg width=“49” height=“39” viewBox=“0 0 49 39” fill=“none” xmlns=“http://www.w3.org/2000/svg”>
<motion.path d=“M49 4.58824C47.1965 5.39118 45.2524 5.91882 43.238 6.17118C45.2992 4.95529 46.892 3.02824 47.6415 0.711176C45.6974 1.85824 43.5425 2.66118 41.2706 3.12C39.4202 1.14706 36.8203 0 33.869 0C28.3647 0 23.8676 4.40471 23.8676 9.84176C23.8676 10.6218 23.9613 11.3788 24.1252 12.09C15.7868 11.6771 8.36185 7.75412 3.41969 1.81235C2.55306 3.25765 2.06119 4.95529 2.06119 6.74471C2.06119 10.1629 3.81788 13.1912 6.53489 14.9118C4.87189 14.9118 3.326 14.4529 1.9675 13.7647V13.8335C1.9675 18.6053 5.43403 22.5971 10.0249 23.4918C8.55094 23.8868 7.00357 23.9418 5.5043 23.6524C6.14047 25.608 7.38639 27.3193 9.06691 28.5455C10.7474 29.7718 12.7781 30.4514 14.8733 30.4888C11.3216 33.2428 6.91902 34.7314 2.3891 34.71C1.59273 34.71 0.796367 34.6641 0 34.5724C4.45029 37.3712 9.74379 39 15.412 39C33.869 39 44.011 23.9965 44.011 10.9888C44.011 10.5529 44.011 10.14 43.9876 9.70412C45.9551 8.32765 47.6415 6.58412 49 4.58824Z”
variants={svgVariants}
initial=“default”
animate={isHovered ? “hover” : “default”}
/>
</svg>
)
: (
<svg width=“53” height=“37” viewBox=“0 0 53 37” fill=“none” xmlns=“http://www.w3.org/2000/svg”>
<motion.path d=“M52.9878 13.04C53.1069 9.6163 52.3532 6.21826 50.7969 3.16135C49.741 1.90703 48.2755 1.06056 46.6559 0.769439C39.9569 0.165541 33.2301 -0.0819771 26.5045 0.0279454C19.8034 -0.0869662 13.1009 0.152565 6.42537 0.74552C5.10558 0.984034 3.88422 1.59906 2.91031 2.51554C0.743496 4.50083 0.502739 7.89735 0.261982 10.7676C-0.0873274 15.9284 -0.0873274 21.1065 0.261982 26.2673C0.331634 27.8828 0.573748 29.4864 0.984253 31.0511C1.27455 32.2592 1.86187 33.3769 2.69363 34.3041C3.67416 35.2691 4.92397 35.9191 6.28091 36.1698C11.4714 36.8063 16.7014 37.0701 21.9301 36.9591C30.3566 37.0787 37.7479 36.9591 46.4873 36.2894C47.8776 36.0541 49.1626 35.4033 50.1709 34.4237C50.845 33.7537 51.3484 32.9338 51.6396 32.0318C52.5006 29.4069 52.9235 26.6598 52.8915 23.8993C52.9878 22.5598 52.9878 14.4751 52.9878 13.04ZM21.0634 25.3344V10.5285L35.3162 17.9673C31.3197 20.1679 26.0471 22.6555 21.0634 25.3344Z”
variants={svgVariants}
initial=“default”
animate={isHovered ? “hover” : “default”}
/>
</svg>
)
}
</motion.a>
)
}
import { useEffect, useState, useRef } from “react”;
import { Link, useLocation } from “react-router-dom”;
import BoutonReseau from “…/…/components/Artiste/BoutonReseau”;
import { motion } from “framer-motion”;
import axios from “axios”;

export default function PageEvenement() {
const location = useLocation();
const oldX = location.state?.oldX;
const oldY = location.state?.oldY;
const oldGroupes = location.state?.oldGroupes;
const [nomArtiste, setNomArtiste] = useState(location.state?.nomArtiste);
const [date, setDate] = useState(location.state?.date);
const [heure, setHeure] = useState(location.state?.heure);
const titleRef = useRef<HTMLHeadingElement>(null);
const [description, setDescription] = useState(
location.state?.description ||
“Description par défaut si aucune description n’est passée.”
);
const [socialLinks, setSocialLinks] = useState<{ reseau: string }[]>([]);
const params = new URLSearchParams(window.location.search);
const idArtiste = params.get(“id”);

const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const [infosGridPosition, setInfosGridPosition] = useState<“top” | “bottom”>(
“top”
);



function getSocialNetworkType(
url: string
):
| “soundcloud”
| “spotify”
| “instagram”
| “twitter”
| “youtube”
| “inconnu” {
if (url.includes(“soundcloud”)) {
return “soundcloud”;
} else if (url.includes(“spotify”)) {
return “spotify”;
} else if (url.includes(“instagram”)) {
return “instagram”;
} else if (url.includes(“twitter”)) {
return “twitter”;
} else if (url.includes(“youtube”)) {
return “youtube”;
} else {
return “inconnu”;
}
}

useEffect(() => {
axios
.get(“http://localhost:8080/getSocialLinks/” + idArtiste)
.then((response) => {
setSocialLinks(response.data);
console.log(response.data);
})
.catch((error) => {
console.error(
“Erreur lors de la récupération des liens de réseaux sociaux”,
error
);
});

const handleResize = () => {
setWindowWidth(window.innerWidth);
};

window.addEventListener(“resize”, handleResize);

return () => {
window.removeEventListener(“resize”, handleResize);
};
}, []);

useEffect(() => {
console.log(‘oldXEvenement:’, oldX);
console.log(‘oldYEvenement:’, oldY);
if (titleRef.current) {
// regarde si la width du titre est plus grande que 25% de la width de la fenetre - 2*3rem
if (titleRef.current.offsetWidth > windowWidth / 4 - 2 * 3 * 16) {
setInfosGridPosition(“top”);
} else {
setInfosGridPosition(“bottom”);
}
}
}, [titleRef, windowWidth]);

const infosVariants = {
initial: {
opacity: 0,
transition: {
duration: 0.1,
ease: “easeInOut”,
},
},
visible: {
opacity: 1,
transition: {
delay: 0.8,
duration: 0.6,
ease: “easeInOut”,
},
},
};

const overlayVariants = {
initial: {
background:
“linear-gradient(to top, rgba(0, 0, 0, 0.7) 20%, rgba(0, 0, 0, 0) 60%)”,
transition: {
duration: 0.1,
ease: “easeInOut”,
},
},
extended: {
background:
“linear-gradient(to top, rgba(0, 0, 0, 0.7) 40%, rgba(0, 0, 0, 0) 100%)”,
transition: {
delay: 0.6,
duration: 0.6,
ease: “easeInOut”,
},
},
};

return (
<div id=“PageArtiste”>
<motion.div
className=“overlay”
variants={overlayVariants}
initial=“initial”
animate={infosGridPosition === “top” ? “extended” : “initial”}
/>

<img
src={“http://localhost:8080/getImageArtiste/” + idArtiste}
alt=“image de fond”
/>
<div
className=“content”
style={{
columnGap: infosGridPosition === “top” ? “0” : “5rem”,
rowGap: windowWidth > 991 ? “0” : “5rem”,
}}
>
<h3 ref={titleRef}>
{nomArtiste
.toUpperCase()
.split(" “)
.map((mot: string, index: number) => {
return (
<span key={index}>
{mot}
<br />
</span>
);
})}
</h3>
<motion.div
className=“infos”
variants={infosVariants}
initial=“initial”
animate=“visible”
exit=“initial”
style={{ gridArea: infosGridPosition }}
>
<p className=“description”>{description}</p>
<div className=“les-reseaux”>
{socialLinks.map((link, index) => {
const type = getSocialNetworkType(link.reseau);
if (type) {
return (
<BoutonReseau key={index} href={link.reseau} type={type} />
);
}
return null;
})}
</div>
<Link
to=”/programmation"
state={{
comesFromPageArtist: idArtiste,
oldX: oldX,
oldY: oldY,
oldGroupes: oldGroupes,
}}
className=“btn-retour”
>
<svg
width=“36”
height=“28”
viewBox=“0 0 36 28”
fill=“none”
xmlns=“http://www.w3.org/2000/svg”
>
<rect
x=“6.52539”
y=“0.321533”
width=“35.8974”
height=“3.58974”
rx=“1.79487”
transform=“rotate(45 6.52539 0.321533)”
/>
<rect
x=“3.87891”
y=“25.5957”
width=“35.8974”
height=“3.58974”
rx=“1.79487”
transform=“rotate(-45 3.87891 25.5957)”
/>
</svg>
</Link>
</motion.div>
<div className=“date-heure”>
<h4>{date}</h4>
<h4>{heure}</h4>
</div>
</div>
</div>
);
}
import { useEffect, useState, useRef, useLayoutEffect } from “react”;
import SearchBar from “…/…/components/form/SearchBar”;
import Combo from “…/…/components/form/Combo”;
import CarteArtiste from “…/…/components/Artiste/CarteProgrammation”;
import { motion } from “framer-motion”;
import { useLocation } from “react-router-dom”;
import axios from “axios”;
import Footer from “…/…/components/footer”;
import CarteProgrammation from “…/…/components/Artiste/CarteProgrammation”;
import { AnimatePresence } from “framer-motion”;


type Props = {
isNavInFocus: boolean;
setIsNavTransparent: (isNavTransparent: boolean) => void;
};

type Groupe = {
idG: number;
nomG: string;
descriptionG: string;
datePassage: string;
heurePassage: string;
};

type Artiste = {
descriptionA?: string;
idMG?: number;
idG: number;
nomDeSceneMG: string;
nomMG: string;
prenomMG?: string;
datePassage?: string;
heurePassage?: string;
};

type Evenement = {
dateDebutE: string;
dateFinE: string;
heureDebutE: string;
heureFinE: string;
idE: number;
idG: number;
idL: number | null;
nomE: string;
};

type Programme = Groupe | Evenement | Artiste;

export default function Programmation(props: Props) {
const location = useLocation();
const idArtistComingFrom = location.state?.comesFromPageArtist;
const oldX = location.state?.oldX;
const oldY = location.state?.oldY;
const oldGroupes = location.state?.oldGroupes;
const [searchTerm, setSearchTerm] = useState(“”);


window.history.replaceState({}, document.title);
const [lesGroupes, setLesGroupes] = useState<Groupe[]>(location.state ? oldGroupes : []);
const [lesArtistes, setLesArtistes] = useState<Artiste[]>([]);
const groupePassageMap = useRef<
Map<number, { datePassage: string; heurePassage: string }>
>(new Map());

useEffect(() => {
axios.get(“http://localhost:8080/getGroupesWithEvenements”).then((res) => {
const groupedData = res.data as Programme[][];
// console.log(“groupedData”)
// console.log(groupedData);

const listeGroupes: Groupe[] = [];
const groupSet = new Set<number>();
const listeArtistes: Artiste[] = [];

groupedData.forEach((groupArray) => {
let groupeObj: Partial<Groupe> = {};
let artisteObj: Partial<Artiste> = {};

groupArray.forEach((item) => {
if (“nomG” in item) {
groupeObj = { …groupeObj, …item };
} else if (“nomDeSceneMG” in item) {
artisteObj = { …artisteObj, …item };
} else if (“dateDebutE” in item) {
const datePassage = item.dateDebutE;
const heurePassage = item.heureDebutE;
if (groupeObj.idG === item.idG) {
groupeObj.datePassage = datePassage;
groupeObj.heurePassage = heurePassage;
}
if (artisteObj.idG === item.idG) {
artisteObj.datePassage = datePassage;
artisteObj.heurePassage = heurePassage;
}
}
});

// si l’id n’a pas encore était ajoutée à la liste on ajoute le groupe
if (groupeObj.idG !== undefined && !groupSet.has(groupeObj.idG)) {
listeGroupes.push(groupeObj as Groupe);
groupSet.add(groupeObj.idG);
}
if (artisteObj.nomDeSceneMG !== undefined) {
listeArtistes.push(artisteObj as Artiste);
}
});

setLesGroupes(listeGroupes);
// console.log(listeGroupes);
groupePassageMap.current.clear();
listeGroupes.forEach((groupe) => {
groupePassageMap.current.set(groupe.idG, {
datePassage: groupe.datePassage,
heurePassage: groupe.heurePassage,
});
});

setLesArtistes(listeArtistes);
});
}, []);

// let filteredGroupes = lesGroupes;
// if (searchTerm) {
// filteredGroupes = lesGroupes.filter((groupe) =>
// groupe.nomG.toLowerCase().includes(searchTerm.toLowerCase())
// );
// }
// let filteredArtistes = lesArtistes;
// if (searchTerm) {
// filteredArtistes = lesArtistes.filter((artiste) =>
// artiste.nomDeSceneMG.toLowerCase().includes(searchTerm.toLowerCase())
// )
// }

const [filtreDate, setFiltreDate] = useState(“Tout”);
console.log(filtreDate)
let termeRechercher = ‘Tout’;

if (filtreDate === ‘21 Juillet’) {
termeRechercher = ‘07-21’;
}
else if (filtreDate === ‘22 Juillet’) {
termeRechercher = ‘07-22’;
}
else if (filtreDate === ‘23 Juillet’) {
termeRechercher = ‘07-23’;
}


let filteredGroupes = lesGroupes;
if (searchTerm) {
filteredGroupes = filteredGroupes.filter((groupe) => {
console.log(groupe.nomG);
if (searchTerm.length > 5) {
return groupe.nomG.toLowerCase().includes(searchTerm.toLowerCase());
} else {
return groupe.nomG.toLowerCase().startsWith(searchTerm.toLowerCase());
}
});
}

if (filtreDate !== ‘Tout’) {
console.log(termeRechercher)
filteredGroupes = filteredGroupes.filter((groupe) => {
return groupe.datePassage.includes(termeRechercher);
});
}

let filteredArtistes = lesArtistes;
console.log(“Les Artistes avant filtre:”, lesArtistes);
if (searchTerm) {
console.log(“Filtered Artistes après searchTerm:”, filteredArtistes);
filteredArtistes = filteredArtistes.filter((artiste) => {
if (searchTerm.length > 5) {
return artiste.nomDeSceneMG.toLowerCase().includes(searchTerm.toLowerCase());
} else {
return artiste.nomDeSceneMG.toLowerCase().startsWith(searchTerm.toLowerCase());
}
});
}
if (filtreDate !== ‘Tout’) {
filteredArtistes = filteredArtistes.filter((artiste) => {
console.log(“Filtered Artistes après searchTerm:”, filteredArtistes);
const groupeInfo = groupePassageMap.current.get(artiste.idG);
return groupeInfo?.datePassage.includes(termeRechercher);
});
}
console.log(filteredGroupes);
console.log(filteredArtistes);
const [filtreAffichage, setFiltreAffichage] = useState(“Grille”);
const [filtreGenre, setFiltreGenre] = useState(“Tout”);

const pageRef = useRef<HTMLDivElement>(null);

const contentVariants = {
visible: {
filter: “blur(0px)”,
scale: 1,
zIndex: 1,
transition: {
duration: 0.5,
ease: [1, 0, 0, 1],
},
},
hidden: {
filter: “blur(10px)”,
scale: 0.8,
zIndex: -1,
transition: {
duration: 0.5,
ease: [1, 0, 0, 1],
},
},
};

useEffect(() => {
window.scrollTo(0, 0);
props.setIsNavTransparent(false);
}, []);

return (
<>
<motion.div
id=“Programmation”
className=“page-defaut”
variants={contentVariants}
animate={props.isNavInFocus ? “hidden” : “visible”}
ref={pageRef}
>
<header>
<div className=“title”>
<h2>PROGRAMMATION</h2>
<svg
width=“64”
height=“64”
viewBox=“0 0 64 64”
fill=“none”
xmlns=“http://www.w3.org/2000/svg”
>
<path
d=“M62.9991 27.739L42.1815 27.7675L56.8787 13.0286L50.7001 6.86056L36.0029 21.5994L35.9744 0.785744L27.2406 0.797718L27.2692 21.6114L12.5316 6.91288L6.36413 13.0979L21.1017 27.7964L0.289932 27.825L0.301899 36.5537L21.1137 36.5251L6.41646 51.2641L12.6009 57.4321L27.2981 42.6932L27.3266 63.5069L36.0603 63.4949L36.0318 42.6812L50.7694 57.3798L56.931 51.1948L42.1934 36.4962L63.011 36.4677L62.9991 27.739Z”
fill=“#FFD600”
/>
</svg>
</div>
<div className=“filters-container”>
<div className=“filters”>
<Combo
title=“DATE”
choices={[“Tout”, “21 Juillet”, “22 Juillet”, “23 Juillet”]}
currentChoice={filtreDate}
setCurrentChoice={setFiltreDate}
/>
<Combo
title=“AFFICHAGE”
choices={[“Grille”, “Horaires”]}
currentChoice={filtreAffichage}
setCurrentChoice={setFiltreAffichage}
/>
<Combo
title=“GENRE”
choices={[“Tout”, “Rap”, “Rock”, “Pop”]}
currentChoice={filtreGenre}
setCurrentChoice={setFiltreGenre}
/>
</div>
<SearchBar text=“Rechercher un artiste” onSearch={setSearchTerm} />
</div>
</header>
<main className=“liste-artistes”>
<AnimatePresence>
{filteredGroupes.map((groupe) => {
console.log(‘idArtistComingFrom for groupe:’, idArtistComingFrom);
console.log(“groupe.idG : “);
console.log(groupe.idG);
return (
<CarteProgrammation
key={groupe.idG}
id={groupe.idG}
nomArtiste={groupe.nomG}
description={groupe.descriptionG}
date={groupe.datePassage}
heure={groupe.heurePassage}
setIsNavTransparent={props.setIsNavTransparent}
oldGroupes={lesGroupes}
oldX={idArtistComingFrom == groupe.idG ? oldX : null} oldY={idArtistComingFrom == groupe.idG ? oldY : null} comesFromPageArtist={idArtistComingFrom == groupe.idG}
/>
);
})}

{filteredArtistes.map((artiste) => {
console.log(‘idArtistComingFrom for artiste:’, idArtistComingFrom);
const groupeInfo = groupePassageMap.current.get(artiste.idG);
console.log(“artiste.idMG : “);
console.log(artiste.idMG);
return (
<CarteProgrammation

key={artiste.idMG}
id={artiste.idMG}
nomArtiste={artiste.nomDeSceneMG}
description={artiste.descriptionA}
date={groupeInfo?.datePassage ?? “Date inconnue”}
heure={groupeInfo?.heurePassage ?? “Heure inconnue”}
setIsNavTransparent={props.setIsNavTransparent}

oldGroupes={lesGroupes}

oldX={idArtistComingFrom == artiste.idMG ? oldX : null} oldY={idArtistComingFrom == artiste.idMG ? oldY : null} comesFromPageArtist={idArtistComingFrom == artiste.idMG}

/>
);
})}
</AnimatePresence>
</main>
</motion.div>
<Footer />
</>
);
}
import { TargetAndTransition, motion } from “framer-motion”;
import { useContext, useState } from “react”;
import Button from “…/components/form/Button”;
import { getCookie, setCookie } from “…/cookies/CookiesLib.tsx”;
import { CartContext } from “…/App”;

const initialDays = {
“20 Jui”: false,
“21 Jui”: false,
“22 Jui”: false,
};

type Props = {
id: number;
title: string;
price: number | string;
nbTicket: number;
isForfait?: boolean;
disabled?: boolean;
};

export default function TicketCard({
id,
title,
price,
nbTicket,
isForfait,
}: Props) {
const [isOpen, setIsOpen] = useState(false);
const [tickets, setTickets] = useState(nbTicket);
const [rotation, setRotation] = useState(0);
const [days, setDays] = useState(initialDays);
const { cart, setCart } = useContext(CartContext);
const [isLoading, setIsLoading] = useState(false);
const [isAdded, setIsAdded] = useState(false);
type DayKey = “20 Jui” |“21 Jui”|“22 Jui”;
const pricesByDay:{[key in DayKey]: number} = {
“20 Jui”: 60,
“21 Jui”: 80,
“22 Jui”: 90,
};


const selectedDayCount = () => {
return Object.values(days).filter(Boolean).length;
}
const isDisabled = tickets === 0 || (isForfait && selectedDayCount() !== 2);

const handleTicketChange = (newTickets: number, event: React.MouseEvent) => {
event.stopPropagation();
setTickets(newTickets);
}


const addToCartHandler = async (
event: React.MouseEvent<HTMLButtonElement>
) => {
event.stopPropagation();
setIsLoading(true);

// Longueur actuelle du panier dans les cookies
const selectedDaysSortedString = isForfait
? Object.entries(days)
.filter(([_, isChosen]) => isChosen)
.map(([day, ]) => day)
.sort()
.join(‘-’)
: null;

const calculPrixSelonJourSelec = isForfait && selectedDaysSortedString
? selectedDaysSortedString.split(‘-’).reduce((total, dayKey) => {
return total + pricesByDay[dayKey as DayKey];
}, 0)
: (typeof price === “number” ? price : 0);

const uniqueId = isForfait && selectedDaysSortedString
? ${id}-${selectedDaysSortedString}
: ${id};

const itemForCart = {
id,
uniqueId,
title,
price: isForfait ? calculPrixSelonJourSelec : (typeof price === “number” ? price : 0),
quantity: tickets,
selectedDaysSortedString: isForfait ? selectedDaysSortedString : null,
};


// const currentCartLength = (getCookie(“cart”) || []).length;
// condition non valide car si on ajoute un typê billet déjà présent dans le panier,
// l’ajout des nouveaux billets ne ferons que changer la valeur de quantité dans le tableau et
// donc la longueur du tableau ne changera pas
// Initialise la vérification de l’ajout
let expectedNewQuantity = itemForCart.quantity;
let newCart = […cart];


const forfaitIndex = newCart.findIndex((billet) => billet.uniqueId === uniqueId);

// si l’article existait déjà dans le panie on garde la quantité précédente et on détermine la nouvelle quantité attendue
if (forfaitIndex > -1) {
// si ce type de billet existe déjà, ajoute à la quantité et calcule la nouvelle quantité attendue
expectedNewQuantity += newCart[forfaitIndex].quantity;
newCart[forfaitIndex].quantity = expectedNewQuantity;
} else {
// sinon on l’ajoute au panier
newCart.push(itemForCart);
}
setCart(newCart);
setCookie(“cart”, newCart, { expires: 7, sameSite: “None”, secure: true });

setTimeout(() => {
// trouver l’article après la mise à jour pour vérifier que la quantité est correcte
const newForfaitIndex = newCart.findIndex((billet) => billet.uniqueId === uniqueId);
const hasCorrectQuantity = newForfaitIndex > -1
? newCart[newForfaitIndex].quantity === expectedNewQuantity
: false;

if (hasCorrectQuantity) {
setIsAdded(true);
} else {
console.error(‘Erreur : la quantité dans le panier ne correspond pas à la quantité attendue.’);
}

setIsLoading(false);

// Afficher le message ‘ARTICLE AJOUTÉ’ pendant un certain temps
setTimeout(() => {
setIsAdded(false);
}, 2000);
}, 400);
};


const displayDay = (day: string) => day.replace(“Juillet”, “Juillet”);

const buttonVariants: { [key: string]: TargetAndTransition } = {
tap: { scale: 0.95 },
selected: { scale: 1.1, backgroundColor: “#E45A3B” },
unselected: { scale: 1, backgroundColor: “#FFFFFF” },
};

const contentVariants = {
closed: {
opacity: 0,
height: 0,
overflow: “hidden”,
transition: {
duration: 0.2,
ease: “easeInOut”,
when: “afterChildren”,
},
},
open: {
opacity: 1,
height: title === “Forfait 2 jours” ? 160 : 150,
transition: {
duration: 0.2,
ease: “easeInOut”,
when: “beforeChildren”,
},
},
};

const cardVariants = {
hidden: { opacity: 0, y: 50 },
visible: {
opacity: 1,
y: 0,
transition: {
type: “spring”,
stiffness: 120,
},
},
};

const calculateSubTotal = () => {
if (isForfait) {
// Calculer le prix total en fonction des jours sélectionnés
const totalSelectedDaysPrice = Object.entries(days)
.filter(([, isSelected]) => isSelected)
.reduce((total, [day]) => {
return total + pricesByDay[day as keyof typeof pricesByDay];
}, 0);

return tickets * totalSelectedDaysPrice;
} else {
return tickets * (typeof price === ‘number’ ? price : parseInt(price, 10));
}
};

const maxSelectedDays = 2;

const toggleDaySelection = (day: keyof typeof initialDays) => {
setDays((prevDays) => {
const isSelected = prevDays[day];
const count = selectedDayCount();

if (!isSelected && count >= maxSelectedDays) {
return prevDays;
}

return { …prevDays, [day]: !prevDays[day] };
});
};

return (
<motion.div
className=“ticket-card”
layout
initial=“hidden”
animate=“visible”
variants={cardVariants}
onClick={() => {
setIsOpen(!isOpen);
setRotation(rotation === 0 ? 90 : 0);
}}
>
<div className=“content”>
<div className=“left-part”>
<h4>{title}</h4>
<p>Les tickets ne sont pas remboursables.</p>
<p>Dernière entrée à 11H.</p>
</div>
<div className=“right-part”>
<p>{price}€</p>
<motion.div className=“svg-container” animate={{ rotate: rotation }}>
<svg
xmlns=“http://www.w3.org/2000/svg”
width=“13”
height=“20”
viewBox=“0 0 13 20”
fill=“none”
>
<path d=“M2 18L10 10L2 2” stroke=”#4E4E4E” strokeWidth=“4” />
</svg>
</motion.div>
</div>
</div>
<motion.div
className={sub-menu ${<br/> title === "Forfait 2 jours" ? "forfait-2j" : ""<br/> }}
variants={contentVariants}
initial=“closed”
animate={isOpen ? “open” : “closed”}
exit=“closed”
>
<div className=“top-partsubmenu”>
<div className=“left-part-sub”>
<div className=“sub-menu-left-part”>
<div className=“rect”>
<img
className=””
src=“images/billet_pass1j.png”
alt=“Billet pass 1 jour”
/>
</div>
<div className=“container”></div>
<div className=“article-select”>
<svg
xmlns=“http://www.w3.org/2000/svg”
width=“22”
height=“21”
viewBox=“0 0 22 21”
fill=“none”
>
<path
d=“M22 9.03848H14.6966L19.8599 4.10947L17.6953 2.04109L12.532 6.97007V0H9.46799V6.97007L4.30475 2.04109L2.13807 4.10947L7.30131 9.03848H0V11.9615H7.30131L2.13807 16.8906L4.30475 18.9589L9.46799 14.0299V21H12.532V14.0299L17.6953 18.9589L19.8599 16.8906L14.6966 11.9615H22V9.03848Z”
fill=“#FFD600”
/>
</svg>
<p>x{tickets} Article(s) sélectionné(s)</p>
</div>

</div>

<div className=“ticket-control”>
<button
className=“minusButton”
onClick={(event) =>
handleTicketChange(Math.max(tickets - 1, 0), event)
}
>
-
</button>
<span>{tickets}</span>
<button
className=“sommeButton”
onClick={(event) => handleTicketChange(tickets + 1, event)}
>
+
</button>
</div>
</div>
</div>
<div className=“delimiter-submenu”></div>
<div className=“bottom-partsubmenu”>
<div className=“bottom-part-left”>
<div className=“day-checkbox-container”>
{isForfait &&
(Object.keys(days) as Array<keyof typeof initialDays>).map(
(day) => (
<motion.label
key={day}
className=“day-checkbox”
whileTap=“tap”
>
<input
type=“checkbox”
checked={days[day]}
onChange={() => toggleDaySelection(day)}
style={{ display: “none” }}
/>
<motion.div
className=“day-button”
variants={buttonVariants}
animate={days[day] ? “selected” : “unselected”}
>
{displayDay(day)}
</motion.div>
</motion.label>
)
)}
</div>
<p>Sous-total</p>
<p>{calculateSubTotal().toFixed(2)}€</p>
</div>
<Button
text={
isLoading
? “Chargement…”
: isAdded
? “ARTICLE AJOUTÉ”
: “AJOUT PANIER”
}
isLoading={isLoading}
onClick={(event: React.MouseEvent<HTMLElement>) =>
addToCartHandler(event as React.MouseEvent<HTMLButtonElement>)
}
isDisabled={isDisabled}
/>
</div>
</motion.div>
</motion.div>
);
}