645 lines
24 KiB
JavaScript
Raw Normal View History

2025-03-09 16:15:31 +01:00
// Liste des monstres du jeu
let monsters = [
{ id: 1, name: 'Chatacabra', image: 'img/Chatacabra.jpg' },
{ id: 2, name: 'Quematrice', image: 'img/Quematrice.jpg' },
{ id: 3, name: 'Lala Barina', image: 'img/Lala_Barina.jpg' },
{ id: 4, name: 'Congalala', image: 'img/Congalala.jpg' },
{ id: 5, name: 'Balahara', image: 'img/Balahara.jpg' },
{ id: 6, name: 'Doshaguma', image: 'img/Doshaguma.jpg' },
{ id: 7, name: 'Uth Duna', image: 'img/Uth_Duna.jpg' },
{ id: 8, name: 'Rompopolo', image: 'img/Rompopolo.jpg' },
{ id: 9, name: 'Rey Dau', image: 'img/Rey_Dau.jpg' },
{ id: 10, name: 'Nerscylla', image: 'img/Nerscylla.jpg' },
{ id: 11, name: 'Hirabami', image: 'img/Hirabami.jpg' },
{ id: 12, name: 'Ajarakan', image: 'img/Ajarakan.jpg' },
{ id: 13, name: 'Nu Udra', image: 'img/Nu_Udra.jpg' },
{ id: 14, name: 'Doshaguma Gardien', image: 'img/Doshaguma_Gardien.jpg' },
{ id: 15, name: 'Rathalos Gardien', image: 'img/Rathalos_Gardien.jpg' },
{ id: 16, name: 'Jin Dahaad', image: 'img/Jin_Dahaad.jpg' },
{ id: 17, name: 'Odogaron Désastre Gardien', image: 'img/Odogaron_Desastre_Gardien.jpg' },
{ id: 18, name: 'Xu Wu', image: 'img/Xu_Wu.jpg' },
{ id: 19, name: 'Arkveld Gardien', image: 'img/Arkveld_Gardien.jpg' },
{ id: 20, name: 'Zoh Shia', image: 'img/Zoh_Shia.jpg' },
{ id: 21, name: 'Yian Kut-Ku', image: 'img/Yian_Kut-Ku.jpg' },
{ id: 22, name: 'Gypceros', image: 'img/Gypceros.jpg' },
{ id: 23, name: 'Rathian', image: 'img/Rathian.jpg' },
{ id: 24, name: 'Anjanath Tonnerre Gardien', image: 'img/Anjanath_Tonnerre_Gardien.jpg' },
{ id: 25, name: 'Rathalos', image: 'img/Rathalos.jpg' },
{ id: 26, name: 'Gravios', image: 'img/Gravios.jpg' },
{ id: 27, name: 'Blangonga', image: 'img/Blangonga.jpg' },
{ id: 28, name: 'Gore Malaga', image: 'img/Gore_Malaga.jpg' },
{ id: 29, name: 'Arkveld', image: 'img/Arkveld.jpg' }
];
let quests = [
{ id: 1, monsterId: 1, crownType: 'small', playerName: 'Hunter123', playerId: 'MHW-1234', date: new Date().toISOString() },
{ id: 2, monsterId: 1, crownType: 'large', playerName: 'DragonSlayer', playerId: 'MHW-5678', date: new Date().toISOString() },
{ id: 3, monsterId: 2, crownType: 'small', playerName: 'ThunderLord', playerId: 'MHW-9012', date: new Date().toISOString() }
];
let announcements = [
{ id: 1, text: "Bienvenue sur le site de partage de quêtes à couronnes pour Monster Hunter Wilds!", active: true }
];
// Éléments DOM
const monsterListEl = document.getElementById('monsterList');
const modalMonsterNameEl = document.getElementById('modalMonsterName');
const questListEl = document.getElementById('questList');
const addQuestBtn = document.getElementById('addQuestBtn');
const addQuestForm = document.getElementById('addQuestForm');
const crownFilterEls = document.querySelectorAll('input[name="crownFilter"]');
const announcementAreaEl = document.getElementById('announcementArea');
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
const monsterSearchEl = document.getElementById('monsterSearch');
const clearSearchBtn = document.getElementById('clearSearchBtn');
// Variables globales
let currentMonsterId = null;
let currentQuestToDelete = null;
// Modales Bootstrap
const questListModal = new bootstrap.Modal(document.getElementById('questListModal'));
const addQuestModal = new bootstrap.Modal(document.getElementById('addQuestModal'));
const deleteQuestModal = new bootstrap.Modal(document.getElementById('deleteQuestModal'));
// Initialisation
document.addEventListener('DOMContentLoaded', () => {
loadData();
renderMonsterList();
setupMonsterSearchSelect();
2025-03-09 16:15:31 +01:00
displayAnnouncements();
// Événements
addQuestBtn.addEventListener('click', () => {
resetMonsterSearchSelect();
addQuestModal.show();
});
2025-03-09 16:15:31 +01:00
addQuestForm.addEventListener('submit', handleAddQuest);
crownFilterEls.forEach(radio => {
radio.addEventListener('change', filterQuests);
});
confirmDeleteBtn.addEventListener('click', handleDeleteQuest);
// Événements pour la recherche
monsterSearchEl.addEventListener('input', searchMonsters);
clearSearchBtn.addEventListener('click', clearSearch);
// Initialiser le sélecteur de monstre avec recherche
initMonsterSearchSelect();
2025-03-09 16:15:31 +01:00
});
// Fonctions de chargement et sauvegarde des données
function loadData() {
// Essayer de charger depuis localStorage s'il y a des données
const storedQuests = localStorage.getItem('mhw_quests');
const storedAnnouncements = localStorage.getItem('mhw_announcements');
if (storedQuests) quests = JSON.parse(storedQuests);
if (storedAnnouncements) announcements = JSON.parse(storedAnnouncements);
2025-03-09 16:15:31 +01:00
}
function saveData() {
localStorage.setItem('mhw_quests', JSON.stringify(quests));
console.log('Données sauvegardées');
}
// Affichage des annonces
function displayAnnouncements() {
const activeAnnouncements = announcements.filter(a => a.active);
if (activeAnnouncements.length > 0) {
announcementAreaEl.innerHTML = activeAnnouncements.map(a => `<p>${a.text}</p>`).join('');
announcementAreaEl.classList.remove('d-none');
} else {
announcementAreaEl.classList.add('d-none');
}
}
// Rendu de la liste des monstres
function renderMonsterList(filteredMonsters = null) {
const monstersToRender = filteredMonsters || monsters;
if (monstersToRender.length === 0) {
2025-03-09 16:15:31 +01:00
monsterListEl.innerHTML = `
<div class="col-12">
<div class="empty-message">
<p>Aucun monstre disponible pour le moment.</p>
</div>
</div>
`;
return;
}
monsterListEl.innerHTML = monstersToRender.map(monster => {
2025-03-09 16:15:31 +01:00
const smallCrownCount = quests.filter(q => q.monsterId === monster.id && q.crownType === 'small').length;
const largeCrownCount = quests.filter(q => q.monsterId === monster.id && q.crownType === 'large').length;
return `
<div class="col-lg-3 col-md-4 col-sm-6">
<div class="card monster-card fade-in" data-monster-id="${monster.id}">
<div class="card-img-container">
<img src="${monster.image}" class="card-img-top" alt="${monster.name}">
</div>
<div class="card-body">
<h5 class="card-title">${monster.name}</h5>
<div class="d-flex justify-content-between">
<div>
<span class="crown-badge small-crown" title="Petites couronnes">
<i class="bi bi-trophy-fill crown-icon">👑</i> ${smallCrownCount}
</span>
<span class="crown-badge large-crown" title="Grandes couronnes">
<i class="bi bi-trophy-fill crown-icon">👑</i> ${largeCrownCount}
</span>
</div>
<button class="btn btn-sm btn-outline-primary">Voir les quêtes</button>
</div>
</div>
</div>
</div>
`;
}).join('');
// Ajouter les écouteurs d'événements après avoir rendu la liste
document.querySelectorAll('.monster-card').forEach(card => {
card.addEventListener('click', () => {
const monsterId = parseInt(card.dataset.monsterId);
showQuestsForMonster(monsterId);
});
});
}
// Recherche de monstres sur la page principale
function searchMonsters() {
const searchTerm = monsterSearchEl.value.trim().toLowerCase();
if (searchTerm === '') {
// Si la recherche est vide, afficher tous les monstres
renderMonsterList();
return;
}
// Filtrer les monstres qui correspondent à la recherche
const filteredMonsters = monsters.filter(monster =>
monster.name.toLowerCase().includes(searchTerm)
);
// Afficher un message si aucun monstre ne correspond
if (filteredMonsters.length === 0) {
monsterListEl.innerHTML = `
<div class="col-12">
<div class="empty-message">
<p>Aucun monstre ne correspond à votre recherche "${monsterSearchEl.value}".</p>
</div>
</div>
`;
return;
}
renderMonsterList(filteredMonsters);
}
// Effacer la recherche
function clearSearch() {
monsterSearchEl.value = '';
renderMonsterList();
2025-03-09 16:15:31 +01:00
}
// Afficher les quêtes pour un monstre spécifique
function showQuestsForMonster(monsterId) {
currentMonsterId = monsterId;
const monster = monsters.find(m => m.id === monsterId);
if (!monster) return;
modalMonsterNameEl.textContent = monster.name;
// Réinitialiser le filtre
document.getElementById('filterAll').checked = true;
// Afficher toutes les quêtes pour ce monstre
renderQuestList(monsterId);
// Afficher la modale
questListModal.show();
}
// Rendu de la liste des quêtes
function renderQuestList(monsterId, filter = 'all') {
let monsterQuests = quests.filter(q => q.monsterId === monsterId);
if (filter !== 'all') {
monsterQuests = monsterQuests.filter(q => q.crownType === filter);
}
if (monsterQuests.length === 0) {
questListEl.innerHTML = `
<div class="empty-message">
<p>Aucune quête disponible pour ce monstre avec ce filtre.</p>
</div>
`;
return;
}
questListEl.innerHTML = monsterQuests.map(quest => {
const monster = monsters.find(m => m.id === quest.monsterId);
const crownClass = quest.crownType === 'small' ? 'small-crown' : 'large-crown';
const crownText = quest.crownType === 'small' ? 'Petite couronne' : 'Grande couronne';
const questDate = new Date(quest.date);
const daysDiff = Math.floor((new Date() - questDate) / (1000 * 60 * 60 * 24));
const freshness = daysDiff <= 1 ? 'Aujourd\'hui' :
daysDiff <= 2 ? 'Hier' :
`Il y a ${daysDiff} jours`;
return `
<div class="card quest-card mb-3 fade-in">
<div class="card-body">
<h5 class="card-title">
<span class="${crownClass}"><i class="bi bi-trophy-fill">👑</i></span>
${crownText} pour ${monster.name}
</h5>
<p class="card-text">
Proposée par: <strong>${quest.playerName}</strong> (ID: ${quest.playerId})
</p>
<p class="quest-date">Ajoutée: ${freshness}</p>
<button class="btn btn-outline-danger btn-sm quest-delete-btn"
data-quest-id="${quest.id}">
Supprimer
</button>
</div>
</div>
`;
}).join('');
// Ajouter les écouteurs d'événements pour les boutons de suppression
document.querySelectorAll('.quest-delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const questId = parseInt(btn.dataset.questId);
showDeleteConfirmation(questId);
});
});
}
// Filtrer les quêtes
function filterQuests() {
const filterValue = document.querySelector('input[name="crownFilter"]:checked').value;
renderQuestList(currentMonsterId, filterValue);
}
// Afficher la confirmation de suppression
function showDeleteConfirmation(questId) {
currentQuestToDelete = questId;
deleteQuestModal.show();
}
// Gérer la suppression d'une quête
function handleDeleteQuest() {
if (currentQuestToDelete === null) return;
// Trouver l'index de la quête à supprimer
const questIndex = quests.findIndex(q => q.id === currentQuestToDelete);
if (questIndex === -1) return;
// Mémoriser le monsterId pour mettre à jour l'affichage
const monsterId = quests[questIndex].monsterId;
// Supprimer la quête
quests.splice(questIndex, 1);
// Sauvegarder les données
saveData();
// Mettre à jour l'affichage
renderMonsterList();
// Si la modale des quêtes est ouverte, mettre à jour son contenu
if (currentMonsterId === monsterId) {
renderQuestList(currentMonsterId);
}
// Fermer la modale de confirmation
deleteQuestModal.hide();
// Réinitialiser
currentQuestToDelete = null;
}
// Préparer le HTML pour le sélecteur de monstre avec recherche
function setupMonsterSearchSelect() {
// Récupérer le conteneur du formulaire
const monsterFieldContainer = document.querySelector('.mb-3:has(#monsterSelect)');
if (!monsterFieldContainer) return;
// Remplacer le contenu par notre nouveau sélecteur
monsterFieldContainer.innerHTML = `
<label for="monsterSearchSelect" class="form-label">Monstre</label>
<div class="monster-search-container">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-search">🔍</i></span>
<input type="text" class="form-control" id="monsterSearchSelect"
placeholder="Rechercher et sélectionner un monstre..." autocomplete="off">
<input type="hidden" id="selectedMonsterId" name="selectedMonsterId" required>
</div>
<div class="monster-search-results d-none" id="monsterSearchResults">
<!-- Les résultats seront générés dynamiquement ici -->
</div>
</div>
<div class="invalid-feedback">Veuillez sélectionner un monstre</div>
`;
// Ajouter les styles CSS nécessaires s'ils n'existent pas déjà
if (!document.getElementById('monster-search-styles')) {
const styleEl = document.createElement('style');
styleEl.id = 'monster-search-styles';
styleEl.textContent = `
/* Styles pour le select searchable personnalisé */
.monster-search-container {
position: relative;
}
.monster-search-results {
position: absolute;
width: 100%;
max-height: 250px;
overflow-y: auto;
background-color: var(--mh-light-bg);
border: 1px solid var(--mh-accent);
border-radius: 0.375rem;
z-index: 1050;
margin-top: 2px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.monster-search-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: var(--mh-light);
}
.monster-search-item:hover,
.monster-search-item.active {
background-color: var(--mh-dark);
}
.monster-search-item:last-child {
border-bottom: none;
}
.selected-monster {
font-weight: bold;
color: var(--mh-accent);
}
.monster-search-no-results {
padding: 10px;
text-align: center;
color: var(--mh-light);
font-style: italic;
}
`;
document.head.appendChild(styleEl);
}
}
// Initialiser le sélecteur de monstre avec recherche
function initMonsterSearchSelect() {
const searchInput = document.getElementById('monsterSearchSelect');
const searchResults = document.getElementById('monsterSearchResults');
const hiddenInput = document.getElementById('selectedMonsterId');
if (!searchInput || !searchResults || !hiddenInput) return;
// Événement pour l'input de recherche
searchInput.addEventListener('input', function() {
const searchTerm = this.value.trim().toLowerCase();
// Filtrer les monstres qui correspondent à la recherche
const filteredMonsters = monsters.filter(monster =>
monster.name.toLowerCase().includes(searchTerm)
);
// Afficher les résultats
renderSearchResults(filteredMonsters, searchResults);
// Montrer les résultats si l'input a du contenu
if (searchTerm.length > 0) {
searchResults.classList.remove('d-none');
} else if (!hiddenInput.value) {
// Cacher seulement si aucun monstre n'est déjà sélectionné
searchResults.classList.add('d-none');
}
});
// Événement pour le focus sur l'input
searchInput.addEventListener('focus', function() {
const searchTerm = this.value.trim().toLowerCase();
// Si l'input a du contenu ou si un monstre est déjà sélectionné, montrer les résultats
if (searchTerm.length > 0 || hiddenInput.value) {
// Filtrer les monstres qui correspondent à la recherche
const filteredMonsters = searchTerm.length > 0
? monsters.filter(monster => monster.name.toLowerCase().includes(searchTerm))
: monsters;
// Afficher les résultats
renderSearchResults(filteredMonsters, searchResults);
searchResults.classList.remove('d-none');
}
});
// Fermer les résultats lors d'un clic à l'extérieur
document.addEventListener('click', function(e) {
if (!searchInput?.contains(e.target) && !searchResults?.contains(e.target)) {
searchResults?.classList.add('d-none');
}
});
// Navigation avec les flèches du clavier
searchInput.addEventListener('keydown', function(e) {
if (searchResults.classList.contains('d-none')) return;
const items = searchResults.querySelectorAll('.monster-search-item');
const activeItem = searchResults.querySelector('.monster-search-item.active');
switch(e.key) {
case 'ArrowDown':
e.preventDefault();
if (!activeItem) {
items[0]?.classList.add('active');
ensureVisible(items[0], searchResults);
} else {
const nextItem = activeItem.nextElementSibling;
if (nextItem) {
activeItem.classList.remove('active');
nextItem.classList.add('active');
ensureVisible(nextItem, searchResults);
}
}
break;
case 'ArrowUp':
e.preventDefault();
if (activeItem) {
const prevItem = activeItem.previousElementSibling;
if (prevItem) {
activeItem.classList.remove('active');
prevItem.classList.add('active');
ensureVisible(prevItem, searchResults);
}
}
break;
case 'Enter':
e.preventDefault();
if (activeItem) {
const monsterId = activeItem.dataset.monsterId;
selectMonster(monsterId, searchInput, hiddenInput, searchResults);
}
break;
case 'Escape':
e.preventDefault();
searchResults.classList.add('d-none');
break;
}
});
}
// S'assurer que l'élément actif est visible dans la liste déroulante
function ensureVisible(element, container) {
if (!element || !container) return;
const containerRect = container.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
if (elementRect.bottom > containerRect.bottom) {
container.scrollTop += elementRect.bottom - containerRect.bottom;
} else if (elementRect.top < containerRect.top) {
container.scrollTop -= containerRect.top - elementRect.top;
}
}
// Afficher les résultats de recherche pour le sélecteur de monstre
function renderSearchResults(results, container) {
if (!container) return;
if (results.length === 0) {
container.innerHTML = '<div class="monster-search-no-results">Aucun monstre trouvé</div>';
return;
}
container.innerHTML = results.map(monster =>
`<div class="monster-search-item" data-monster-id="${monster.id}">
${monster.name}
</div>`
).join('');
// Ajouter les écouteurs d'événements pour les éléments de la liste
container.querySelectorAll('.monster-search-item').forEach(item => {
item.addEventListener('click', function() {
const monsterId = this.dataset.monsterId;
const searchInput = document.getElementById('monsterSearchSelect');
const hiddenInput = document.getElementById('selectedMonsterId');
const searchResults = document.getElementById('monsterSearchResults');
selectMonster(monsterId, searchInput, hiddenInput, searchResults);
});
});
}
// Sélectionner un monstre dans le sélecteur
function selectMonster(monsterId, searchInput, hiddenInput, searchResults) {
const monster = monsters.find(m => m.id == monsterId);
if (monster) {
searchInput.value = monster.name;
hiddenInput.value = monster.id;
searchInput.classList.add('selected-monster');
searchResults.classList.add('d-none');
}
}
// Réinitialiser le sélecteur de monstre
function resetMonsterSearchSelect() {
const searchInput = document.getElementById('monsterSearchSelect');
const hiddenInput = document.getElementById('selectedMonsterId');
const searchResults = document.getElementById('monsterSearchResults');
if (searchInput) {
searchInput.value = '';
searchInput.classList.remove('selected-monster');
}
if (hiddenInput) {
hiddenInput.value = '';
}
if (searchResults) {
searchResults.classList.add('d-none');
}
}
2025-03-09 16:15:31 +01:00
// Gérer l'ajout d'une quête
function handleAddQuest(e) {
e.preventDefault();
const monsterId = parseInt(document.getElementById('selectedMonsterId')?.value);
const crownType = document.querySelector('input[name="crownType"]:checked')?.value;
const playerName = document.getElementById('playerName')?.value.trim();
const playerId = document.getElementById('playerId')?.value.trim();
2025-03-09 16:15:31 +01:00
if (!monsterId || !crownType || !playerName || !playerId) {
alert('Veuillez remplir tous les champs requis');
return;
}
// Générer un ID unique pour la nouvelle quête
const newQuestId = quests.length > 0 ? Math.max(...quests.map(q => q.id)) + 1 : 1;
// Créer la nouvelle quête
const newQuest = {
id: newQuestId,
monsterId,
crownType,
playerName,
playerId,
date: new Date().toISOString()
};
// Ajouter la quête à la liste
quests.push(newQuest);
// Sauvegarder les données
saveData();
// Mettre à jour l'affichage
renderMonsterList();
// Réinitialiser le formulaire et fermer la modale
addQuestForm.reset();
resetMonsterSearchSelect();
2025-03-09 16:15:31 +01:00
addQuestModal.hide();
// Si la modale des quêtes est ouverte, mettre à jour son contenu
if (currentMonsterId === monsterId) {
renderQuestList(currentMonsterId);
}
}
// Nettoyer les quêtes de plus de 7 jours (à appeler depuis l'admin)
function cleanOldQuests() {
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const oldQuestsCount = quests.filter(q => new Date(q.date) < sevenDaysAgo).length;
quests = quests.filter(q => new Date(q.date) >= sevenDaysAgo);
saveData();
renderMonsterList();
return oldQuestsCount;
}