ELITE 9 2CH

Plage de prix : 469,00 € à 549,00 €

UGS : ELITE 8 2 CH-1 Catégorie :

Description

Découvrez l’ELITE 9 en vidéo


<?php


/**
 * ============================================================
 *  MOOVIKA — Système de prise de rendez-vous (EDR Auto)
 * ============================================================
 *  À coller dans le plugin "Code Snippets" (PHP, "Exécuter partout").
 *  >>> UN SEUL snippet RDV actif à la fois (sinon erreur fatale). <<<
 *
 *  Shortcode :
 *      [rdv_calendrier]                     (formulaire + calendrier)
 *      [rdv_calendrier product_id="123"]    (+ ajout panier WooCommerce)
 *
 *  Admin (menu "Rendez-vous") :
 *      - liste, modification et création de RDV (dates passées OK)
 *      - mode vacances / congés
 *  E-mails : confirmation au client + notification admin + rappel J-1.
 *
 *  >>> CONFIG (horaires, durée, jours) : moovika_rdv_config() <<<
 * ============================================================
 */

if (!defined('ABSPATH')) exit;

/* ------------------------------------------------------------
 * 0) CONFIGURATION CENTRALE + HELPERS
 * ---------------------------------------------------------- */
function moovika_rdv_config() {
    return array(
        'open'      => '08:00',
        'close'     => '13:00',
        'duration'  => 180,                 // minutes (3 h)
        'step'      => 60,                  // pas entre deux débuts
        'open_days' => array(3, 4, 5, 6),   // mer..sam (date('N'))
    );
}
function moovika_rdv_to_min($hhmm) { $p = explode(':', $hhmm); return ((int)$p[0]) * 60 + ((int)($p[1] ?? 0)); }
function moovika_rdv_to_hhmm($min) { return sprintf('%02d:%02d', floor($min / 60), $min % 60); }

function moovika_rdv_slots() {
    $c = moovika_rdv_config();
    $open = moovika_rdv_to_min($c['open']);
    $close = moovika_rdv_to_min($c['close']);
    $slots = array();
    for ($t = $open; $t + $c['duration'] <= $close; $t += $c['step']) {
        $slots[] = moovika_rdv_to_hhmm($t);
    }
    return $slots;
}
function moovika_rdv_duration_label() {
    $d = moovika_rdv_config()['duration'];
    $h = floor($d / 60); $m = $d % 60;
    return $m ? ($h . ' h ' . $m) : ($h . ' h');
}
function moovika_rdv_format_fr($date) {
    $ts = strtotime($date);
    $jours = array('Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
    $mois  = array('', 'janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre');
    return $jours[(int)date('w', $ts)] . ' ' . (int)date('j', $ts) . ' ' . $mois[(int)date('n', $ts)] . ' ' . date('Y', $ts);
}
function moovika_rdv_mail_headers() {
    return array(
        'Content-Type: text/plain; charset=UTF-8',
        'From: ' . get_bloginfo('name') . ' <' . get_option('admin_email') . '>',
        'Reply-To: ' . get_option('admin_email'),
    );
}

/* Périodes de congés */
function moovika_rdv_get_vacances() {
    $v = get_option('moovika_rdv_vacances', array());
    return is_array($v) ? array_values($v) : array();
}
function moovika_rdv_in_vacances($date) {
    foreach (moovika_rdv_get_vacances() as $p) {
        if ($date >= $p['start'] && $date <= $p['end']) return true;
    }
    return false;
}

/* ------------------------------------------------------------
 * 1) Table (v1.2 : ajout email + reminder_sent)
 * ---------------------------------------------------------- */
add_action('init', 'moovika_rdv_create_table');
function moovika_rdv_create_table() {
    if (get_option('moovika_rdv_db_version') === '1.2') return;

    global $wpdb;
    $table   = $wpdb->prefix . 'moovika_rdv';
    $charset = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        nom VARCHAR(100) NOT NULL,
        prenom VARCHAR(100) NOT NULL,
        email VARCHAR(150) NOT NULL DEFAULT '',
        adresse VARCHAR(255) NOT NULL,
        telephone VARCHAR(30) NOT NULL,
        vehicule_marque VARCHAR(100) NOT NULL,
        vehicule_modele VARCHAR(100) NOT NULL,
        motorisation VARCHAR(20) NOT NULL,
        date_rdv DATE NOT NULL,
        heure_rdv VARCHAR(10) NOT NULL,
        reminder_sent TINYINT(1) NOT NULL DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY slot (date_rdv, heure_rdv)
    ) $charset;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
    update_option('moovika_rdv_db_version', '1.2');
}

/* ------------------------------------------------------------
 * 2) AJAX public : lecture (3 lettres) + congés
 * ---------------------------------------------------------- */
add_action('wp_ajax_moovika_get_rdv', 'moovika_get_rdv');
add_action('wp_ajax_nopriv_moovika_get_rdv', 'moovika_get_rdv');
function moovika_get_rdv() {
    global $wpdb;
    $table = $wpdb->prefix . 'moovika_rdv';
    $rows  = $wpdb->get_results("SELECT date_rdv, heure_rdv, nom FROM $table ORDER BY date_rdv, heure_rdv", ARRAY_A);

    $appts = array();
    foreach ((array) $rows as $r) {
        $appts[] = array(
            'date'  => $r['date_rdv'],
            'heure' => $r['heure_rdv'],
            'label' => mb_strtoupper(mb_substr($r['nom'], 0, 3)),
        );
    }
    wp_send_json_success(array('appts' => $appts, 'vacances' => moovika_rdv_get_vacances()));
}

/* 2bis) AJAX détails complets (ADMIN UNIQUEMENT) */
add_action('wp_ajax_moovika_rdv_detail', 'moovika_rdv_detail');
function moovika_rdv_detail() {
    check_ajax_referer('moovika_rdv_nonce', 'nonce');
    if (!current_user_can('manage_options')) {
        wp_send_json_error(array('message' => 'Accès refusé.'));
    }
    global $wpdb;
    $table = $wpdb->prefix . 'moovika_rdv';
    $date  = sanitize_text_field($_POST['date'] ?? '');
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
        wp_send_json_error(array('message' => 'Date invalide.'));
    }
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT nom, prenom, email, adresse, telephone, vehicule_marque, vehicule_modele, motorisation, heure_rdv
         FROM $table WHERE date_rdv = %s ORDER BY heure_rdv", $date
    ), ARRAY_A);
    wp_send_json_success($rows ? $rows : array());
}

