Adapter le bandeau quotidien à la nouvelle réponse de l'API `api/google-ads/getAppsFlyerDailyFunnel`. La réponse contient maintenant les vraies installations AppsFlyer : ```text appsflyer_installs_android appsflyer_installs_ios appsflyer_installs_total ``` ainsi que : ```text total_spend onboarding_start_active_users onboarding_success_active_users ``` Construire une fonction : ```js buildDailyControlCards(apiResponse, monthlyBudget) ``` Avant tout calcul : ```js const sortedData = [...apiResponse.data].sort( (a, b) => new Date(a.date) - new Date(b.date) ); const lastDay = sortedData[sortedData.length - 1]; const last7Days = sortedData.slice(-7); ``` ### 1. Pacing du jour Utiliser la dépense de la dernière date disponible : ```js const daysInMonth = new Date( new Date(lastDay.date).getFullYear(), new Date(lastDay.date).getMonth() + 1, 0 ).getDate(); const dailyTarget = monthlyBudget / daysInMonth; const pacing = dailyTarget > 0 ? (Number(lastDay.total_spend || 0) / dailyTarget) * 100 : null; ``` Retour : ```js { label: "Pacing du jour", value: pacing !== null ? `${Math.round(pacing)} %` : "N/A", rawValue: pacing, subtitle: "Dépense J-1 face au rythme cible", status: pacing === null ? "unknown" : pacing < 60 || pacing > 115 ? "warning" : "ok" } ``` ### 2. Coût / onboarding Calculer sur les 7 dernières dates : ```js const spend7d = last7Days.reduce( (sum, row) => sum + Number(row.total_spend || 0), 0 ); const onboardingSuccess7d = last7Days.reduce( (sum, row) => sum + Number(row.onboarding_success_active_users || 0), 0 ); const costPerOnboarding = onboardingSuccess7d > 0 ? spend7d / onboardingSuccess7d : null; ``` Retour : ```js { label: "Coût / onboarding", value: costPerOnboarding !== null ? `${Math.round(costPerOnboarding).toLocaleString("fr-FR")} MAD` : "N/A", rawValue: costPerOnboarding, subtitle: "Glissant 7 j — cible 1 500", status: costPerOnboarding === null ? "unknown" : costPerOnboarding > 3000 ? "danger" : costPerOnboarding > 1500 ? "warning" : "ok" } ``` Ne pas utiliser `cpi`, car : ```text cpi = total_spend / onboarding_start_active_users ``` et ce n'est pas le coût par onboarding réussi. ### 3. Onboardings 7 jours Utiliser : ```js const onboardings7d = last7Days.reduce( (sum, row) => sum + Number(row.onboarding_success_active_users || 0), 0 ); ``` Retour : ```js { label: "Onboardings 7 j", value: onboardings7d.toLocaleString("fr-FR"), rawValue: onboardings7d, subtitle: "Volume incomplet, remontée en cours", status: "ok" } ``` Utiliser directement `onboarding_success_active_users`. Ne pas utiliser l'alias `comptes` dans le code du dashboard, même s'il représente actuellement la même valeur. ### 4. Install → démarrage Utiliser désormais les vrais installs AppsFlyer. Calcul : ```js const installs7d = last7Days.reduce( (sum, row) => sum + Number(row.appsflyer_installs_total || 0), 0 ); const onboardingStart7d = last7Days.reduce( (sum, row) => sum + Number(row.onboarding_start_active_users || 0), 0 ); const installToStart = installs7d > 0 ? (onboardingStart7d / installs7d) * 100 : null; ``` Retour : ```js { label: "Install → démarrage", value: installToStart !== null ? `${installToStart.toFixed(1)} %` : "N/A", rawValue: installToStart, subtitle: "Sur 7 jours — seuil d'alerte 10 %", status: installToStart === null ? "unknown" : installToStart < 10 ? "danger" : "ok" } ``` IMPORTANT : Ne pas utiliser : ```text installations ``` pour représenter les installs. Dans la réponse actuelle : ```text installations = onboarding_start_active_users ``` Le vrai champ d'install est : ```text appsflyer_installs_total ``` Pour les graphiques OS, conserver également : ```text appsflyer_installs_android appsflyer_installs_ios ``` ### 5. Campagnes actives Ne pas inventer la valeur. La réponse actuelle ne fournit toujours pas le statut des campagnes Google Ads et Meta Ads. Afficher temporairement : ```js { label: "Campagnes actives", value: "N/A", rawValue: null, subtitle: "Donnée campagne indisponible", status: "unknown" } ``` Le mapper doit être prêt à utiliser ultérieurement : ```json { "campaign_summary": { "active_campaigns": 13, "active_campaigns_google": 7, "active_campaigns_meta": 6, "active_campaigns_zero_spend": 0 } } ``` Quand ce bloc existera : ```js const summary = apiResponse.campaign_summary; { label: "Campagnes actives", value: String(summary.active_campaigns), rawValue: summary.active_campaigns, subtitle: summary.active_campaigns_zero_spend === 0 ? "Aucune campagne à zéro dépense" : `${summary.active_campaigns_zero_spend} campagne(s) à zéro dépense`, status: summary.active_campaigns_zero_spend > 0 ? "danger" : "ok" } ``` ### Résultat final attendu ```js return { pacing, costPerOnboarding, onboardings7d, installToStart, activeCampaigns }; ``` Ne jamais hardcoder les valeurs du dashboard. Ne jamais utiliser `apiResponse.total` pour les cartes 7 jours si la période API peut être supérieure à 7 jours. Toujours calculer les métriques 7 jours à partir de `data.slice(-7)` après tri par date.