diff --git a/README.md b/README.md new file mode 100644 index 0000000..41d430d --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# CinéDuCoin : Application web requêtes API + +## 30/07/25 +Limiter l'affichage du loader à une seule fois, même si l'utilisateur appuie plusieurs fois sur recherche\ +Faire marcher des liens externes vers d'autres sites\ +Améliorer la mise en page d'informationsPage\ +Mise en page du graphique avec de nouvelles couleurs et taille de texte +## 29/07/25 +Création du logo et implémentation de celui-ci dans la page d'accueil +Ajout d'une pie chart en doughnut pour l'es parts de marché des cinémas (français, américains, européens, autres)\ +## 28/07/25 +Réparer la map de la page InformationsPage qui ne se charge plus\ +Réparer la page InformationsPage qui ne se charge pas quand on est revenu une fois à la page précédente\ +Calculer les distances entre l'adresse saisie et chaque cinéma (l'afficher sur la liste des cinémas)\ +Aumentation du nombre de données extraites de l'API pour la page InformationsPage +## 25/07/25 +Création d'une ébauche de style en CSS\ +Affichage des cinémas dans l'ordre de distance, le plus proche en premier +## 24/07/25 +Création des boutons de chaque cinéma dans le périmètre\ +Programmation du comportement des boutons pour qu'ils affichent les informations du cinéma séléctionné\ +Refactorisation du code pour séparer les fonctions, division de la fonction searchEngine en quatres fonctions distinctes\ +Mise en place du display: none/block permettant de naviguer fenêtre après fenêtre\ +Mise en place de l'auto-complétion des champs de recherches\ +Ajout du bouton précédent pour revenir en arrière\ +Mise en place de l'écran de chargement avant l'arrivée des résultats +## 23/07/25 +Mise en commun des travaux distincts sur les différentes pages de l'application\ +Résolution de la mise en lien entre les deux API, dans une fonction searchEngine (avec l'aide de Vi)\ +Mise en lien de la fonction searchEngine au DOM pour l'utiliser dans le navigateur\ +Mise en fonction du slider qui permet à l'utilisateur de déterminer le rayon de sa recherche +## 22/07/25 +Création des fonctions de base de notre application\ +Création de trois différentes pages : searchPage, resultsPage et informationsPage\ +Recherches pour régler un soucis d'envoyer des paramètres SQL dans l'API cinéma +## 21/07/25 +Choix du thème du projet\ +Recherche des APIs publiques et gratuites : https://geoservices.ign.fr/ et https://data.culture.gouv.fr/explore/dataset/etablissements-cinematographiques/. La première transforme une adresse rédigée par un utilisateur en coordonées géographiques. La deuxième indique les cinémas de France, et peut admettre un paramètre de localisation par coordonnées géographiques.\ +Identification des objectifs du projet et création du répertoire (fichiers html, js et css) \ No newline at end of file diff --git a/images/LOGO FINAL.svg b/images/LOGO FINAL.svg new file mode 100644 index 0000000..76319a0 --- /dev/null +++ b/images/LOGO FINAL.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/video.mp4 b/images/video.mp4 new file mode 100644 index 0000000..e9577d8 Binary files /dev/null and b/images/video.mp4 differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..2a877e1 --- /dev/null +++ b/index.html @@ -0,0 +1,49 @@ + + + + + + + CinéDuCoin + + + + + + + + +
+
+ +

Trouvez des cinémas autour de vous

+
+ +
+ + 3km +
+ +
+
+
+
+