/* ------------------------------------------------------------
 * 3) AJAX public : enregistrement + e-mails
 * ---------------------------------------------------------- */
add_action('wp_ajax_moovika_save_rdv', 'moovika_save_rdv');
add_action('wp_ajax_nopriv_moovika_save_rdv', 'moovika_save_rdv');
function moovika_save_rdv() {
    check_ajax_referer('moovika_rdv_nonce', 'nonce');

    global $wpdb;
    $table = $wpdb->prefix . 'moovika_rdv';
    $cfg   = moovika_rdv_config();

    $nom     = sanitize_text_field($_POST['nom']          ?? '');
    $prenom  = sanitize_text_field($_POST['prenom']       ?? '');
    $email   = sanitize_email($_POST['email']             ?? '');
    $adresse = sanitize_text_field($_POST['adresse']      ?? '');
    $tel     = sanitize_text_field($_POST['telephone']    ?? '');
    $marque  = sanitize_text_field($_POST['marque']       ?? '');
    $modele  = sanitize_text_field($_POST['modele']       ?? '');
    $motor   = sanitize_text_field($_POST['motorisation'] ?? '');
    $date    = sanitize_text_field($_POST['date']         ?? '');
    $heure   = sanitize_text_field($_POST['heure']        ?? '');
    $product = intval($_POST['product_id'] ?? 0);

    if (!$nom || !$prenom || !$email || !$adresse || !$tel || !$marque || !$modele || !$motor || !$date || !$heure) {
        wp_send_json_error(array('message' => 'Tous les champs sont obligatoires.'));
    }
    if (!is_email($email)) {
        wp_send_json_error(array('message' => 'Adresse e-mail invalide.'));
    }
    if (!in_array($motor, array('thermique', 'hybride', 'electrique'), true)) {
        wp_send_json_error(array('message' => 'Motorisation invalide.'));
    }
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
        wp_send_json_error(array('message' => 'Date invalide.'));
    }
    if (strtotime($date) < strtotime(date('Y-m-d'))) {
        wp_send_json_error(array('message' => 'Impossible de réserver une date passée.'));
    }
    if (!in_array((int) date('N', strtotime($date)), $cfg['open_days'], true)) {
        wp_send_json_error(array('message' => 'Les installations ont lieu du mercredi au samedi uniquement.'));
    }
    if (moovika_rdv_in_vacances($date)) {
        wp_send_json_error(array('message' => 'Cette date correspond à une période de congés.'));
    }
    if (!in_array($heure, moovika_rdv_slots(), true)) {
        wp_send_json_error(array('message' => 'Créneau horaire invalide.'));
    }

    // Chevauchement
    $dur = (int) $cfg['duration'];
    $reqStart = moovika_rdv_to_min($heure);
    $existing = $wpdb->get_col($wpdb->prepare("SELECT heure_rdv FROM $table WHERE date_rdv = %s", $date));
    foreach ((array) $existing as $e) {
        $es = moovika_rdv_to_min($e);
        if ($reqStart < $es + $dur && $es < $reqStart + $dur) {
            wp_send_json_error(array('message' => 'Ce créneau chevauche un rendez-vous existant.'));
        }
    }

    $row = array(
        'nom'             => $nom,
        'prenom'          => $prenom,
        'email'           => $email,
        'adresse'         => $adresse,
        'telephone'       => $tel,
        'vehicule_marque' => $marque,
        'vehicule_modele' => $modele,
        'motorisation'    => $motor,
        'date_rdv'        => $date,
        'heure_rdv'       => $heure,
        'reminder_sent'   => 0,
    );
    $ok = $wpdb->insert($table, $row, array('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%d'));
    if ($ok === false) {
        wp_send_json_error(array('message' => 'Erreur lors de l\'enregistrement.'));
    }

    // E-mail de confirmation au client
    moovika_rdv_send_client_mail($row, 'confirmation');

    // Notification à l'administrateur
    $admin_email = get_option('admin_email');
    if ($admin_email) {
        $subject = 'Nouveau RDV installation — ' . $date . ' ' . $heure;
        $body  = "Nouveau rendez-vous d'installation :\n\n";
        $body .= "Date     : $date à $heure (durée " . moovika_rdv_duration_label() . ")\n";
        $body .= "Client   : $prenom $nom\n";
        $body .= "E-mail   : $email\n";
        $body .= "Téléphone: $tel\n";
        $body .= "Adresse  : $adresse\n";
        $body .= "Véhicule : $marque $modele (" . ucfirst($motor) . ")\n";
        wp_mail($admin_email, $subject, $body, moovika_rdv_mail_headers());
    }

    // WooCommerce (optionnel)
    $redirect = '';
    if ($product > 0 && function_exists('WC') && WC()->cart) {
        WC()->cart->add_to_cart($product);
        $redirect = wc_get_checkout_url();
    }

    wp_send_json_success(array(
        'message'  => 'Rendez-vous confirmé ! Un e-mail de confirmation vous a été envoyé.',
        'redirect' => $redirect,
    ));
}

/* E-mail client : type = 'confirmation' ou 'rappel' */
function moovika_rdv_send_client_mail($r, $type) {
    if (empty($r['email']) || !is_email($r['email'])) return;
    $dateFr = moovika_rdv_format_fr($r['date_rdv']);
    if ($type === 'rappel') {
        $subject = 'Rappel — votre rendez-vous demain à ' . $r['heure_rdv'];
        $intro   = "Petit rappel : votre rendez-vous a lieu demain.";
    } else {
        $subject = 'Confirmation de votre rendez-vous — ' . $r['date_rdv'] . ' à ' . $r['heure_rdv'];
        $intro   = "Votre rendez-vous est bien confirmé.";
    }
    $body  = "Bonjour " . $r['prenom'] . ",\n\n";
    $body .= $intro . "\n\n";
    $body .= "Date     : " . $dateFr . " à " . $r['heure_rdv'] . "\n";
    $body .= "Durée    : " . moovika_rdv_duration_label() . "\n";
    $body .= "Véhicule : " . $r['vehicule_marque'] . " " . $r['vehicule_modele'] . "\n\n";
    $body .= "Lieu de l'installation :\n";
    $body .= "MOOVIKA SARL\n";
    $body .= "6 Allée Rodolphe Piguet, 77400 Lagny-sur-Marne\n\n";
    $body .= "Pour toute modification, répondez simplement à cet e-mail.\n\n";
    $body .= "À bientôt,\n" . get_bloginfo('name');
    $headers = moovika_rdv_mail_headers();
    if ($type === 'confirmation') {
        $headers[] = 'Cc: contact@moovika.fr';
    }
    wp_mail($r['email'], $subject, $body, $headers);
}

