✨Use API
This commit is contained in:
@@ -37,14 +37,20 @@
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.wide-card {
|
||||
width: calc(100% - 60px);
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
text-align: left;
|
||||
}
|
||||
.contributors-list {
|
||||
width: calc(100% - 60px);
|
||||
max-height: 500px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
}
|
||||
.contributor-item {
|
||||
padding: A10px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #2d4a7d;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -89,27 +95,17 @@
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
}
|
||||
input {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
.campaign-title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 5px;
|
||||
color: #e3b505;
|
||||
}
|
||||
button {
|
||||
background-color: #e3b505; /* Bouton jaune */
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #ffc107; /* Jaune plus clair au survol */
|
||||
.campaign-subtitle {
|
||||
font-size: 16px;
|
||||
color: #a0aec0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.loading {
|
||||
display: inline-block;
|
||||
@@ -161,15 +157,92 @@
|
||||
.html-tag { background-color: #ed8936; }
|
||||
.demo-tag { background-color: #a0aec0; }
|
||||
.error-tag { background-color: #f56565; }
|
||||
|
||||
.reward-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.reward-item {
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #233876;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
}
|
||||
.reward-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #e3b505;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.reward-price {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background-color: #e3b505;
|
||||
color: #1a365d;
|
||||
font-weight: bold;
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.reward-description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.reward-stats {
|
||||
font-size: 14px;
|
||||
color: #a0aec0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.reward-stat {
|
||||
background-color: #1a365d;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.reward-progress {
|
||||
height: 8px;
|
||||
background-color: #1a365d;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.reward-progress-fill {
|
||||
height: 100%;
|
||||
background-color: #e3b505;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Affichage sur mobile */
|
||||
@media (max-width: 600px) {
|
||||
.card {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.wide-card {
|
||||
width: 100%;
|
||||
}
|
||||
.contributors-list {
|
||||
width: 100%;
|
||||
}
|
||||
.reward-price {
|
||||
position: static;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.reward-stats {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Suivi de Campagne Ulule</h1>
|
||||
|
||||
<div class="config-panel">
|
||||
<h2>Suivi de la campagne</h2>
|
||||
<div>
|
||||
<p id="campaign-info">Chargement des données de la campagne...</p>
|
||||
<div id="campaign-info">
|
||||
<div class="loading"></div>
|
||||
<p>Chargement des données de la campagne...</p>
|
||||
</div>
|
||||
<p id="error-message" class="error"></p>
|
||||
</div>
|
||||
@@ -192,18 +265,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card wide-card">
|
||||
<h2>Récompenses</h2>
|
||||
<div id="rewards-container">
|
||||
<div class="loading"></div>
|
||||
<p>Chargement des récompenses...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card contributors-list">
|
||||
<h2>Derniers contributeurs</h2>
|
||||
<div id="contributors-container">Chargement des contributeurs...</div>
|
||||
<div id="contributors-container">
|
||||
<div class="loading"></div>
|
||||
<p>Chargement des contributeurs...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="last-update">Dernière mise à jour: <span id="last-update">--</span></p>
|
||||
|
||||
<script>
|
||||
let intervalId = null;
|
||||
// Configuration en dur
|
||||
const PROJECT_URL = 'https://fr.ulule.com/suppdonn-knights-of-water/'; // Remplacez par l'URL de votre campagne
|
||||
const REFRESH_INTERVAL = 20; // Intervalle de rafraîchissement en secondes
|
||||
// Définir l'URL de la campagne en dur
|
||||
const PROJECT_SLUG = 'suppdonn-knights-of-water'; // Remplacer par le slug de votre campagne
|
||||
const REFRESH_INTERVAL = 60; // Intervalle de rafraîchissement en secondes
|
||||
|
||||
// Éléments DOM
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
@@ -217,24 +300,11 @@
|
||||
const lastUpdateElement = document.getElementById('last-update');
|
||||
const sourceTag = document.getElementById('source-tag');
|
||||
const contributorsContainer = document.getElementById('contributors-container');
|
||||
const rewardsContainer = document.getElementById('rewards-container');
|
||||
|
||||
function extractProjectSlug(url) {
|
||||
async function fetchProjectData(slug) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
const pathParts = urlObj.pathname.split('/').filter(part => part);
|
||||
if (pathParts.length > 0) {
|
||||
return pathParts[pathParts.length - 1];
|
||||
}
|
||||
throw new Error('URL de projet invalide');
|
||||
} catch (error) {
|
||||
throw new Error('URL de projet invalide');
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchProjectData(projectSlug) {
|
||||
try {
|
||||
// Appeler notre API locale
|
||||
const response = await fetch(`/api/ulule/${projectSlug}`);
|
||||
const response = await fetch(`/api/ulule/${slug}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur API: ${response.status}`);
|
||||
@@ -246,10 +316,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchContributors(projectSlug) {
|
||||
async function fetchRewards(slug) {
|
||||
try {
|
||||
// Essayez de récupérer la page des contributeurs
|
||||
const response = await fetch(`/api/ulule/${projectSlug}/contributors`);
|
||||
const response = await fetch(`/api/ulule/${slug}/rewards`);
|
||||
|
||||
if (!response.ok) {
|
||||
return { rewards: [] };
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des récompenses:", error);
|
||||
return { rewards: [] };
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchContributors(slug) {
|
||||
try {
|
||||
const response = await fetch(`/api/ulule/${slug}/contributors`);
|
||||
|
||||
if (!response.ok) {
|
||||
return { contributors: [] };
|
||||
@@ -265,7 +349,13 @@
|
||||
function updateDashboard(data) {
|
||||
// Mettre à jour le titre de la campagne
|
||||
document.title = `Suivi de ${data.name}`;
|
||||
campaignInfo.textContent = `Campagne: ${data.name}`;
|
||||
|
||||
// Mettre à jour les informations de la campagne
|
||||
let campaignHtml = `
|
||||
<div class="campaign-title">${data.name}</div>
|
||||
<div class="campaign-subtitle">Campagne Ulule en cours</div>
|
||||
`;
|
||||
campaignInfo.innerHTML = campaignHtml;
|
||||
|
||||
// Formater le montant avec l'espace comme séparateur de milliers
|
||||
const formatter = new Intl.NumberFormat('fr-FR', {
|
||||
@@ -304,6 +394,57 @@
|
||||
lastUpdateElement.textContent = new Date().toLocaleString('fr-FR');
|
||||
}
|
||||
|
||||
function updateRewards(data) {
|
||||
if (!data.rewards || data.rewards.length === 0) {
|
||||
rewardsContainer.innerHTML = '<p>Aucune récompense trouvée ou impossible de récupérer la liste.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const formatter = new Intl.NumberFormat('fr-FR', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
|
||||
let html = '<div class="reward-list">';
|
||||
|
||||
// Trier les récompenses par prix
|
||||
const sortedRewards = [...data.rewards].sort((a, b) => a.price - b.price);
|
||||
|
||||
sortedRewards.forEach(reward => {
|
||||
// Calculer le pourcentage pour les récompenses limitées
|
||||
let percentTaken = 0;
|
||||
let stockText = "";
|
||||
|
||||
if (reward.stock) {
|
||||
percentTaken = Math.round((reward.orders_count / reward.stock) * 100);
|
||||
const remaining = reward.stock_available || (reward.stock - reward.orders_count);
|
||||
stockText = `<span class="reward-stat">${remaining} restants sur ${reward.stock}</span>`;
|
||||
} else {
|
||||
stockText = `<span class="reward-stat">Stock illimité</span>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="reward-item">
|
||||
<div class="reward-price">${formatter.format(reward.price)}</div>
|
||||
<div class="reward-title">${reward.title}</div>
|
||||
<div class="reward-description">${reward.description}</div>
|
||||
<div class="reward-stats">
|
||||
<span class="reward-stat">${reward.orders_count} contributeurs</span>
|
||||
${stockText}
|
||||
<span class="reward-stat">Livraison: ${new Date(reward.date_delivery).toLocaleDateString('fr-FR', { year: 'numeric', month: 'long' })}</span>
|
||||
</div>
|
||||
${reward.stock ? `
|
||||
<div class="reward-progress">
|
||||
<div class="reward-progress-fill" style="width: ${percentTaken}%"></div>
|
||||
</div>` : ''}
|
||||
</div>`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
rewardsContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
function updateContributors(data) {
|
||||
if (!data.contributors || data.contributors.length === 0) {
|
||||
contributorsContainer.innerHTML = '<p>Aucun contributeur trouvé ou impossible de récupérer la liste.</p>';
|
||||
@@ -332,54 +473,35 @@
|
||||
contributorsContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
// Fonction pour démarrer le suivi automatiquement
|
||||
async function startTracking() {
|
||||
// Fonction pour charger toutes les données
|
||||
async function loadAllData() {
|
||||
try {
|
||||
// Extraire le slug du projet depuis l'URL
|
||||
const projectSlug = extractProjectSlug(PROJECT_URL);
|
||||
campaignInfo.textContent = `Chargement des données pour ${projectSlug}...`;
|
||||
|
||||
// Récupérer les données initiales
|
||||
const projectData = await fetchProjectData(projectSlug);
|
||||
// Récupérer les données du projet
|
||||
const projectData = await fetchProjectData(PROJECT_SLUG);
|
||||
updateDashboard(projectData);
|
||||
|
||||
// Essayer de récupérer les contributeurs
|
||||
try {
|
||||
const contributorsData = await fetchContributors(projectSlug);
|
||||
updateContributors(contributorsData);
|
||||
} catch (contribError) {
|
||||
console.error("Erreur contributeurs:", contribError);
|
||||
contributorsContainer.innerHTML = '<p>Impossible de récupérer la liste des contributeurs.</p>';
|
||||
}
|
||||
// Récupérer les récompenses
|
||||
const rewardsData = await fetchRewards(PROJECT_SLUG);
|
||||
updateRewards(rewardsData);
|
||||
|
||||
// Arrêter l'intervalle précédent s'il existe
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
|
||||
// Configurer le rafraîchissement automatique
|
||||
intervalId = setInterval(async () => {
|
||||
try {
|
||||
// Mettre à jour les données du projet
|
||||
const updatedData = await fetchProjectData(projectSlug);
|
||||
updateDashboard(updatedData);
|
||||
|
||||
// Mettre à jour les contributeurs
|
||||
const contributorsData = await fetchContributors(projectSlug);
|
||||
updateContributors(contributorsData);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour:', error);
|
||||
}
|
||||
}, REFRESH_INTERVAL * 1000);
|
||||
// Récupérer les contributeurs
|
||||
const contributorsData = await fetchContributors(PROJECT_SLUG);
|
||||
updateContributors(contributorsData);
|
||||
|
||||
} catch (error) {
|
||||
errorMessage.textContent = error.message;
|
||||
campaignInfo.textContent = "Erreur lors du chargement des données";
|
||||
campaignInfo.innerHTML = "<p>Erreur lors du chargement des données</p>";
|
||||
}
|
||||
}
|
||||
|
||||
// Démarrer le suivi dès le chargement de la page
|
||||
document.addEventListener('DOMContentLoaded', startTracking);
|
||||
// Charger les données et configurer le rafraîchissement automatique
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Chargement initial
|
||||
loadAllData();
|
||||
|
||||
// Rafraîchissement automatique
|
||||
setInterval(loadAllData, REFRESH_INTERVAL * 1000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user