+ +
+ +
+
+ + + +
+ + + \ No newline at end of file diff --git a/js/loader.js b/js/loader.js new file mode 100644 index 0000000..3297b2f --- /dev/null +++ b/js/loader.js @@ -0,0 +1,9 @@ +const loader = document.getElementById("loader"); + +export function displayLoader() { + + loader.innerHTML = ""; + const loading = document.createElement("p"); + loading.innerText = "Chargement des cinémas à proximité…"; + loader.appendChild(loading); +} \ No newline at end of file diff --git a/js/script.js b/js/script.js new file mode 100644 index 0000000..633fdeb --- /dev/null +++ b/js/script.js @@ -0,0 +1,223 @@ +import { displayLoader } from "./loader.js"; + +const addressInput = document.getElementById("addressInput"); +const suggestion = document.getElementById("suggestion"); +const userSearchRadius = document.getElementById("userSearchRadius"); +const form = document.getElementById("submissionForm"); +const cinemaList = document.getElementById("cinemaList"); +const resultAddress = document.getElementById("resultAddress"); +const searchPage = document.getElementById("searchPage"); +const resultPage = document.getElementById("resultPage"); +const informationsPage = document.getElementById("informationsPage"); +const previousButton = document.getElementById("previousButton"); +const loader = document.getElementById("loader"); +let currentPage = 1; + +form.addEventListener("submit", async (event) => { + event.preventDefault(); + cinemaList.innerHTML = ""; + informationsPage.innerHTML = ""; + const address = addressInput.value; + const currentRadius = userSearchRadius.value; + + const coordinates = await getCoordinates(address); + if (!coordinates) { + resultAddress.innerText = "Aucune coordonnée trouvée pour cette adresse."; + return; + } + + const { userLongitude, userLatitude, label } = coordinates; + resultAddress.innerText = `Cinémas trouvés à proximité de ${label} :`; + + const cinemas = await getCinema(userLongitude, userLatitude, currentRadius); + if (cinemas.length === 0) { + resultAddress.innerText = `Aucun cinéma trouvé à proximité de ${label} dans un rayon de ${currentRadius} km.`; + return; + } + + displayCinema(cinemas, userLongitude, userLatitude); +}); + +async function getCoordinates(address) { + try { + const responseCoord = await fetch(`https://data.geopf.fr/geocodage/search?q=${address.trim()}`); + if (!responseCoord.ok) throw new Error("Erreur lors de la récupération") + const dataCoord = await responseCoord.json(); + + if (!dataCoord.features || dataCoord.features.length === 0) { + return null; + } + + const coord = dataCoord.features[0].geometry.coordinates; + const userLongitude = coord[0]; + const userLatitude = coord[1]; + const label = dataCoord.features[0].properties.label; + + return { userLongitude, userLatitude, label }; + } catch (error) { + console.error("Erreur :", error); + } finally { + displayLoader(); + }; +}; + +addressInput.addEventListener("input", async function () { + const inputAdded = addressInput.value.trim(); + + if (inputAdded.length < 3) { + suggestion.innerHTML = ""; + return; + } + + try { + const response = await fetch(`https://data.geopf.fr/geocodage/search?index=address&q=${encodeURIComponent(inputAdded)}&limit=5`); + if (!response.ok) throw new Error("Erreur lors de la récupération"); + const data = await response.json(); + + suggestion.innerHTML = ""; + + if (!data.features || data.features.length === 0) { + suggestion.innerHTML = "
Aucun résultat
"; + return; + } + + data.features.forEach(feature => { + const newDiv = document.createElement("div"); + newDiv.textContent = feature.properties.label; + newDiv.style.cursor = "pointer"; + newDiv.addEventListener("click", () => { + addressInput.value = feature.properties.label; + suggestion.innerHTML = ""; + }); + suggestion.appendChild(newDiv); + }); + } catch (error) { + console.error("Erreur :", error); + suggestion.innerHTML = "
Erreur lors du chargement des suggestions
"; + } +}); + +async function getCinema(userLongitude, userLatitude, userSearchRadius) { + try { + const responseCinema = await fetch( + `https://data.culture.gouv.fr/api/explore/v2.1/catalog/datasets/etablissements-cinematographiques/records?where=(distance(%60geolocalisation%60%2C%20geom%27POINT(${userLongitude}%20${userLatitude})%27%2C%20${userSearchRadius}km))&order_by=distance(%60geolocalisation%60%2C%20geom%27POINT(${userLongitude}%20${userLatitude})%27)%20ASC&limit=20` + ); + if (!responseCinema.ok) throw new Error("Erreur lors de la récupération"); + const dataCinema = await responseCinema.json(); + return dataCinema.results; + } catch (error) { + console.error("Erreur :", error); + }; +}; + +function getDistanceFromCoord(lat1, lon1, lat2, lon2) { + const earthRadius = 6371; + const dLat = (lat2 - lat1) * Math.PI / 180; + const dLon = (lon2 - lon1) * Math.PI / 180; + const haversine = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(lat1 * Math.PI / 180) * + Math.cos(lat2 * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const triangulationFactor = 2 * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)); + return earthRadius * triangulationFactor; +} + +// à partir d'ici, ce sont les fonctions d'affichage + +function displayCinema(cinemas, userLatitude, userLongitude) { + currentPage = 2; + loader.innerHTML = ""; + searchPage.style.display = "none"; + loader.style.display = "none"; + informationsPage.style.display = "none"; + resultPage.style.display = "block"; + previousButton.style.display = "block"; + + + for (const item of cinemas) { + const button = document.createElement("button"); + button.className = "cinemaButton"; + button.innerHTML += `${item.nom}
${item.adresse}, ${item.commune}
${getDistanceFromCoord(item.longitude, item.latitude, userLatitude, userLongitude).toFixed(2)} km`; + button.addEventListener("click", () => { + showCinemaInformations(item, getDistanceFromCoord(item.longitude, item.latitude, userLatitude, userLongitude).toFixed(2)); + }); + + cinemaList.appendChild(button); + }; +}; + +function showCinemaInformations(cinema, distanceInKms) { + currentPage = 3; + resultPage.style.display = "none"; + informationsPage.style.display = "block"; + + informationsPage.innerHTML = ` +

${cinema.nom}

+
+

+
+

Adresse : ${cinema.adresse}, ${cinema.commune}

+

À ${distanceInKms} kilomètres de votre adresse

+

Nombre d'écrans : ${cinema.ecrans}

+

Nombre de fauteuils : ${cinema.fauteuils}

+

Nombre de films par semaine : ${cinema.nombre_de_films_en_semaine_1}

+

Site du cinéma

+

Rechercher ce cinéma sur Google

+
+
+

+ `; + const pdmLabels = ["Films Français", "Films Américains", " Films Europeens", "Autres Films"]; + const pdmValues = [cinema.pdm_en_entrees_des_films_francais, + cinema.pdm_en_entrees_des_films_americains, + cinema.pdm_en_entrees_des_films_europeens, + cinema.pdm_en_entrees_des_autres_films]; + if (window.pdmChartInstance) window.pdmChartInstance.destroy() + const canvas = document.getElementById("myChart"); + window.pdmChartInstance = new Chart(canvas, { + type: 'doughnut', + data: { + labels: pdmLabels, + datasets: [{ + label: "Part de marché (%)", + data: pdmValues, + borderWidth: 1, + backgroundColor: [ + "#08528fff", "#C0392B", "#45B39D", "#F4D03F" + ] + }] + }, + options: { + responsive: false, + plugins: { + legend: { + labels: { + color: 'white', + font: { + size: 14 + } + } + } + } + + } +}); + +function toPreviousPage() { + previousButton.addEventListener("click", () => { + if (currentPage === 3) { + informationsPage.innerHTML = ""; + informationsPage.style.display = "none"; + resultPage.style.display = "block"; + currentPage = 2; + } else if (currentPage === 2) { + cinemaList.innerHTML = ""; + resultPage.style.display = "none"; + previousButton.style.display = "none"; + searchPage.style.display = "flex"; + currentPage = 1; + } + }); +}; +toPreviousPage();} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..5c5a943 --- /dev/null +++ b/style.css @@ -0,0 +1,294 @@ +body { + font-family: 'Segoe UI', Tahoma, Verdana, sans-serif; + background-color:#ffffff; + color: #fff; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +#pagesBackground { + max-width: 900px; + background-color: rgba(0, 0, 0, 0.8); + padding: 3.5rem; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + border-radius: 20px; +} + +#searchPage { + display: flex; + margin-top: 1rem; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + gap: 1.5rem; +} + +#welcomeMsg { + font-size: 2rem; + margin-bottom: 1rem; + color: #ffffff; + text-align: center; +} + +form { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + width: 100%; +} + +#addressInput { + width: 100%; + max-width: 600px; + /* largeur max pour les grands écrans */ + padding: 1rem 1.5rem; + /* plus de hauteur */ + border-radius: 25px; + border: none; + font-size: 1.2rem; + /* texte plus grand */ +} + +#suggestion { + background-color: none#000000; + color: #ffffff; + width: 80%; + max-height: 150px; + overflow-y: auto; + border-radius: 10px; + padding: 0.5rem; +} + +#suggestion div { + padding: 0.3rem 0.5rem; + cursor: pointer; +} + +#suggestion div:hover { + color: #f1c40f; +} + +/* Slider */ +input[type="range"] { + width: 300px; + height: 8px; + appearance: none; + background: #ffffff; + margin-right: 0.4rem; + margin-left: 0.5rem; + border-radius: 5px; + outline: none; + cursor: pointer; +} + +span { + font-size: 1.3rem; +} + +output { + font-weight: bold; + text-align: center; + font-size: 1.3rem; +} + +/* Bouton soumission */ +#submissionBtn { + padding: 0.6rem 1.2rem; + background-color: #f1c40f; + border: none; + color: rgb(0, 0, 0); + border-radius: 10px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s ease; +} + +#submissionBtn:hover { + background-color: #e8d176; + transition: background-color 0.3s ease; +} + +/* Résultats */ +#resultPage { + display: none; + margin-top: 1rem; +} + +#resultAddress { + font-size: 1.2rem; + margin-bottom: 1rem; + color: #ffffff; + text-align: center; +} + +#cinemaList { + list-style: none; + display: block; + padding-left: 0; +} + +.cinemaButton { + background: none; + border: none; + color: #fff; + font-size: 1rem; + text-align: left; + padding: 0.5rem 0; + width: 100%; + cursor: pointer; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + +} + +.cinemaButton:hover { + color: #f1c40f; + background: none; +} + +#informationsPage { + display: none; + margin-top: 1rem; +} + +#InformationsPageTitle { + font-size: 1.8rem; + margin-right: 2rem; + margin-bottom: 1rem; + color: #ffffff; + text-align: left; +} + +#informationsPage .content-wrapper { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-wrap: wrap; + margin-bottom: 1rem; +} + +#informationsPage .info-section { + flex: 1; + min-width: 250px; + padding-left: 1rem; + text-align: left; +} + +#informationsPage p { + margin: 0.5rem 0; + text-align: left; +} + +#informationsPage a { + color:#f1c40f; +} + +iframe { + margin-top: 1rem; + width: 100%; + max-width: 100%; + border-radius: 15px; + + aspect-ratio: 4 / 3; + height: auto; +} + +#myChart { + width: 350px; + height: 230px; + flex-shrink: 0; +} + +/* Bouton retour */ +#previousButton { + display: none; + margin-top: 2rem; + padding: 0.5rem 1rem; + background-color:#f1c40f; + color: black; + border: none; + border-radius: 10px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s ease; +} + +#previousButton:hover { + background-color: #e8d176; + transition: background-color 0.3s ease; +} + +/* Chargement */ +#loader p { + margin-top: 1rem; + font-style: italic; + color: #bdc3c7; + text-align: center; +} + +.video { + position: fixed; + top: 0; + left: 0; + min-width: 100%; + min-height: 100%; + z-index: -1; + object-fit: cover; +} + +#logo { + max-width: 100%; + width: 400px; + height: auto; + display: block; + margin: 0 auto 1rem; + align-items: center; + animation: myAnim 2s ease 0s 1 normal forwards; +} + +@keyframes myAnim { + 0% { + animation-timing-function: ease-in; + opacity: 0; + transform: translateY(-250px); + } + + 38% { + animation-timing-function: ease-out; + opacity: 1; + transform: translateY(0); + } + + 55% { + animation-timing-function: ease-in; + transform: translateY(-65px); + } + + 72% { + animation-timing-function: ease-out; + transform: translateY(0); + } + + 81% { + animation-timing-function: ease-in; + transform: translateY(-28px); + } + + 90% { + animation-timing-function: ease-out; + transform: translateY(0); + } + + 95% { + animation-timing-function: ease-in; + transform: translateY(-8px); + } + + 100% { + animation-timing-function: ease-out; + transform: translateY(0); + } +} \ No newline at end of file