/* ------------------------------------------------------------
 * 3bis) Rappel automatique J-1 (WP-Cron quotidien)
 * ---------------------------------------------------------- */
add_action('init', function () {
    if (!wp_next_scheduled('moovika_rdv_reminder_cron')) {
        wp_schedule_event(time(), 'daily', 'moovika_rdv_reminder_cron');
    }
});
add_action('moovika_rdv_reminder_cron', 'moovika_rdv_send_reminders');
function moovika_rdv_send_reminders() {
    global $wpdb;
    $table = $wpdb->prefix . 'moovika_rdv';
    $tomorrow = date('Y-m-d', strtotime('+1 day'));
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM $table WHERE date_rdv = %s AND reminder_sent = 0 AND email <> ''", $tomorrow
    ), ARRAY_A);
    foreach ((array) $rows as $r) {
        moovika_rdv_send_client_mail($r, 'rappel');
        $wpdb->update($table, array('reminder_sent' => 1), array('id' => $r['id']), array('%d'), array('%d'));
    }
}

/* ------------------------------------------------------------
 * 4) Shortcode [rdv_calendrier]
 * ---------------------------------------------------------- */
add_shortcode('rdv_calendrier', 'moovika_rdv_shortcode');
function moovika_rdv_shortcode($atts) {
    $atts = shortcode_atts(array('product_id' => 0), $atts);
    $cfg  = moovika_rdv_config();

    $config = '<script>window.MOOVIKA_RDV = ' . wp_json_encode(array(
        'ajaxurl'       => admin_url('admin-ajax.php'),
        'nonce'         => wp_create_nonce('moovika_rdv_nonce'),
        'product_id'    => intval($atts['product_id']),
        'isAdmin'       => current_user_can('manage_options'),
        'slots'         => moovika_rdv_slots(),
        'durationMin'   => intval($cfg['duration']),
        'openDays'      => array_values($cfg['open_days']),
        'durationLabel' => moovika_rdv_duration_label(),
    )) . ';</script>';

    $html = <<<'HTML'
<style>
#mvk-rdv-app{max-width:760px;margin:0 auto;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:#1f2933;}
#mvk-rdv-app *{box-sizing:border-box;}
#mvk-rdv-app .mvk-intro{background:#eef0f7;border:1px solid #d7dcef;border-left:4px solid #374084;border-radius:10px;padding:14px 18px;margin-bottom:22px;font-size:.92rem;line-height:1.55;}
#mvk-rdv-app .mvk-intro h4{margin:0 0 8px;color:#374084;font-size:1.05rem;}
#mvk-rdv-app .mvk-intro ul{margin:0;padding-left:20px;}
#mvk-rdv-app .mvk-intro li{margin-bottom:6px;}
#mvk-rdv-app .mvk-intro strong{color:#374084;}
#mvk-rdv-app .mvk-intro .mvk-warn{color:#b25b00;font-weight:600;}
#mvk-rdv-app .mvk-cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;}
#mvk-rdv-app .mvk-title{font-size:1.25rem;font-weight:700;text-transform:capitalize;}
#mvk-rdv-app .mvk-nav{background:linear-gradient(135deg,#3f4992,#374084);color:#fff;border:none;width:38px;height:38px;border-radius:8px;font-size:1.3rem;line-height:1;cursor:pointer;}
#mvk-rdv-app .mvk-nav:hover{background:#2c3468;}
#mvk-rdv-app .mvk-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;}
#mvk-rdv-app .mvk-dow-cell{text-align:center;font-weight:600;font-size:.8rem;color:#7b8794;padding:4px 0;}
#mvk-rdv-app .mvk-day{min-height:84px;border:1px solid #e4e7eb;border-radius:8px;padding:5px;background:#fff;position:relative;transition:.12s;}
#mvk-rdv-app .mvk-day[data-date]{cursor:pointer;}
#mvk-rdv-app .mvk-day[data-date]:hover{border-color:#374084;box-shadow:0 2px 8px rgba(55,64,132,.15);}
#mvk-rdv-app .mvk-empty{background:transparent;border:none;}
#mvk-rdv-app .mvk-past{background:#f5f7fa;color:#cbd2d9;}
#mvk-rdv-app .mvk-closed{background:#f5f7fa;color:#cbd2d9;cursor:not-allowed;}
#mvk-rdv-app .mvk-vacday{background:#fff8e1;border-color:#f3d27a;cursor:not-allowed;}
#mvk-rdv-app .mvk-full{cursor:not-allowed;background:#f5f7fa;}
#mvk-rdv-app .mvk-today .mvk-daynum{background:#374084;color:#fff;border-radius:50%;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;}
#mvk-rdv-app .mvk-daynum{font-size:.8rem;font-weight:600;}
#mvk-rdv-app .mvk-badges{margin-top:4px;display:flex;flex-direction:column;gap:3px;}
#mvk-rdv-app .mvk-badge{background:linear-gradient(135deg,#404a93,#374084);color:#fff;font-size:.68rem;font-weight:600;border-radius:4px;padding:2px 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
#mvk-rdv-app .mvk-vac{display:inline-block;background:#e0a800;color:#fff;font-size:.62rem;font-weight:600;border-radius:4px;padding:2px 4px;}
#mvk-rdv-app .mvk-modal{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:99999;padding:16px;}
#mvk-rdv-app .mvk-modal[hidden]{display:none;}
#mvk-rdv-app .mvk-modal-box{background:#fff;border-radius:14px;padding:24px;max-width:440px;width:100%;max-height:90vh;overflow:auto;position:relative;}
#mvk-rdv-app .mvk-modal-box h3{margin:0 0 4px;font-size:1.2rem;}
#mvk-rdv-app .mvk-modal-date{margin:0 0 2px;color:#374084;font-weight:600;text-transform:capitalize;}
#mvk-rdv-app .mvk-duree{margin:0 0 16px;font-size:.8rem;color:#7b8794;}
#mvk-rdv-app .mvk-close,#mvk-rdv-app .mvk-close-detail{position:absolute;top:12px;right:14px;border:none;background:none;font-size:1.6rem;line-height:1;cursor:pointer;color:#7b8794;}
#mvk-rdv-app .mvk-field{margin-bottom:12px;}
#mvk-rdv-app .mvk-field label{display:block;font-size:.82rem;font-weight:600;margin-bottom:4px;}
#mvk-rdv-app .mvk-field input,#mvk-rdv-app .mvk-field select{width:100%;padding:9px 11px;border:1px solid #cbd2d9;border-radius:8px;font-size:.92rem;}
#mvk-rdv-app .mvk-field input:focus,#mvk-rdv-app .mvk-field select:focus{outline:none;border-color:#374084;}
#mvk-rdv-app .mvk-row{display:flex;gap:12px;}
#mvk-rdv-app .mvk-row .mvk-field{flex:1;}
#mvk-rdv-app .mvk-submit{width:100%;background:linear-gradient(135deg,#3f4992,#374084);color:#fff;border:none;padding:12px;border-radius:8px;font-size:1rem;font-weight:700;cursor:pointer;margin-top:4px;}
#mvk-rdv-app .mvk-submit:hover{background:#2c3468;}
#mvk-rdv-app .mvk-submit:disabled{opacity:.6;cursor:not-allowed;}
#mvk-rdv-app .mvk-msg{padding:9px 11px;border-radius:8px;font-size:.85rem;margin-bottom:12px;}
#mvk-rdv-app .mvk-msg.ok{background:#e3f9e5;color:#207227;}
#mvk-rdv-app .mvk-msg.err{background:#ffeeee;color:#374084;}
#mvk-rdv-app .mvk-detail-card{border:1px solid #e4e7eb;border-radius:10px;padding:14px 16px;margin-bottom:12px;font-size:.92rem;line-height:1.7;}
#mvk-rdv-app .mvk-detail-card strong{color:#52606d;font-weight:600;}
#mvk-rdv-app .mvk-detail-card a{color:#374084;font-weight:600;text-decoration:none;}
@media(max-width:520px){#mvk-rdv-app .mvk-day{min-height:62px;}#mvk-rdv-app .mvk-badge{font-size:.6rem;}}
</style>

<div id="mvk-rdv-app">
  <div class="mvk-intro">
    <h4>Réservez votre créneau d'installation</h4>
    <ul>
      <li>Sélectionnez un jour disponible dans le calendrier, puis remplissez le formulaire pour réserver votre rendez-vous.</li>
      <li>Le <strong>paiement de l'installation</strong> peut se faire directement en ligne via la boutique, ou sur place par carte bancaire après l'intervention.</li>
      <li class="mvk-warn">⚠️ L'achat de votre dashcam BlackVue doit être effectué <u>avant</u> la prise de rendez-vous.</li>
    </ul>
  </div>
  <div class="mvk-cal"></div>
  <div class="mvk-modal" hidden>
    <div class="mvk-modal-box">
      <button type="button" class="mvk-close" aria-label="Fermer">×</button>
      <h3>Prendre rendez-vous</h3>
      <p class="mvk-modal-date"></p>
      <p class="mvk-duree"></p>
      <div class="mvk-msg" hidden></div>
      <div class="mvk-row">
        <div class="mvk-field"><label>Nom</label><input type="text" id="mvk-nom" autocomplete="family-name"></div>
        <div class="mvk-field"><label>Prénom</label><input type="text" id="mvk-prenom" autocomplete="given-name"></div>
      </div>
      <div class="mvk-field"><label>E-mail</label><input type="email" id="mvk-email" autocomplete="email" inputmode="email"></div>
      <div class="mvk-field"><label>Téléphone</label><input type="tel" id="mvk-telephone" autocomplete="tel" inputmode="tel"></div>
      <div class="mvk-field"><label>Adresse</label><input type="text" id="mvk-adresse" autocomplete="street-address"></div>
      <div class="mvk-row">
        <div class="mvk-field"><label>Marque</label><input type="text" id="mvk-marque"></div>
        <div class="mvk-field"><label>Modèle</label><input type="text" id="mvk-modele"></div>
      </div>
      <div class="mvk-field">
        <label>Motorisation</label>
        <select id="mvk-motor">
          <option value="">— Choisir —</option>
          <option value="thermique">Thermique</option>
          <option value="hybride">Hybride</option>
          <option value="electrique">Électrique</option>
        </select>
      </div>
      <div class="mvk-field">
        <label>Créneau horaire</label>
        <select id="mvk-heure"></select>
      </div>
      <button type="button" class="mvk-submit">Confirmer le rendez-vous</button>
    </div>
  </div>
  <div class="mvk-modal mvk-detail" hidden>
    <div class="mvk-modal-box">
      <button type="button" class="mvk-close-detail" aria-label="Fermer">×</button>
      <h3>Détails du rendez-vous</h3>
      <p class="mvk-modal-date mvk-detail-date"></p>
      <div class="mvk-detail-body"></div>
    </div>
  </div>
</div>

<script>
(function(){
  var CFG = window.MOOVIKA_RDV || {};
  var SLOTS = CFG.slots || [];
  var DUR   = CFG.durationMin || 180;
  var ODAYS = CFG.openDays || [3,4,5,6];
  var MOIS  = ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'];
  var JOURS = ['Lun','Mar','Mer','Jeu','Ven','Sam','Dim'];

  var app = document.getElementById('mvk-rdv-app');
  if(!app) return;
  var calBox = app.querySelector('.mvk-cal');
  var modal  = app.querySelector('.mvk-modal');
  var detailModal = app.querySelector('.mvk-detail');

  var view = new Date(); view.setDate(1); view.setHours(0,0,0,0);
  var appts = [];
  var vacances = [];

  function pad(n){return (n<10?'0':'')+n;}
  function ymd(d){return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate());}
  function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});}
  function escAttr(s){return String(s==null?'':s).replace(/"/g,'"');}
  function val(sel){return (app.querySelector(sel).value||'').trim();}
  function toMin(t){var p=t.split(':');return (+p[0])*60+(+p[1]);}
  function toHHMM(m){return pad(Math.floor(m/60))+':'+pad(m%60);}
  function addMin(t,m){return toHHMM(toMin(t)+m);}
  function validEmail(e){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);}
  function apptsFor(ds){return appts.filter(function(a){return a.date===ds;});}
  function isVacation(ds){return vacances.some(function(p){return ds>=p.start && ds<=p.end;});}
  function frDate(ds){var p=ds.split('-');var d=new Date(p[0],p[1]-1,p[2]);return JOURS[(d.getDay()+6)%7]+' '+parseInt(p[2],10)+' '+MOIS[p[1]-1]+' '+p[0];}

  function load(){
    var fd=new FormData(); fd.append('action','moovika_get_rdv');
    fetch(CFG.ajaxurl,{method:'POST',body:fd})
      .then(function(r){return r.json();})
      .then(function(j){
        if(j&&j.success){ appts=j.data.appts||[]; vacances=j.data.vacances||[]; }
        else { appts=[]; vacances=[]; }
        render();
      })
      .catch(function(){ appts=[]; vacances=[]; render(); });
  }

  function render(){
    var y=view.getFullYear(), m=view.getMonth();
    var today=new Date(); today.setHours(0,0,0,0);
    var h='';
    h+='<div class="mvk-cal-head">';
    h+='<button type="button" class="mvk-nav" data-nav="-1">‹</button>';
    h+='<span class="mvk-title">'+MOIS[m]+' '+y+'</span>';
    h+='<button type="button" class="mvk-nav" data-nav="1">›</button>';
    h+='</div>';
    h+='<div class="mvk-grid mvk-dow">';
    JOURS.forEach(function(j){h+='<div class="mvk-dow-cell">'+j+'</div>';});
    h+='</div>';
    h+='<div class="mvk-grid mvk-days">';
    var first=new Date(y,m,1);
    var lead=(first.getDay()+6)%7;
    for(var i=0;i<lead;i++) h+='<div class="mvk-day mvk-empty"></div>';
    var dim=new Date(y,m+1,0).getDate();
    for(var d=1;d<=dim;d++){
      var date=new Date(y,m,d); date.setHours(0,0,0,0);
      var ds=ymd(date);
      var isPast=date<today;
      var isOpen=ODAYS.indexOf(date.getDay())!==-1;
      var isVac=isVacation(ds);
      var list=apptsFor(ds);
      var cls='mvk-day';
      if(isPast) cls+=' mvk-past';
      if(isVac){ cls+=' mvk-vacday'; } else if(!isOpen){ cls+=' mvk-closed'; }
      if(ds===ymd(today)) cls+=' mvk-today';
      var hasAppt=list.length>0;
      var freeFuture=!isPast && isOpen && !isVac;
      if(hasAppt && !CFG.isAdmin) cls+=' mvk-full';
      var clickable = CFG.isAdmin ? (freeFuture || hasAppt) : (freeFuture && !hasAppt);
      h+='<div class="'+cls+'"'+(clickable?' data-date="'+ds+'"':'')+'>';
      h+='<span class="mvk-daynum">'+d+'</span>';
      if(isVac && isOpen && !isPast){
        h+='<div class="mvk-badges"><span class="mvk-vac">Congés</span></div>';
      } else if(list.length){
        h+='<div class="mvk-badges">';
        list.forEach(function(a){ h+='<span class="mvk-badge">'+a.heure+' '+esc(a.label)+'</span>'; });
        h+='</div>';
      }
      h+='</div>';
    }
    h+='</div>';
    calBox.innerHTML=h;
  }

  function openDay(ds){
    app.dataset.selDate=ds;
    app.querySelector('.mvk-modal-date').textContent=frDate(ds);
    var booked=apptsFor(ds).map(function(a){return toMin(a.heure);});
    var free=SLOTS.filter(function(s){
      var st=toMin(s);
      return !booked.some(function(b){ return st < b+DUR && b < st+DUR; });
    });
    var sel=app.querySelector('#mvk-heure');
    var submit=app.querySelector('.mvk-submit');
    if(free.length===0){
      sel.innerHTML='<option value="">— Complet —</option>';
      submit.disabled=true;
    } else {
      sel.innerHTML=free.map(function(s){return '<option value="'+s+'">'+s+' → '+addMin(s,DUR)+'</option>';}).join('');
      submit.disabled=false;
    }
    showMsg('');
    modal.hidden=false;
  }

  function showMsg(txt,type){
    var m=app.querySelector('.mvk-msg');
    if(!txt){ m.hidden=true; m.textContent=''; m.className='mvk-msg'; return; }
    m.hidden=false; m.textContent=txt; m.className='mvk-msg '+(type||'');
  }
  function clearForm(){
    ['#mvk-nom','#mvk-prenom','#mvk-email','#mvk-telephone','#mvk-adresse','#mvk-marque','#mvk-modele'].forEach(function(s){app.querySelector(s).value='';});
    app.querySelector('#mvk-motor').value='';
  }

  // Détails (admin)
  function renderDetail(r){
    var tel=r.telephone||'', mail=r.email||'';
    var s='<div class="mvk-detail-card">';
    s+='<div><strong>Créneau :</strong> '+esc(r.heure_rdv)+'</div>';
    s+='<div><strong>Client :</strong> '+esc(r.nom)+' '+esc(r.prenom)+'</div>';
    if(mail) s+='<div><strong>E-mail :</strong> <a href="mailto:'+escAttr(mail)+'">'+esc(mail)+'</a></div>';
    s+='<div><strong>Téléphone :</strong> <a href="tel:'+escAttr(tel)+'">'+esc(tel)+'</a></div>';
    s+='<div><strong>Adresse :</strong> '+esc(r.adresse)+'</div>';
    s+='<div><strong>Véhicule :</strong> '+esc(r.vehicule_marque)+' '+esc(r.vehicule_modele)+'</div>';
    s+='<div><strong>Motorisation :</strong> '+esc(r.motorisation)+'</div>';
    s+='</div>';
    return s;
  }
  function openDetail(ds){
    app.querySelector('.mvk-detail-date').textContent=frDate(ds);
    var body=app.querySelector('.mvk-detail-body');
    body.innerHTML='<p>Chargement…</p>';
    detailModal.hidden=false;
    var fd=new FormData();
    fd.append('action','moovika_rdv_detail');
    fd.append('nonce',CFG.nonce);
    fd.append('date',ds);
    fetch(CFG.ajaxurl,{method:'POST',body:fd})
      .then(function(r){return r.json();})
      .then(function(j){
        if(j&&j.success&&j.data&&j.data.length){ body.innerHTML=j.data.map(renderDetail).join(''); }
        else if(j&&j.success){ body.innerHTML='<p>Aucun détail pour cette date.</p>'; }
        else { body.innerHTML='<p>'+((j&&j.data&&j.data.message)||'Accès refusé.')+'</p>'; }
      })
      .catch(function(){ body.innerHTML='<p>Erreur de chargement.</p>'; });
  }

  // Durée affichée
  var dn=app.querySelector('.mvk-duree');
  if(dn) dn.textContent='Durée de l\'intervention : '+(CFG.durationLabel||'');

  app.addEventListener('click',function(e){
    var nav=e.target.closest('[data-nav]');
    if(nav){ view.setMonth(view.getMonth()+parseInt(nav.dataset.nav,10)); render(); return; }
    var day=e.target.closest('[data-date]');
    if(day){
      var ds=day.dataset.date;
      if(CFG.isAdmin && apptsFor(ds).length>0){ openDetail(ds); }
      else { openDay(ds); }
      return;
    }
  });

  app.querySelector('.mvk-close-detail').addEventListener('click',function(){detailModal.hidden=true;});
  detailModal.addEventListener('click',function(e){ if(e.target===detailModal) detailModal.hidden=true; });
  app.querySelector('.mvk-close').addEventListener('click',function(){modal.hidden=true;});
  modal.addEventListener('click',function(e){ if(e.target===modal) modal.hidden=true; });

  app.querySelector('.mvk-submit').addEventListener('click',function(){
    var btn=this;
    var ds=app.dataset.selDate;
    var nom=val('#mvk-nom'), prenom=val('#mvk-prenom'), email=val('#mvk-email'), tel=val('#mvk-telephone'), adresse=val('#mvk-adresse');
    var marque=val('#mvk-marque'), modele=val('#mvk-modele'), motor=val('#mvk-motor'), heure=val('#mvk-heure');
    if(!nom||!prenom||!email||!tel||!adresse||!marque||!modele||!motor||!heure){
      showMsg('Merci de remplir tous les champs.','err'); return;
    }
    if(!validEmail(email)){ showMsg('Adresse e-mail invalide.','err'); return; }
    var fd=new FormData();
    fd.append('action','moovika_save_rdv');
    fd.append('nonce',CFG.nonce);
    fd.append('date',ds);
    fd.append('nom',nom); fd.append('prenom',prenom); fd.append('email',email);
    fd.append('telephone',tel); fd.append('adresse',adresse);
    fd.append('marque',marque); fd.append('modele',modele);
    fd.append('motorisation',motor); fd.append('heure',heure);
    fd.append('product_id',CFG.product_id||0);

    btn.disabled=true; showMsg('Envoi en cours…','');
    fetch(CFG.ajaxurl,{method:'POST',body:fd})
      .then(function(r){return r.json();})
      .then(function(j){
        if(j&&j.success){
          showMsg(j.data.message||'Rendez-vous confirmé !','ok');
          if(j.data.redirect){ setTimeout(function(){window.location=j.data.redirect;},1100); }
          else { setTimeout(function(){ modal.hidden=true; clearForm(); btn.disabled=false; load(); },1100); }
        } else {
          showMsg((j&&j.data&&j.data.message)||'Une erreur est survenue.','err');
          btn.disabled=false;
        }
      })
      .catch(function(){ showMsg('Erreur réseau.','err'); btn.disabled=false; });
  });

  load();
})();
</script>
HTML;

    return $config . $html;
}

