is_dashboard_screen() ) { return; } wp_enqueue_style( 'agec-ready-tokens', AGEC_READY_URL . 'assets/css/agec-design-tokens.css', array(), AGEC_READY_VERSION ); wp_enqueue_style( 'agec-ready-dashboard', AGEC_READY_URL . 'assets/css/admin-dashboard.css', array( 'agec-ready-tokens' ), AGEC_READY_VERSION ); } public function render(): void { if ( ! current_user_can( self::CAP ) ) { wp_die( esc_html__( 'Action non autorisée.', 'agec-ready' ) ); } $settings = (array) get_option( Meta::OPTION_SETTINGS, array() ); $display_product = ! empty( $settings['display_product'] ); echo '
'; echo '

' . esc_html__( 'AGEC Ready — statut de conformité', 'agec-ready' ) . '

'; echo '

' . esc_html__( 'Suivez votre conformité AGEC et gardez une longueur d’avance grâce à la veille réglementaire.', 'agec-ready' ) . '

'; echo '
'; $this->card_display( $display_product ); $this->card_catalogue(); $this->card_premium(); echo '
'; $this->section_veille(); $this->section_snapshot(); $this->footer_actions(); echo '
'; } /** * Indicateurs de conformité localement calculables (auto-diagnostic — gates ADR 0009). Aucune donnée * transmise ; les indicateurs de revenu (MRR, rétention, NRR) se suivent côté Freemius. */ private function section_snapshot(): void { $m = Metrics::snapshot(); echo '
'; echo '

' . esc_html__( 'Indicateurs de conformité', 'agec-ready' ) . '

'; echo ''; $this->render_greenwashing_upsell( $m ); echo ''; echo '

' . esc_html__( 'Le revenu récurrent (MRR, rétention, renouvellement) se suit dans votre tableau de bord Freemius.', 'agec-ready' ) . '

'; echo '
'; } private function snapshot_row( string $label, string $value, string $state ): void { echo '
  • '; echo '' . esc_html( $label ) . ''; echo '' . esc_html( $value ) . ''; echo '
  • '; } /** * Upsell « greenwashing » (qualifieur du segment payant, #12) — affiché **seulement après un résultat * explicable** : on liste les allégations à risque repérées et on invite à l'art. 13. Pédagogique, * non culpabilisant ; faux positifs assumés. * * @param array $m Instantané Metrics::snapshot(). */ private function render_greenwashing_upsell( array $m ): void { $at_risk = isset( $m['at_risk'] ) ? (int) $m['at_risk'] : 0; if ( $at_risk <= 0 ) { return; } $examples = isset( $m['risk_examples'] ) && is_array( $m['risk_examples'] ) ? $m['risk_examples'] : array(); echo '
    '; echo '

    ' . esc_html( sprintf( /* translators: %d: nombre de produits avec une allégation à risque. */ _n( '%d produit emploie une allégation environnementale à risque.', '%d produits emploient une allégation environnementale à risque.', $at_risk, 'agec-ready' ), $at_risk ) ) . '

    '; if ( ! empty( $examples ) ) { echo ''; } echo '

    ' . esc_html__( 'Les mentions vagues (« biodégradable », « respectueux de l’environnement », « écologique »…) sont encadrées (code de l’environnement R.541-230 ; directive EmpCo 2024/825). Le plus sûr : afficher vos caractéristiques réelles et sourcées (matière recyclée, recyclabilité, substances…) avec l’article 13.', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Sécuriser mes allégations (Pro)', 'agec-ready' ) . '

    '; echo '
    '; } /** * Carte « Affichage » : indique si le bloc Tri & recyclage est actif sur les fiches. */ private function card_display( bool $enabled ): void { $state = $enabled ? 'ok' : 'warn'; $value = $enabled ? __( 'Activé sur les fiches produits', 'agec-ready' ) : __( 'Désactivé', 'agec-ready' ); echo '
    '; echo '

    ' . esc_html__( 'Affichage du tri', 'agec-ready' ) . '

    '; echo '

    ' . esc_html( $value ) . '

    '; if ( $enabled ) { echo '

    ' . esc_html__( 'Triman et consignes de tri s’affichent automatiquement.', 'agec-ready' ) . '

    '; } else { echo '

    ' . esc_html__( 'Vos clients ne voient pas encore les consignes de tri.', 'agec-ready' ) . '

    '; } echo '' . esc_html__( 'Régler l’affichage', 'agec-ready' ) . ''; echo '
    '; } /** * Carte « Catalogue » : couverture du tri (produits sans consignes) — « on veille pour vous » (C3). */ private function card_catalogue(): void { $cov = CatalogScan::coverage(); $without = (int) $cov['without']; $scanned = (int) $cov['scanned']; $state = CatalogScan::coverage_state( $cov ); if ( 'empty' === $state ) { echo '
    '; echo '

    ' . esc_html__( 'Votre catalogue', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Aucun produit publié', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Ajoutez vos produits, puis renseignez le tri (onglet « AGEC ») ou un défaut par catégorie.', 'agec-ready' ) . '

    '; echo '
    '; return; } // 'covered' = catalogue entier couvert ; tout le reste reste honnête (jamais de faux « tout couvert »). $css = 'covered' === $state ? 'ok' : 'warn'; echo '
    '; echo '

    ' . esc_html__( 'Couverture du tri', 'agec-ready' ) . '

    '; if ( 'unknown' === $state ) { // Produits présents mais aucun scanné : on ne ment pas, on n'affiche pas de vert. echo '

    ' . esc_html__( 'Couverture non calculée pour le moment', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'L’analyse du catalogue n’a pas pu aboutir. Rechargez la page dans un instant ; si le problème persiste, consultez WooCommerce → État → Journaux.', 'agec-ready' ) . '

    '; echo '
    '; return; } if ( 'gaps' === $state ) { echo '

    ' . esc_html( sprintf( /* translators: %s: nombre de produits sans consignes de tri. */ _n( '%s produit sans consignes de tri', '%s produits sans consignes de tri', $without, 'agec-ready' ), number_format_i18n( $without ) ) ) . '

    '; echo '

    ' . esc_html__( 'Renseignez le tri (onglet « AGEC ») ou un défaut par catégorie pour une couverture complète de l’affichage.', 'agec-ready' ) . '

    '; if ( ! empty( $cov['capped'] ) ) { echo '

    ' . esc_html( sprintf( /* translators: %s: nombre de produits analysés. */ __( 'Analyse de vos %s produits les plus récents.', 'agec-ready' ), number_format_i18n( $scanned ) ) ) . '

    '; } echo '' . esc_html__( 'Compléter le tri', 'agec-ready' ) . ''; echo ''; return; } if ( 'partial' === $state ) { // Les produits scannés sont couverts, mais le catalogue dépasse le plafond d'analyse : // on le dit franchement au lieu d'affirmer « toutes vos fiches ». echo '

    ' . esc_html( sprintf( /* translators: %s: nombre de produits analysés. */ __( 'Vos %s produits les plus récents sont couverts', 'agec-ready' ), number_format_i18n( $scanned ) ) ) . '

    '; echo '

    ' . esc_html__( 'Catalogue volumineux : l’analyse porte sur les produits les plus récents. Vérifiez le tri sur le reste de votre catalogue (onglet « AGEC » ou défaut par catégorie).', 'agec-ready' ) . '

    '; echo '' . esc_html__( 'Ouvrir mes produits', 'agec-ready' ) . ''; echo ''; return; } echo '

    ' . esc_html__( 'Toutes vos fiches affichent les consignes', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Vos produits publiés sont couverts (réglage propre ou défaut de catégorie).', 'agec-ready' ) . '

    '; echo '' . esc_html__( 'Ouvrir mes produits', 'agec-ready' ) . ''; echo ''; } /** * Carte « Mentions environnementales (art. 13) » : statut premium ou upsell contextuel non culpabilisant. */ private function card_premium(): void { if ( agec_ready_can_use_premium() ) { echo '
    '; echo '

    ' . esc_html__( 'Mentions environnementales (art. 13)', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Premium actif', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'L’affichage des caractéristiques environnementales et la page dématérialisée sont disponibles.', 'agec-ready' ) . '

    '; $account = self::account_url(); if ( '' !== $account ) { // Expansion / NRR : ajout de sites, gestion de licence (prorata géré par Freemius). echo '' . esc_html__( 'Gérer ma licence et mes sites', 'agec-ready' ) . ''; } echo '
    '; return; } echo '
    '; echo '

    ' . esc_html__( 'Mentions environnementales (art. 13)', 'agec-ready' ) . '

    '; $filieres = Qualification::detect_premium_filieres( self::category_names() ); if ( ! empty( $filieres ) ) { echo '

    ' . esc_html__( 'Vos catégories suggèrent des filières soumises à l’article 13 :', 'agec-ready' ) . '

    '; echo ''; } else { echo '

    ' . esc_html__( 'Affichez les caractéristiques environnementales obligatoires (matière recyclée, recyclabilité…) sur vos fiches et une page dédiée.', 'agec-ready' ) . '

    '; } echo '' . esc_html__( 'Premium', 'agec-ready' ) . ''; echo '' . esc_html__( 'Voir les offres Premium', 'agec-ready' ) . ''; echo '
    '; } /** * URL d'offre premium : page de tarifs Freemius si le SDK est présent, sinon repli filtrable * (jamais de lien mort). Le branchement réel est vérifié avec un compte Freemius live (Phase H). */ public static function upgrade_url(): string { $fs = function_exists( 'agec_fs' ) ? agec_fs() : null; if ( $fs && method_exists( $fs, 'get_upgrade_url' ) ) { return (string) $fs->get_upgrade_url(); } /** * Repli de l'URL « offres premium » hors SDK Freemius (dev/.org sans licence). * * @param string $url URL de repli. */ return (string) apply_filters( 'agec_ready_upgrade_url', 'https://agecready.com/' ); } /** * URL du compte Freemius (gestion licence / sites) si le SDK est présent, sinon chaîne vide. */ private static function account_url(): string { $fs = function_exists( 'agec_fs' ) ? agec_fs() : null; if ( $fs && method_exists( $fs, 'get_account_url' ) ) { return (string) $fs->get_account_url(); } return ''; } /** * Section « Veille réglementaire ». En **gratuit** : un teaser (la valeur est dans Pro). En **Premium**, * le journal daté complet est injecté via le filtre `agec_ready_dashboard_veille` * (module de veille premium, strippé du zip .org). Frontière revue 08 (#2). */ private function section_veille(): void { echo '
    '; echo '

    ' . esc_html__( 'Veille réglementaire', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Nous surveillons l’évolution de la réglementation AGEC (Légifrance, ADEME, éco-organismes agréés).', 'agec-ready' ) . '

    '; /** * Point d'extension : le **journal daté complet** (Premium) est injecté ici par le module de * veille premium (absent du zip .org). En gratuit, aucun premium n'est branché * → teaser ci-dessous. * * @param string $html HTML du journal (vide par défaut). */ $journal = (string) apply_filters( 'agec_ready_dashboard_veille', '' ); if ( '' !== $journal ) { echo wp_kses_post( $journal ); } else { $this->render_veille_teaser(); } echo '
    '; } /** * Teaser veille (gratuit) : la valeur — journal daté/sourcé + alertes e-mail — est dans Pro (#2). * Copy 100 % statique : aucune donnée de veille en free (le dataset vit dans src/Premium, strippé du zip .org). */ private function render_veille_teaser(): void { echo '
    '; echo '

    ' . esc_html__( 'Chaque évolution de la réglementation AGEC est suivie, datée et sourcée. Le journal complet et les alertes e-mail à chaque changement sont inclus dans AGEC Ready Pro.', 'agec-ready' ) . '

    '; echo '

    ' . esc_html__( 'Débloquer la veille (Pro)', 'agec-ready' ) . '

    '; echo '
    '; } private function footer_actions(): void { echo '

    '; echo '' . esc_html__( 'Relancer l’assistant', 'agec-ready' ) . ' '; echo '' . esc_html__( 'Mes produits', 'agec-ready' ) . ''; echo '

    '; } /** * Noms des catégories de produits (borné), pour la détection de filières premium. * * @return array */ private static function category_names(): array { if ( ! taxonomy_exists( 'product_cat' ) ) { return array(); } $terms = get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => false, 'number' => 200, 'fields' => 'names', ) ); if ( is_wp_error( $terms ) ) { // Pas d'échec silencieux : une erreur de taxonomie n'est pas « zéro catégorie » // (sinon l'upsell art. 13 / la qualification PQAS ne se déclencheraient jamais). Log::error( 'get_terms(product_cat) a échoué : ' . $terms->get_error_message() ); return array(); } return is_array( $terms ) ? $terms : array(); } private static function wizard_url( string $step ): string { return add_query_arg( array( 'page' => Wizard::PAGE, 'step' => $step, ), admin_url( 'admin.php' ) ); } private function is_dashboard_screen(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return isset( $_GET['page'] ) && self::PAGE === sanitize_key( wp_unslash( $_GET['page'] ) ); } }