/* ------------------------------------------------------------
 * 5) Page admin : RDV (liste / créer / modifier) + vacances
 * ---------------------------------------------------------- */
add_action('admin_menu', function () {
    add_menu_page('Rendez-vous', 'Rendez-vous', 'manage_options', 'moovika-rdv', 'moovika_rdv_admin_page', 'dashicons-calendar-alt', 26);
});
function moovika_rdv_admin_page() {
    if (!current_user_can('manage_options')) return;
    global $wpdb;
    $table = $wpdb->prefix . 'moovika_rdv';

    /* Suppression d'un RDV */
    if (isset($_GET['del']) && check_admin_referer('mvk_del_' . intval($_GET['del']))) {
        $wpdb->delete($table, array('id' => intval($_GET['del'])), array('%d'));
        echo '<div class="notice notice-success is-dismissible"><p>Rendez-vous supprimé.</p></div>';
    }
    /* Ajout congés */
    if (isset($_POST['mvk_vac_action']) && $_POST['mvk_vac_action'] === 'add' && check_admin_referer('mvk_vacances')) {
        $s = sanitize_text_field($_POST['mvk_vac_start'] ?? '');
        $e = sanitize_text_field($_POST['mvk_vac_end'] ?? '');
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $s) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $e) && $e >= $s) {
            $vac = moovika_rdv_get_vacances();
            $vac[] = array('start' => $s, 'end' => $e);
            update_option('moovika_rdv_vacances', array_values($vac));
            echo '<div class="notice notice-success is-dismissible"><p>Période de congés ajoutée.</p></div>';
        } else {
            echo '<div class="notice notice-error is-dismissible"><p>Dates invalides (fin ≥ début).</p></div>';
        }
    }
    /* Suppression congés */
    if (isset($_GET['delvac']) && check_admin_referer('mvk_delvac_' . intval($_GET['delvac']))) {
        $vac = moovika_rdv_get_vacances();
        $i = intval($_GET['delvac']);
        if (isset($vac[$i])) { unset($vac[$i]); update_option('moovika_rdv_vacances', array_values($vac)); }
        echo '<div class="notice notice-success is-dismissible"><p>Période supprimée.</p></div>';
    }
    /* Création / modification d'un RDV */
    if (isset($_POST['mvk_rdv_save']) && check_admin_referer('mvk_rdv_save')) {
        $id   = intval($_POST['mvk_rdv_id'] ?? 0);
        $data = array(
            'nom'             => sanitize_text_field($_POST['nom'] ?? ''),
            'prenom'          => sanitize_text_field($_POST['prenom'] ?? ''),
            'email'           => sanitize_email($_POST['email'] ?? ''),
            'adresse'         => sanitize_text_field($_POST['adresse'] ?? ''),
            'telephone'       => sanitize_text_field($_POST['telephone'] ?? ''),
            'vehicule_marque' => sanitize_text_field($_POST['marque'] ?? ''),
            'vehicule_modele' => sanitize_text_field($_POST['modele'] ?? ''),
            'motorisation'    => sanitize_text_field($_POST['motorisation'] ?? ''),
            'date_rdv'        => sanitize_text_field($_POST['date_rdv'] ?? ''),
            'heure_rdv'       => substr(sanitize_text_field($_POST['heure_rdv'] ?? ''), 0, 5),
            'reminder_sent'   => 0,
        );
        $fmt = array('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%d');
        if (!$data['nom'] || !$data['date_rdv'] || !$data['heure_rdv']) {
            echo '<div class="notice notice-error is-dismissible"><p>Nom, date et heure sont obligatoires.</p></div>';
        } elseif (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['date_rdv'])) {
            echo '<div class="notice notice-error is-dismissible"><p>Date invalide.</p></div>';
        } elseif ($id > 0) {
            $res = $wpdb->update($table, $data, array('id' => $id), $fmt, array('%d'));
            echo ($res === false)
                ? '<div class="notice notice-error is-dismissible"><p>Échec : un autre RDV occupe peut-être déjà ce créneau (date + heure).</p></div>'
                : '<div class="notice notice-success is-dismissible"><p>Fiche mise à jour.</p></div>';
        } else {
            $res = $wpdb->insert($table, $data, $fmt);
            echo ($res === false)
                ? '<div class="notice notice-error is-dismissible"><p>Échec : un RDV existe déjà sur ce créneau (date + heure).</p></div>'
                : '<div class="notice notice-success is-dismissible"><p>Rendez-vous créé.</p></div>';
        }
    }

    /* Pré-remplissage en mode édition */
    $edit = null;
    if (isset($_GET['edit'])) {
        $edit = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", intval($_GET['edit'])), ARRAY_A);
    }
    $v   = function ($k) use ($edit) { return $edit && isset($edit[$k]) ? esc_attr($edit[$k]) : ''; };
    $mot = $edit ? $edit['motorisation'] : '';

    echo '<div class="wrap"><h1>Rendez-vous Moovika</h1>';

    /* ---- Créer / modifier ---- */
    echo '<h2 style="margin-top:24px;">➕ ' . ($edit ? 'Modifier le rendez-vous #' . intval($edit['id']) : 'Créer un rendez-vous') . '</h2>';
    echo '<p>Créez une intervention (y compris une <strong>date passée</strong>, pour compléter l\'historique) ou modifiez une fiche existante. Un RDV passé n\'envoie ni confirmation ni rappel.</p>';
    echo '<form method="post" style="background:#fff;border:1px solid #dcdcde;border-radius:8px;padding:16px 18px;max-width:780px;margin-bottom:34px;">';
    wp_nonce_field('mvk_rdv_save');
    echo '<input type="hidden" name="mvk_rdv_save" value="1">';
    echo '<input type="hidden" name="mvk_rdv_id" value="' . ($edit ? intval($edit['id']) : 0) . '">';
    echo '<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;">';
    echo '<p><label><strong>Nom *</strong><br><input type="text" name="nom" value="' . $v('nom') . '" class="regular-text" required></label></p>';
    echo '<p><label><strong>Prénom</strong><br><input type="text" name="prenom" value="' . $v('prenom') . '" class="regular-text"></label></p>';
    echo '<p><label>E-mail<br><input type="email" name="email" value="' . $v('email') . '" class="regular-text"></label></p>';
    echo '<p><label>Téléphone<br><input type="text" name="telephone" value="' . $v('telephone') . '" class="regular-text"></label></p>';
    echo '<p style="grid-column:1/3;"><label>Adresse<br><input type="text" name="adresse" value="' . $v('adresse') . '" style="width:100%;"></label></p>';
    echo '<p><label>Marque<br><input type="text" name="marque" value="' . ($edit ? esc_attr($edit['vehicule_marque']) : '') . '" class="regular-text"></label></p>';
    echo '<p><label>Modèle<br><input type="text" name="modele" value="' . ($edit ? esc_attr($edit['vehicule_modele']) : '') . '" class="regular-text"></label></p>';
    echo '<p><label>Motorisation<br><select name="motorisation">';
    echo '<option value="thermique"' . selected($mot, 'thermique', false) . '>Thermique</option>';
    echo '<option value="hybride"' . selected($mot, 'hybride', false) . '>Hybride</option>';
    echo '<option value="electrique"' . selected($mot, 'electrique', false) . '>Électrique</option>';
    echo '</select></label></p>';
    echo '<p></p>';
    echo '<p><label><strong>Date *</strong><br><input type="date" name="date_rdv" value="' . $v('date_rdv') . '" required></label></p>';
    echo '<p><label><strong>Heure *</strong><br><input type="time" name="heure_rdv" value="' . ($edit ? esc_attr(substr($edit['heure_rdv'], 0, 5)) : '') . '" required></label></p>';
    echo '</div>';
    echo '<p style="margin-bottom:0;"><button class="button button-primary">' . ($edit ? 'Mettre à jour la fiche' : 'Créer le rendez-vous') . '</button>';
    if ($edit) echo ' <a href="' . esc_url(admin_url('admin.php?page=moovika-rdv')) . '" class="button">Annuler</a>';
    echo '</p></form>';

    /* ---- Vacances ---- */
    echo '<h2>🏖️ Mode vacances / congés</h2>';
    echo '<p>Les jours de ces périodes sont affichés en jaune et non réservables.</p>';
    echo '<form method="post" style="margin-bottom:14px;">';
    wp_nonce_field('mvk_vacances');
    echo '<input type="hidden" name="mvk_vac_action" value="add">';
    echo 'Du <input type="date" name="mvk_vac_start" required> au <input type="date" name="mvk_vac_end" required> ';
    echo '<button class="button button-primary">Ajouter la période</button></form>';
    $vac = moovika_rdv_get_vacances();
    if ($vac) {
        echo '<table class="wp-list-table widefat fixed striped" style="max-width:520px;margin-bottom:34px;"><thead><tr><th>Début</th><th>Fin</th><th></th></tr></thead><tbody>';
        foreach ($vac as $i => $p) {
            $delv = wp_nonce_url(admin_url('admin.php?page=moovika-rdv&delvac=' . $i), 'mvk_delvac_' . $i);
            echo '<tr><td>' . esc_html($p['start']) . '</td><td>' . esc_html($p['end']) . '</td>';
            echo '<td><a href="' . esc_url($delv) . '" onclick="return confirm(\'Supprimer cette période ?\')">Supprimer</a></td></tr>';
        }
        echo '</tbody></table>';
    } else {
        echo '<p style="color:#7b8794;margin-bottom:34px;"><em>Aucune période de congés.</em></p>';
    }

    /* ---- Liste des RDV ---- */
    echo '<h2>📅 Liste des rendez-vous</h2>';
    $rows = $wpdb->get_results("SELECT * FROM $table ORDER BY date_rdv DESC, heure_rdv DESC", ARRAY_A);
    echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
    echo '<th>Date</th><th>Heure</th><th>Nom</th><th>Prénom</th><th>E-mail</th><th>Téléphone</th><th>Adresse</th><th>Véhicule</th><th>Moteur</th><th>Actions</th>';
    echo '</tr></thead><tbody>';
    if ($rows) {
        foreach ($rows as $r) {
            $del  = wp_nonce_url(admin_url('admin.php?page=moovika-rdv&del=' . $r['id']), 'mvk_del_' . $r['id']);
            $edt  = admin_url('admin.php?page=moovika-rdv&edit=' . $r['id']);
            echo '<tr>';
            echo '<td>' . esc_html($r['date_rdv']) . '</td>';
            echo '<td>' . esc_html($r['heure_rdv']) . '</td>';
            echo '<td>' . esc_html($r['nom']) . '</td>';
            echo '<td>' . esc_html($r['prenom']) . '</td>';
            echo '<td>' . esc_html($r['email']) . '</td>';
            echo '<td>' . esc_html($r['telephone']) . '</td>';
            echo '<td>' . esc_html($r['adresse']) . '</td>';
            echo '<td>' . esc_html($r['vehicule_marque'] . ' ' . $r['vehicule_modele']) . '</td>';
            echo '<td>' . esc_html(ucfirst($r['motorisation'])) . '</td>';
            echo '<td><a href="' . esc_url($edt) . '">Modifier</a>  |  <a href="' . esc_url($del) . '" onclick="return confirm(\'Supprimer ce rendez-vous ?\')" style="color:#b32d2e;">Supprimer</a></td>';
            echo '</tr>';
        }
    } else {
        echo '<tr><td colspan="10">Aucun rendez-vous pour le moment.</td></tr>';
    }
    echo '</tbody></table></div>';
}

 

Comparaison Avant / Après

[bafg id= »8241″]

Spécifications techniques

Caractéristiques du modèle ÉLITE 9-2CH
Nom du modèle ÉLITE 9-2CH
Canal 2CH
Points forts 4K UHD HDR + 2K QHD HDR, Wi-Fi 2,4-5 GHz, Cloud (données non incluses), Mode parking à économie d’énergie
Dimensions / Poids du produit Avant : Longueur 129,7 mm (5,12 po) × Largeur 40 mm (1,58 po) × Hauteur 55,3 mm (2,18 po), 203,2 g (0,45 lb)
Arrière : Longueur 67 mm (2,64 po) × Largeur 29 mm (1,14 po) × Hauteur 39,1 mm (1,54 po), 38,8 g (0,09 lb)
Connexion de la caméra arrière Câble coaxial
Prise en charge de la mémoire carte microSD jusqu’à 1 To
Enregistrement intelligent des événements Impact de conduite
Impact de stationnement
Survitesse
Accélération brutale
Freinage brutal
Virage brutal
(y compris 10 secondes de tampon avant l’événement)
Mode stationnement Économie d’énergie (< 1 mA)
Détection de mouvement
en accéléré
Notifications vocales d’événements en mode stationnement OUI
Systèmes de protection Arrêt de la minuterie : 1 à 48 h
Arrêt basse tension : véhicule de tourisme 11,8 à 12,5 V ou véhicule lourd 22,8 à 24 V
Arrêt haute température (70 °C)
Capteur d’imagerie Avant et arrière : Capteur CMOS STARVIS 2 (IMX675)
Angle de vision Avant et arrière : Diagonale 142°, Horizontale 118°, Verticale 62°
Résolution / Fréquence d’images Avant : 4K UHD HDR à 30 images/s
Arrière : 2K QHD HDR à 30 images/s
* La fréquence d’images peut varier lors de la diffusion en continu via Wi-Fi
Codec vidéo H.264 (AVC)
Qualité d’image et débit binaire avant/arrière Le plus élevé (extrême) : 25 Mbps
Le plus élevé (par défaut) : 20 Mbps
Élevé : 15 Mbps
Normal : 10 Mbps
Format gratuit OUI (adaptatif)
Protection contre l’écrasement des fichiers d’événements OUI (jusqu’à 50)
Alerte de panne de carte SD OUI
Redémarrage programmé OUI
Extension de fichier vidéo MP4
Wi-Fi Intégré (802.11ac – 2,4-5 GHz)
Compatible avec le Cloud OUI
GPS Intégré (double bande : GPS, GLONASS)
Microphone Intégré
Haut-parleur Intégré
Capteur d’impact Capteur d’accélération à 3 axes
Indicateurs LED État d’enregistrement, connectivité GPS, connectivité A/C (application/cloud)
Bouton Bouton d’alimentation : Appuyez brièvement pour allumer / Appuyez longuement pour éteindre.
Capteur tactile : Appuyez sur le capteur tactile pour déclencher l’enregistrement manuel selon les paramètres du micrologiciel.
Bouton de télécommande Bluetooth (en option) : Enregistrement manuel.
Température de fonctionnement -20 °C − 65 °C (-4 °F − 149 °F)
Température de stockage -20 °C − 80 °C (-4 °F − 176 °F)
Coupure haute température Environ 75 °C (167 °F)
Batterie de secours Supercondensateur intégré
Puissance d’entrée DC 12V-24V (prise DC (Ø3,5 x Ø1,1)) vers fils (noir : GND / jaune : B+ / rouge : ACC)
Consommation d’énergie Mode normal (GPS activé/2 canaux) : 5,52 W (moyenne : 460 mA/12 V)
Mode parking (GPS désactivé/2 canaux) : 4,32 W (moyenne : 360 mA/12 V)
Mode économie d’énergie : moins de 12 mW (0,012 W) (moyenne : < 1 mA/12 V)
* La consommation électrique réelle peut varier en fonction des conditions d’utilisation et de l’environnement.
Certifications Avant : Telec, IC(ISED), CE, FCC, RCM, RoHS, WEEE
Arrière : FCC, IC(ISED), CE, RoHS, WEEE
Logiciel BlackVue Viewer
* Windows 7 ou supérieur, Mac Sierra OS X (10.12) ou supérieur
FLEETA Web Viewer
* Chrome 71 ou supérieur, Safari 13.0 ou supérieur
Application Application BlackVue
* Android 10.0 ou supérieur, iOS 15.0 ou supérieur
Autres Système de gestion de fichiers adaptatif sans format
Bluetooth Intégré (V2.1+EDR/5.3)
LTE Externe (avec module LTE en option)
Garantie 2 ans

Informations complémentaires

Poids 0,9 kg
Dimensions 20 × 15 × 10 cm
Carte mémoire Go

64, 128, 256, 512

Avis

Il n’y a pas encore d’avis.

Soyez le premier à laisser votre avis sur “ELITE 9 2CH”

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *