1341 lines
58 KiB
PHP
1341 lines
58 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: Autobooking Driver Dashboard
|
|
* Description: Dashboard unificado del Conductor v2 — mapa, viaje, ganancias, métricas, historial, perfil y pánico.
|
|
* Version: 2.0.0
|
|
* Author: Autobooking
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
class ABDriverDashboard2 {
|
|
|
|
private static $instance = null;
|
|
const NAMESPACE = 'autobooking/v1';
|
|
const VERSION = '2.0.0';
|
|
|
|
public static function get_instance() {
|
|
if ( null === self::$instance ) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct() {
|
|
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue' ] );
|
|
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
|
|
add_shortcode( 'autobooking_driver_dashboard', [ $this, 'render' ] );
|
|
register_activation_hook( __FILE__, [ $this, 'activate' ] );
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────
|
|
ACTIVATION — create new tables
|
|
───────────────────────────────────────────────────────────── */
|
|
public function activate() {
|
|
global $wpdb;
|
|
$charset = $wpdb->get_charset_collate();
|
|
|
|
$sql = [];
|
|
|
|
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_incidents (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
driver_id BIGINT NOT NULL,
|
|
trip_id BIGINT NULL,
|
|
lat DECIMAL(10,7) NOT NULL DEFAULT 0,
|
|
lng DECIMAL(10,7) NOT NULL DEFAULT 0,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
chunks_count INT NOT NULL DEFAULT 0,
|
|
incident_at DATETIME NOT NULL,
|
|
resolved_at DATETIME NULL,
|
|
notes TEXT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY driver_id (driver_id)
|
|
) $charset;";
|
|
|
|
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_zone_alerts (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
title VARCHAR(160) NOT NULL DEFAULT '',
|
|
description TEXT NULL,
|
|
lat DECIMAL(10,7) NOT NULL DEFAULT 0,
|
|
lng DECIMAL(10,7) NOT NULL DEFAULT 0,
|
|
radius_km DECIMAL(5,2) NOT NULL DEFAULT 1.00,
|
|
bonus_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
bonus_currency CHAR(3) NOT NULL DEFAULT 'USD',
|
|
countdown_seconds INT NOT NULL DEFAULT 180,
|
|
type VARCHAR(20) NOT NULL DEFAULT 'hotspot',
|
|
active TINYINT(1) NOT NULL DEFAULT 1,
|
|
expires_at DATETIME NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id)
|
|
) $charset;";
|
|
|
|
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_zone_alert_responses (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
zone_id BIGINT NOT NULL,
|
|
driver_id BIGINT NOT NULL,
|
|
response VARCHAR(20) NOT NULL DEFAULT '',
|
|
responded_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY zone_driver (zone_id, driver_id)
|
|
) $charset;";
|
|
|
|
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_scheduled_trips (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
trip_uuid VARCHAR(64) NOT NULL DEFAULT '',
|
|
driver_id BIGINT NOT NULL,
|
|
passenger_name VARCHAR(160) NOT NULL DEFAULT '',
|
|
pickup_time DATETIME NOT NULL,
|
|
pickup_address VARCHAR(255) NOT NULL DEFAULT '',
|
|
dropoff_address VARCHAR(255) NOT NULL DEFAULT '',
|
|
estimated_fare DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
currency CHAR(3) NOT NULL DEFAULT 'USD',
|
|
confirmed TINYINT(1) NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
KEY driver_id (driver_id)
|
|
) $charset;";
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
|
foreach ( $sql as $s ) {
|
|
dbDelta( $s );
|
|
}
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────
|
|
ENQUEUE
|
|
───────────────────────────────────────────────────────────── */
|
|
public function enqueue() {
|
|
global $post;
|
|
if ( ! is_a( $post, 'WP_Post' ) || ! has_shortcode( $post->post_content, 'autobooking_driver_dashboard' ) ) {
|
|
return;
|
|
}
|
|
|
|
$base = plugin_dir_url( __FILE__ );
|
|
$ver = self::VERSION;
|
|
|
|
// Google Maps
|
|
// Google Maps API key is stored in wp_options (ab_gmaps_key) and set via Admin → Config.
|
|
// NEVER hardcode the key here — read it from the database at runtime.
|
|
$gmaps_key = get_option( 'ab_gmaps_key', '' );
|
|
|
|
// CSS
|
|
wp_enqueue_style(
|
|
'abd-dashboard',
|
|
$base . 'assets/driver-dashboard.css',
|
|
[],
|
|
$ver
|
|
);
|
|
|
|
// JS
|
|
wp_enqueue_script(
|
|
'abd-dashboard',
|
|
$base . 'assets/driver-dashboard.js',
|
|
[],
|
|
$ver,
|
|
true
|
|
);
|
|
|
|
// Build driver data from user meta
|
|
$user = wp_get_current_user();
|
|
$uid = $user->ID;
|
|
|
|
$driver_name = get_user_meta( $uid, 'driver_name', true );
|
|
if ( ! $driver_name ) {
|
|
$driver_name = get_user_meta( $uid, 'full_name', true );
|
|
}
|
|
if ( ! $driver_name ) {
|
|
$fn = get_user_meta( $uid, 'first_name', true );
|
|
$ln = get_user_meta( $uid, 'last_name', true );
|
|
$driver_name = trim( "$fn $ln" );
|
|
}
|
|
if ( ! $driver_name ) {
|
|
$driver_name = $user->display_name;
|
|
}
|
|
|
|
$photo = get_user_meta( $uid, 'photo_url', true );
|
|
if ( ! $photo ) {
|
|
$photo = get_user_meta( $uid, 'driver_photo', true );
|
|
}
|
|
|
|
$vehicle_type = get_user_meta( $uid, 'vehicle_type', true );
|
|
$vehicle_plate = get_user_meta( $uid, 'vehicle_plate', true );
|
|
if ( ! $vehicle_plate ) {
|
|
$vehicle_plate = get_user_meta( $uid, 'license_plate', true );
|
|
}
|
|
$insurance_expiry = get_user_meta( $uid, 'insurance_expiry', true );
|
|
$license_expiry = get_user_meta( $uid, 'license_expiry', true );
|
|
$inspection_expiry = get_user_meta( $uid, 'inspection_expiry', true );
|
|
|
|
wp_localize_script( 'abd-dashboard', 'AB_DRIVER_CFG', [
|
|
'nonce' => wp_create_nonce( 'wp_rest' ),
|
|
'api_root' => esc_url_raw( get_rest_url() ),
|
|
'user_id' => $uid,
|
|
'gmaps_key' => $gmaps_key,
|
|
'driver' => [
|
|
'name' => $driver_name,
|
|
'photo' => $photo ?: get_avatar_url( $uid, [ 'size' => 112 ] ) ?: '',
|
|
'vehicle_type' => $vehicle_type ?: '',
|
|
'vehicle_plate' => $vehicle_plate ?: '',
|
|
'insurance_expiry' => $insurance_expiry ?: '',
|
|
'license_expiry' => $license_expiry ?: '',
|
|
'inspection_expiry' => $inspection_expiry ?: '',
|
|
'email' => $user->user_email,
|
|
'phone' => get_user_meta( $uid, 'phone', true ) ?: get_user_meta( $uid, 'billing_phone', true ) ?: '',
|
|
],
|
|
] );
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────
|
|
REST ROUTES
|
|
───────────────────────────────────────────────────────────── */
|
|
public function register_routes() {
|
|
$ns = self::NAMESPACE;
|
|
$auth = [ 'permission_callback' => [ $this, 'is_driver' ] ];
|
|
|
|
// GET/POST driver status
|
|
register_rest_route( $ns, '/driver/status', [
|
|
[
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_get_status' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
],
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_post_status' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
],
|
|
] );
|
|
|
|
// GET current trip
|
|
register_rest_route( $ns, '/driver/current-trip', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_current_trip' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// GET earnings
|
|
register_rest_route( $ns, '/driver/earnings', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_earnings' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// GET metrics
|
|
register_rest_route( $ns, '/driver/metrics', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_metrics' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// GET history
|
|
register_rest_route( $ns, '/driver/history', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_history' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// GET scheduled
|
|
register_rest_route( $ns, '/driver/scheduled', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_scheduled' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// GET profile
|
|
register_rest_route( $ns, '/driver/profile', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_profile' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// GET zones
|
|
register_rest_route( $ns, '/driver/zones', [
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'rest_zones' ],
|
|
'permission_callback' => '__return_true',
|
|
] );
|
|
|
|
// POST zone-response
|
|
register_rest_route( $ns, '/driver/zone-response', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_zone_response' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// POST incident/sos
|
|
register_rest_route( $ns, '/incident/sos', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_sos' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// POST telemetry/position
|
|
register_rest_route( $ns, '/telemetry/position', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_position' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// POST trip/start
|
|
register_rest_route( $ns, '/trip/start', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_trip_start' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
// POST trip/finish
|
|
register_rest_route( $ns, '/trip/finish', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_trip_finish' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
register_rest_route( $ns, '/driver/change-password', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_change_password' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
|
|
register_rest_route( $ns, '/driver/update-profile', [
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'rest_update_profile' ],
|
|
'permission_callback' => [ $this, 'is_driver' ],
|
|
] );
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────
|
|
PERMISSION
|
|
───────────────────────────────────────────────────────────── */
|
|
public function is_driver() {
|
|
if ( ! is_user_logged_in() ) return new WP_Error( 'not_logged_in', 'Authentication required.', [ 'status' => 401 ] );
|
|
$user = wp_get_current_user();
|
|
if ( in_array( 'driver', (array) $user->roles, true ) ||
|
|
in_array( 'administrator', (array) $user->roles, true ) ) {
|
|
return true;
|
|
}
|
|
return new WP_Error( 'not_driver', 'Access denied.', [ 'status' => 403 ] );
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────
|
|
REST HANDLERS
|
|
───────────────────────────────────────────────────────────── */
|
|
|
|
// GET /driver/status
|
|
public function rest_get_status( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$row = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT online, last_seen, current_trip_id FROM {$wpdb->prefix}ab_driver_status WHERE driver_id = %d",
|
|
$uid
|
|
) );
|
|
if ( ! $row ) {
|
|
return rest_ensure_response( [ 'online' => false, 'last_seen' => null, 'current_trip_id' => null ] );
|
|
}
|
|
return rest_ensure_response( [
|
|
'online' => (bool) $row->online,
|
|
'last_seen' => $row->last_seen,
|
|
'current_trip_id' => $row->current_trip_id,
|
|
] );
|
|
}
|
|
|
|
// POST /driver/status
|
|
public function rest_post_status( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$body = $req->get_json_params();
|
|
$online = ! empty( $body['online'] );
|
|
$lat = isset( $body['lat'] ) ? floatval( $body['lat'] ) : null;
|
|
$lng = isset( $body['lng'] ) ? floatval( $body['lng'] ) : null;
|
|
$now = current_time( 'mysql' );
|
|
|
|
$exists = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_driver_status WHERE driver_id = %d", $uid
|
|
) );
|
|
|
|
$data = [
|
|
'online' => $online ? 1 : 0,
|
|
'last_seen' => $now,
|
|
];
|
|
if ( $lat !== null ) $data['last_lat'] = $lat;
|
|
if ( $lng !== null ) $data['last_lng'] = $lng;
|
|
|
|
if ( $exists ) {
|
|
$wpdb->update( "{$wpdb->prefix}ab_driver_status", $data, [ 'driver_id' => $uid ] );
|
|
} else {
|
|
$data['driver_id'] = $uid;
|
|
$wpdb->insert( "{$wpdb->prefix}ab_driver_status", $data );
|
|
}
|
|
|
|
// Manage sessions
|
|
if ( $online ) {
|
|
// Insert new session (go online)
|
|
$sess = [
|
|
'driver_id' => $uid,
|
|
'started_at' => $now,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
if ( $lat !== null ) { $sess['start_lat'] = $lat; $sess['start_lng'] = $lng; }
|
|
$wpdb->insert( "{$wpdb->prefix}ab_driver_sessions", $sess );
|
|
} else {
|
|
// Close latest open session
|
|
$sess_id = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT id FROM {$wpdb->prefix}ab_driver_sessions WHERE driver_id = %d AND ended_at IS NULL ORDER BY started_at DESC LIMIT 1",
|
|
$uid
|
|
) );
|
|
if ( $sess_id ) {
|
|
$upd = [ 'ended_at' => $now, 'updated_at' => $now ];
|
|
if ( $lat !== null ) { $upd['end_lat'] = $lat; $upd['end_lng'] = $lng; }
|
|
// Calculate online_seconds
|
|
$start = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT started_at FROM {$wpdb->prefix}ab_driver_sessions WHERE id = %d", $sess_id
|
|
) );
|
|
if ( $start ) {
|
|
$upd['online_seconds'] = max( 0, strtotime( $now ) - strtotime( $start ) );
|
|
}
|
|
$wpdb->update( "{$wpdb->prefix}ab_driver_sessions", $upd, [ 'id' => $sess_id ] );
|
|
}
|
|
}
|
|
|
|
return rest_ensure_response( [ 'ok' => true, 'online' => $online ] );
|
|
}
|
|
|
|
// GET /driver/current-trip
|
|
public function rest_current_trip( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$statuses = "'assigned','en_route','waiting','in_progress'";
|
|
|
|
$trip = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status IN ($statuses)
|
|
ORDER BY updated_at DESC LIMIT 1",
|
|
$uid
|
|
), ARRAY_A );
|
|
|
|
if ( ! $trip ) {
|
|
return rest_ensure_response( null );
|
|
}
|
|
|
|
// Unread chat count
|
|
$unread = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}autobooking_chat
|
|
WHERE ride_id = %d AND sender != 'driver'",
|
|
$trip['id']
|
|
) );
|
|
$trip['unread_count'] = $unread;
|
|
|
|
return rest_ensure_response( $trip );
|
|
}
|
|
|
|
// GET /driver/earnings
|
|
public function rest_earnings( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$period = $req->get_param( 'period' ) ?: 'day';
|
|
|
|
switch ( $period ) {
|
|
case 'week':
|
|
$where_date = "AND DATE(created_at) >= CURDATE() - INTERVAL 6 DAY";
|
|
break;
|
|
case 'month':
|
|
$where_date = "AND YEAR(created_at)=YEAR(NOW()) AND MONTH(created_at)=MONTH(NOW())";
|
|
break;
|
|
case 'year':
|
|
$where_date = "AND YEAR(created_at)=YEAR(NOW())";
|
|
break;
|
|
default: // day
|
|
$where_date = "AND DATE(created_at) = CURDATE()";
|
|
break;
|
|
}
|
|
|
|
$row = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT
|
|
COALESCE(SUM(fare_total_amount),0) AS gross,
|
|
COALESCE(SUM(platform_fee_amount),0) AS fee,
|
|
COALESCE(SUM(driver_payout_amount),0) AS net,
|
|
COUNT(*) AS trips_count,
|
|
COALESCE(SUM(fare_distance_m),0) AS distance_m
|
|
FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status = 'finished' $where_date",
|
|
$uid
|
|
) );
|
|
|
|
// Last 7 days
|
|
$last7 = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT DATE(created_at) AS the_date, COALESCE(SUM(driver_payout_amount),0) AS net
|
|
FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status = 'finished'
|
|
AND DATE(created_at) >= CURDATE() - INTERVAL 6 DAY
|
|
GROUP BY DATE(created_at)
|
|
ORDER BY DATE(created_at) ASC",
|
|
$uid
|
|
), ARRAY_A );
|
|
|
|
// Fill missing days
|
|
$days_map = [];
|
|
for ( $i = 6; $i >= 0; $i-- ) {
|
|
$d = date( 'Y-m-d', strtotime( "-$i days" ) );
|
|
$days_map[ $d ] = 0;
|
|
}
|
|
foreach ( $last7 as $r ) {
|
|
$days_map[ $r['the_date'] ] = (float) $r['net'];
|
|
}
|
|
$last7_arr = [];
|
|
foreach ( $days_map as $d => $v ) {
|
|
$last7_arr[] = [ 'date' => $d, 'net' => $v ];
|
|
}
|
|
|
|
return rest_ensure_response( [
|
|
'gross' => (float) $row->gross,
|
|
'fee' => (float) $row->fee,
|
|
'net' => (float) $row->net,
|
|
'trips_count' => (int) $row->trips_count,
|
|
'distance_km' => round( (float) $row->distance_m / 1000, 2 ),
|
|
'last_7_days' => $last7_arr,
|
|
] );
|
|
}
|
|
|
|
// GET /driver/metrics
|
|
public function rest_metrics( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
|
|
// Last 30 days trips assigned to driver
|
|
$total_assigned = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status != 'available'
|
|
AND created_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
|
|
$accepted = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status NOT IN ('assigned','canceled')
|
|
AND created_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
|
|
$finished = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status = 'finished'
|
|
AND created_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
|
|
$canceled = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status = 'canceled'
|
|
AND created_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
|
|
$avg_rating = (float) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT AVG(driver_rating) FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND driver_rating > 0
|
|
AND created_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
|
|
// Online hours this week from sessions
|
|
$online_secs = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COALESCE(SUM(online_seconds),0) FROM {$wpdb->prefix}ab_driver_sessions
|
|
WHERE driver_id = %d AND started_at >= CURDATE() - INTERVAL 6 DAY",
|
|
$uid
|
|
) );
|
|
|
|
// Zone responses
|
|
$zones_responded = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_zone_alert_responses
|
|
WHERE driver_id = %d AND response = 'going'
|
|
AND responded_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
$zones_ignored = (int) $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_zone_alert_responses
|
|
WHERE driver_id = %d AND response IN ('not_going','ignored')
|
|
AND responded_at >= NOW() - INTERVAL 30 DAY",
|
|
$uid
|
|
) );
|
|
|
|
$acceptance_rate = $total_assigned > 0 ? round( ( $accepted / $total_assigned ) * 100, 1 ) : 0;
|
|
$total_started = $accepted;
|
|
$completion_rate = $total_started > 0 ? round( ( $finished / $total_started ) * 100, 1 ) : 0;
|
|
|
|
return rest_ensure_response( [
|
|
'acceptance_rate' => $acceptance_rate,
|
|
'completion_rate' => $completion_rate,
|
|
'avg_rating' => round( $avg_rating, 2 ),
|
|
'online_hours_week'=> round( $online_secs / 3600, 1 ),
|
|
'zones_responded' => $zones_responded,
|
|
'zones_ignored' => $zones_ignored,
|
|
] );
|
|
}
|
|
|
|
// GET /driver/history
|
|
public function rest_history( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$page = max( 1, intval( $req->get_param( 'page' ) ?: 1 ) );
|
|
$per_page = max( 1, min( 100, intval( $req->get_param( 'per_page' ) ?: 20 ) ) );
|
|
$offset = ( $page - 1 ) * $per_page;
|
|
$date_from = sanitize_text_field( $req->get_param( 'date_from' ) ?: '' );
|
|
$date_to = sanitize_text_field( $req->get_param( 'date_to' ) ?: '' );
|
|
|
|
try {
|
|
$where = $wpdb->prepare( "WHERE driver_id = %d", $uid );
|
|
if ( $date_from ) $where .= $wpdb->prepare( " AND DATE(created_at) >= %s", $date_from );
|
|
if ( $date_to ) $where .= $wpdb->prepare( " AND DATE(created_at) <= %s", $date_to );
|
|
|
|
$total = (int) $wpdb->get_var(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips $where"
|
|
);
|
|
if ( $wpdb->last_error ) throw new \Exception( $wpdb->last_error );
|
|
|
|
$rows = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"SELECT id, trip_uuid, created_at, passenger_name,
|
|
pickup_lat, pickup_lng, dropoff_lat, dropoff_lng,
|
|
fare_total_amount, platform_fee_amount, driver_payout_amount,
|
|
status, driver_rating, currency
|
|
FROM {$wpdb->prefix}ab_trips $where
|
|
ORDER BY created_at DESC
|
|
LIMIT %d OFFSET %d",
|
|
$per_page, $offset
|
|
),
|
|
ARRAY_A
|
|
);
|
|
if ( $wpdb->last_error ) throw new \Exception( $wpdb->last_error );
|
|
|
|
return rest_ensure_response( [
|
|
'ok' => true,
|
|
'rows' => $rows ?: [],
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $per_page,
|
|
'total_pages'=> $total > 0 ? (int) ceil( $total / $per_page ) : 0,
|
|
] );
|
|
} catch ( \Exception $e ) {
|
|
return rest_ensure_response( [
|
|
'ok' => true,
|
|
'rows' => [],
|
|
'total' => 0,
|
|
'page' => $page,
|
|
'per_page' => $per_page,
|
|
'total_pages'=> 0,
|
|
] );
|
|
}
|
|
}
|
|
|
|
// GET /driver/scheduled
|
|
public function rest_scheduled( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
|
|
$rows = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}ab_trips
|
|
WHERE driver_id = %d AND status = 'assigned' AND pickup_time > NOW()
|
|
ORDER BY pickup_time ASC",
|
|
$uid
|
|
), ARRAY_A );
|
|
|
|
return rest_ensure_response( $rows ?: [] );
|
|
}
|
|
|
|
// GET /driver/profile
|
|
public function rest_profile( $req ) {
|
|
$uid = get_current_user_id();
|
|
$user = get_userdata( $uid );
|
|
|
|
$driver_name = get_user_meta( $uid, 'driver_name', true );
|
|
if ( ! $driver_name ) $driver_name = get_user_meta( $uid, 'full_name', true );
|
|
if ( ! $driver_name ) {
|
|
$fn = get_user_meta( $uid, 'first_name', true );
|
|
$ln = get_user_meta( $uid, 'last_name', true );
|
|
$driver_name = trim( "$fn $ln" );
|
|
}
|
|
if ( ! $driver_name ) $driver_name = $user->display_name;
|
|
|
|
$photo = get_user_meta( $uid, 'photo_url', true ) ?: get_user_meta( $uid, 'driver_photo', true );
|
|
$vehicle_plate = get_user_meta( $uid, 'vehicle_plate', true ) ?: get_user_meta( $uid, 'license_plate', true );
|
|
|
|
$insurance_expiry = get_user_meta( $uid, 'insurance_expiry', true );
|
|
$license_expiry = get_user_meta( $uid, 'license_expiry', true );
|
|
$inspection_expiry = get_user_meta( $uid, 'inspection_expiry', true );
|
|
|
|
$now = time();
|
|
|
|
return rest_ensure_response( [
|
|
'name' => $driver_name,
|
|
'email' => $user->user_email,
|
|
'phone' => get_user_meta( $uid, 'phone', true ) ?: get_user_meta( $uid, 'billing_phone', true ),
|
|
'vehicle_type' => get_user_meta( $uid, 'vehicle_type', true ),
|
|
'vehicle_plate' => $vehicle_plate,
|
|
'photo_url' => $photo,
|
|
'insurance_expiry' => $insurance_expiry,
|
|
'license_expiry' => $license_expiry,
|
|
'inspection_expiry' => $inspection_expiry,
|
|
'insurance_status' => $this->doc_status( $insurance_expiry ),
|
|
'license_status' => $this->doc_status( $license_expiry ),
|
|
'inspection_status' => $this->doc_status( $inspection_expiry ),
|
|
] );
|
|
}
|
|
|
|
private function doc_status( $expiry_date ) {
|
|
if ( ! $expiry_date ) return 'unknown';
|
|
$ts = strtotime( $expiry_date );
|
|
$diff = $ts - time();
|
|
if ( $diff < 0 ) return 'expired';
|
|
if ( $diff < 30 * 86400 ) return 'warning';
|
|
return 'ok';
|
|
}
|
|
|
|
// POST /driver/change-password
|
|
public function rest_change_password( $req ) {
|
|
$uid = get_current_user_id();
|
|
$current = $req->get_param('current_password');
|
|
$new_pw = $req->get_param('new_password');
|
|
$confirm = $req->get_param('confirm_password');
|
|
|
|
if ( ! $current || ! $new_pw || ! $confirm ) {
|
|
return new WP_Error( 'missing_fields', 'Todos los campos son requeridos.', [ 'status' => 400 ] );
|
|
}
|
|
if ( $new_pw !== $confirm ) {
|
|
return new WP_Error( 'password_mismatch', 'Las contraseñas no coinciden.', [ 'status' => 400 ] );
|
|
}
|
|
if ( strlen( $new_pw ) < 8 ) {
|
|
return new WP_Error( 'password_too_short', 'La contraseña debe tener al menos 8 caracteres.', [ 'status' => 400 ] );
|
|
}
|
|
|
|
$user = get_userdata( $uid );
|
|
if ( ! wp_check_password( $current, $user->user_pass, $uid ) ) {
|
|
return new WP_Error( 'wrong_password', 'Contraseña actual incorrecta.', [ 'status' => 400 ] );
|
|
}
|
|
|
|
wp_set_password( $new_pw, $uid );
|
|
return rest_ensure_response( [ 'ok' => true, 'msg' => 'Contraseña actualizada correctamente.' ] );
|
|
}
|
|
|
|
// POST /driver/update-profile
|
|
public function rest_update_profile( $req ) {
|
|
$uid = get_current_user_id();
|
|
$phone = sanitize_text_field( $req->get_param('phone') ?: '' );
|
|
$vehicle = sanitize_text_field( $req->get_param('vehicle_type') ?: '' );
|
|
$plate = sanitize_text_field( $req->get_param('vehicle_plate') ?: '' );
|
|
$photo = esc_url_raw( $req->get_param('photo_url') ?: '' );
|
|
|
|
if ( $phone ) update_user_meta( $uid, 'phone', $phone );
|
|
if ( $vehicle ) update_user_meta( $uid, 'vehicle_type', $vehicle );
|
|
if ( $plate ) {
|
|
update_user_meta( $uid, 'vehicle_plate', $plate );
|
|
update_user_meta( $uid, 'license_plate', $plate );
|
|
}
|
|
if ( $photo ) update_user_meta( $uid, 'photo_url', $photo );
|
|
|
|
return rest_ensure_response( [ 'ok' => true, 'msg' => 'Perfil actualizado correctamente.' ] );
|
|
}
|
|
|
|
// GET /driver/zones
|
|
public function rest_zones( $req ) {
|
|
global $wpdb;
|
|
|
|
$rows = $wpdb->get_results(
|
|
"SELECT id, title, description, lat, lng, radius_km, bonus_amount, bonus_currency,
|
|
countdown_seconds, type, expires_at
|
|
FROM {$wpdb->prefix}ab_zone_alerts
|
|
WHERE active = 1 AND (expires_at IS NULL OR expires_at > NOW())
|
|
ORDER BY created_at DESC",
|
|
ARRAY_A
|
|
);
|
|
|
|
return rest_ensure_response( $rows ?: [] );
|
|
}
|
|
|
|
// POST /driver/zone-response
|
|
public function rest_zone_response( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$body = $req->get_json_params();
|
|
$zone_id = intval( $body['zone_id'] ?? 0 );
|
|
$resp = sanitize_text_field( $body['response'] ?? '' );
|
|
|
|
if ( ! $zone_id || ! in_array( $resp, [ 'going', 'not_going' ], true ) ) {
|
|
return new WP_Error( 'bad_request', 'Invalid parameters.', [ 'status' => 400 ] );
|
|
}
|
|
|
|
$wpdb->insert( "{$wpdb->prefix}ab_zone_alert_responses", [
|
|
'zone_id' => $zone_id,
|
|
'driver_id' => $uid,
|
|
'response' => $resp,
|
|
'responded_at' => current_time( 'mysql' ),
|
|
] );
|
|
|
|
return rest_ensure_response( [ 'ok' => true ] );
|
|
}
|
|
|
|
// POST /incident/sos
|
|
public function rest_sos( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$body = $req->get_json_params();
|
|
|
|
$lat = floatval( $body['lat'] ?? 0 );
|
|
$lng = floatval( $body['lng'] ?? 0 );
|
|
$trip_uuid = sanitize_text_field( $body['trip_uuid'] ?? '' );
|
|
$chunk_index = isset( $body['chunk_index'] ) ? intval( $body['chunk_index'] ) : null;
|
|
$audio_data = $body['audio_data'] ?? null;
|
|
$incident_id = intval( $body['incident_id'] ?? 0 );
|
|
|
|
if ( ! $incident_id ) {
|
|
// First call — create incident
|
|
$trip_id = null;
|
|
if ( $trip_uuid ) {
|
|
$trip_id = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT id FROM {$wpdb->prefix}ab_trips WHERE trip_uuid = %s LIMIT 1",
|
|
$trip_uuid
|
|
) );
|
|
}
|
|
|
|
$wpdb->insert( "{$wpdb->prefix}ab_incidents", [
|
|
'driver_id' => $uid,
|
|
'trip_id' => $trip_id ?: null,
|
|
'lat' => $lat,
|
|
'lng' => $lng,
|
|
'status' => 'active',
|
|
'chunks_count'=> 0,
|
|
'incident_at' => current_time( 'mysql' ),
|
|
] );
|
|
$incident_id = $wpdb->insert_id;
|
|
|
|
// Send admin email
|
|
$admin_email = get_option( 'admin_email' );
|
|
$maps_link = "https://maps.google.com/?q={$lat},{$lng}";
|
|
$user = get_userdata( $uid );
|
|
$subject = '🚨 SOS ALERT — Autobooking Driver';
|
|
$message = "Driver: {$user->display_name} (ID: {$uid})\n";
|
|
$message .= "GPS: {$lat}, {$lng}\n";
|
|
$message .= "Google Maps: {$maps_link}\n";
|
|
if ( $trip_uuid ) $message .= "Trip UUID: {$trip_uuid}\n";
|
|
$message .= "Time: " . current_time( 'mysql' ) . "\n";
|
|
wp_mail( $admin_email, $subject, $message );
|
|
}
|
|
|
|
// Save audio chunk if provided
|
|
if ( $audio_data && $incident_id ) {
|
|
$upload_dir = wp_upload_dir();
|
|
$dir = $upload_dir['basedir'] . '/ab-incidents/' . $incident_id;
|
|
if ( ! file_exists( $dir ) ) {
|
|
wp_mkdir_p( $dir );
|
|
}
|
|
$idx = intval( $chunk_index ) ?: 0;
|
|
$filename = $dir . '/chunk_' . $idx . '.webm';
|
|
$decoded = base64_decode( $audio_data );
|
|
if ( $decoded !== false ) {
|
|
file_put_contents( $filename, $decoded );
|
|
}
|
|
$wpdb->query( $wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}ab_incidents SET chunks_count = chunks_count + 1 WHERE id = %d",
|
|
$incident_id
|
|
) );
|
|
}
|
|
|
|
return rest_ensure_response( [ 'ok' => true, 'incident_id' => $incident_id ] );
|
|
}
|
|
|
|
// POST /telemetry/position
|
|
public function rest_position( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$body = $req->get_json_params();
|
|
|
|
$lat = floatval( $body['lat'] ?? 0 );
|
|
$lng = floatval( $body['lng'] ?? 0 );
|
|
$accuracy = intval( $body['accuracy'] ?? 0 );
|
|
$trip_id = intval( $body['trip_id'] ?? 0 );
|
|
|
|
// Update driver status
|
|
$exists = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}ab_driver_status WHERE driver_id = %d", $uid
|
|
) );
|
|
$now = current_time( 'mysql' );
|
|
$data = [
|
|
'last_lat' => $lat,
|
|
'last_lng' => $lng,
|
|
'last_accuracy' => $accuracy,
|
|
'last_seen' => $now,
|
|
];
|
|
if ( $exists ) {
|
|
$wpdb->update( "{$wpdb->prefix}ab_driver_status", $data, [ 'driver_id' => $uid ] );
|
|
} else {
|
|
$data['driver_id'] = $uid;
|
|
$wpdb->insert( "{$wpdb->prefix}ab_driver_status", $data );
|
|
}
|
|
|
|
// Insert trip position if trip_id given (table may not exist — catch quietly)
|
|
if ( $trip_id ) {
|
|
$wpdb->query( $wpdb->prepare(
|
|
"INSERT IGNORE INTO {$wpdb->prefix}ab_trip_positions (trip_id, driver_id, lat, lng, accuracy, recorded_at)
|
|
VALUES (%d, %d, %s, %s, %d, %s)",
|
|
$trip_id, $uid, $lat, $lng, $accuracy, $now
|
|
) );
|
|
}
|
|
|
|
return rest_ensure_response( [ 'ok' => true ] );
|
|
}
|
|
|
|
// POST /trip/start
|
|
public function rest_trip_start( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$body = $req->get_json_params();
|
|
$trip_uuid = sanitize_text_field( $body['trip_uuid'] ?? '' );
|
|
|
|
if ( ! $trip_uuid ) {
|
|
return new WP_Error( 'bad_request', 'trip_uuid required.', [ 'status' => 400 ] );
|
|
}
|
|
|
|
$affected = $wpdb->query( $wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}ab_trips SET status = 'in_progress', pickup_time = NOW(), updated_at = NOW()
|
|
WHERE trip_uuid = %s AND driver_id = %d AND status = 'waiting'",
|
|
$trip_uuid, $uid
|
|
) );
|
|
|
|
if ( ! $affected ) {
|
|
return new WP_Error( 'not_found', 'Trip not found or invalid state.', [ 'status' => 404 ] );
|
|
}
|
|
|
|
return rest_ensure_response( [ 'ok' => true ] );
|
|
}
|
|
|
|
// POST /trip/finish
|
|
public function rest_trip_finish( $req ) {
|
|
global $wpdb;
|
|
$uid = get_current_user_id();
|
|
$body = $req->get_json_params();
|
|
|
|
$trip_uuid = sanitize_text_field( $body['trip_uuid'] ?? '' );
|
|
$tolls = floatval( $body['tolls_amount'] ?? 0 );
|
|
$tips = floatval( $body['tips_amount'] ?? 0 );
|
|
$final_amount = isset( $body['final_amount'] ) ? floatval( $body['final_amount'] ) : null;
|
|
|
|
if ( ! $trip_uuid ) {
|
|
return new WP_Error( 'bad_request', 'trip_uuid required.', [ 'status' => 400 ] );
|
|
}
|
|
|
|
// Fetch trip to recalculate payout
|
|
$trip = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}ab_trips WHERE trip_uuid = %s AND driver_id = %d",
|
|
$trip_uuid, $uid
|
|
) );
|
|
|
|
if ( ! $trip ) {
|
|
return new WP_Error( 'not_found', 'Trip not found.', [ 'status' => 404 ] );
|
|
}
|
|
|
|
$payout = $final_amount !== null
|
|
? $final_amount
|
|
: ( floatval( $trip->driver_payout_amount ) + $tolls + $tips );
|
|
|
|
$wpdb->query( $wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}ab_trips
|
|
SET status = 'finished', dropoff_time = NOW(), updated_at = NOW(),
|
|
tolls_amount = %f, driver_payout_amount = %f
|
|
WHERE trip_uuid = %s AND driver_id = %d",
|
|
$tolls, $payout, $trip_uuid, $uid
|
|
) );
|
|
|
|
// Clear current_trip_id in driver status
|
|
$wpdb->update(
|
|
"{$wpdb->prefix}ab_driver_status",
|
|
[ 'current_trip_id' => null ],
|
|
[ 'driver_id' => $uid ]
|
|
);
|
|
|
|
return rest_ensure_response( [ 'ok' => true ] );
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────
|
|
SHORTCODE RENDER
|
|
───────────────────────────────────────────────────────────── */
|
|
public function render( $atts ) {
|
|
if ( ! is_user_logged_in() ) {
|
|
return '<div style="background:rgba(17,17,17,.45);border:1px solid rgba(255,255,255,.18);backdrop-filter:blur(8px);border-radius:16px;padding:32px;text-align:center;color:#fff;font-family:system-ui;max-width:420px;margin:40px auto;">'
|
|
. '<p style="font-size:18px;font-weight:700;margin-bottom:16px;">🔒 Acceso Restringido</p>'
|
|
. '<p style="color:rgba(255,255,255,.7);margin-bottom:20px;">Debes iniciar sesión para acceder al panel de conductor.</p>'
|
|
. '<a href="' . esc_url( wp_login_url( get_permalink() ) ) . '" style="display:inline-block;background:#FF6F00;color:#fff;padding:12px 24px;border-radius:12px;text-decoration:none;font-weight:800;text-transform:uppercase;">Iniciar sesión</a>'
|
|
. '</div>';
|
|
}
|
|
|
|
$user = wp_get_current_user();
|
|
$is_driver = in_array( 'driver', (array) $user->roles, true ) || in_array( 'administrator', (array) $user->roles, true );
|
|
if ( ! $is_driver ) {
|
|
return '<div style="background:rgba(17,17,17,.45);border:1px solid rgba(255,255,255,.18);backdrop-filter:blur(8px);border-radius:16px;padding:32px;text-align:center;color:#fff;font-family:system-ui;max-width:420px;margin:40px auto;">'
|
|
. '<p style="font-size:18px;font-weight:700;margin-bottom:16px;">🚫 Acceso Denegado</p>'
|
|
. '<p style="color:rgba(255,255,255,.7);">Esta página es exclusiva para conductores registrados.</p>'
|
|
. '</div>';
|
|
}
|
|
|
|
$uid = $user->ID;
|
|
$render_photo = get_user_meta( $uid, 'photo_url', true )
|
|
?: get_user_meta( $uid, 'driver_photo', true )
|
|
?: get_avatar_url( $uid, [ 'size' => 112 ] )
|
|
?: '';
|
|
|
|
ob_start();
|
|
?>
|
|
<div id="abd-app" class="abd-app">
|
|
|
|
<!-- Skeleton initial loader -->
|
|
<div id="abd-init-loader">
|
|
<div class="abd-init-logo">🚗</div>
|
|
<div class="abd-skeleton" style="width:160px;height:20px;margin:16px auto 8px;"></div>
|
|
<div class="abd-skeleton" style="width:100px;height:14px;margin:0 auto;"></div>
|
|
</div>
|
|
|
|
<!-- SOS Badge -->
|
|
<div id="abd-sos-badge" style="display:none">🚨 ALERTA ACTIVA — Admin notificado <button id="abd-sos-stop" style="margin-left:12px;background:rgba(0,0,0,.4);color:#fff;border:1px solid rgba(255,255,255,.4);border-radius:8px;padding:2px 10px;cursor:pointer;">Detener</button></div>
|
|
|
|
<!-- Header -->
|
|
<header class="abd-header">
|
|
<div class="abd-header-left">
|
|
<img id="abd-header-photo" src="<?php echo esc_url( $render_photo ); ?>" alt="" class="abd-header-avatar" <?php echo $render_photo ? '' : 'style="display:none"'; ?>>
|
|
<div class="abd-header-info">
|
|
<div id="abd-header-name" class="abd-header-name">Conductor</div>
|
|
<div id="abd-header-plate" class="abd-header-plate"></div>
|
|
</div>
|
|
</div>
|
|
<div class="abd-header-right">
|
|
<span id="abd-status-label" class="abd-status-label abd-status--offline">OFFLINE</span>
|
|
<label class="abd-toggle" title="Conectar / Desconectar">
|
|
<input type="checkbox" id="abd-online-toggle">
|
|
<span class="abd-toggle-track"><span class="abd-toggle-thumb"></span></span>
|
|
</label>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Tab navigation -->
|
|
<nav class="abd-tabs">
|
|
<button class="abd-tab abd-tab--active" data-tab="viaje">🚗 Viaje</button>
|
|
<button class="abd-tab" data-tab="ganancias">💰 Ganancias</button>
|
|
<button class="abd-tab" data-tab="metricas">📊 Métricas</button>
|
|
<button class="abd-tab" data-tab="historial">📋 Historial</button>
|
|
<button class="abd-tab" data-tab="perfil">👤 Perfil</button>
|
|
</nav>
|
|
|
|
<!-- ═══ VIAJE TAB ═══ -->
|
|
<section class="abd-panel abd-panel--active" id="abd-tab-viaje">
|
|
|
|
<!-- Zone Alert Card -->
|
|
<div id="abd-zone-card" class="abd-glass-card abd-zone-card" style="display:none">
|
|
<div class="abd-zone-header">
|
|
<span id="abd-zone-type-badge" class="abd-zone-badge">HOTSPOT</span>
|
|
<span id="abd-zone-countdown" class="abd-zone-countdown">3:00</span>
|
|
</div>
|
|
<div id="abd-zone-title" class="abd-zone-title"></div>
|
|
<div id="abd-zone-desc" class="abd-zone-desc"></div>
|
|
<div class="abd-zone-bonus" id="abd-zone-bonus" style="display:none">Bono: <b id="abd-zone-bonus-val"></b></div>
|
|
<div class="abd-zone-actions">
|
|
<button id="abd-zone-going" class="abd-btn abd-btn--brand">✅ Voy</button>
|
|
<button id="abd-zone-no" class="abd-btn abd-btn--ghost">❌ No puedo</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Map -->
|
|
<div id="abd-map-wrap">
|
|
<div id="abd-map"></div>
|
|
<div id="abd-route-banner" style="display:none">
|
|
<div id="abd-route-banner-text">Ruta sugerida disponible</div>
|
|
<div class="abd-route-banner-btns">
|
|
<button id="abd-route-accept" class="abd-btn abd-btn--brand">Aceptar</button>
|
|
<button id="abd-route-keep" class="abd-btn abd-btn--ghost">Mantener</button>
|
|
</div>
|
|
</div>
|
|
<div id="abd-theme-switcher">
|
|
<button class="abd-theme-btn active" data-theme="auto" title="Automático (oscuro de noche)">Auto</button>
|
|
<button class="abd-theme-btn" data-theme="dark" title="Siempre oscuro">Oscuro</button>
|
|
<button class="abd-theme-btn" data-theme="normal" title="Mapa normal">Normal</button>
|
|
</div>
|
|
<div id="abd-gps-badge">GPS: —</div>
|
|
</div>
|
|
|
|
<!-- Trip controls card -->
|
|
<div id="abd-trip-card" class="abd-glass-card">
|
|
|
|
<!-- No trip -->
|
|
<div id="abd-no-trip">
|
|
<div style="text-align:center;padding:12px 0;">
|
|
<img id="abd-driver-avatar-trip" src="" alt="" class="abd-driver-avatar-lg">
|
|
<div id="abd-driver-name-trip" class="abd-driver-name-lg"></div>
|
|
<div id="abd-driver-rating-trip" class="abd-driver-rating-lg">★ —</div>
|
|
<div id="abd-driver-vehicle-trip" class="abd-driver-vehicle-lg"></div>
|
|
<p style="color:rgba(255,255,255,.5);margin-top:16px;font-size:14px;">Conéctate para recibir viajes</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active trip -->
|
|
<div id="abd-active-trip" style="display:none">
|
|
<div class="abd-passenger-info">
|
|
<img id="abd-pass-photo" src="" alt="" class="abd-pass-avatar">
|
|
<div>
|
|
<div id="abd-pass-name" class="abd-pass-name"></div>
|
|
<div id="abd-trip-status-badge" class="abd-trip-status-badge"></div>
|
|
</div>
|
|
</div>
|
|
<div class="abd-trip-info">
|
|
<div class="abd-trip-info-row"><span class="abd-info-label">Origen</span><b id="abd-origin"></b></div>
|
|
<div class="abd-trip-info-row"><span class="abd-info-label">Destino</span><b id="abd-dest"></b></div>
|
|
<div class="abd-trip-info-row"><span class="abd-info-label">Distancia</span><b id="abd-distance"></b></div>
|
|
<div class="abd-trip-info-row"><span class="abd-info-label">Tarifa estimada</span><b id="abd-fare"></b></div>
|
|
</div>
|
|
<div id="abd-trip-instructions" class="abd-instructions"></div>
|
|
<div id="abd-courtesy-timer" class="abd-courtesy-timer" style="display:none">⏱ Tiempo de cortesía: <b id="abd-courtesy-val">3:00</b></div>
|
|
<div id="abd-trip-actions" class="abd-actions"></div>
|
|
<button id="abd-sos-btn" class="abd-sos-btn" style="display:none">🆘 PÁNICO</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chat -->
|
|
<div id="abd-chat" class="abd-glass-card" style="display:none">
|
|
<div class="abd-chat-header" id="abd-chat-toggle-header">
|
|
💬 Chat <span id="abd-chat-badge" class="ab-badge--warn" style="display:none">0</span>
|
|
</div>
|
|
<div id="abd-chat-body">
|
|
<div id="abd-chat-msgs"></div>
|
|
<div class="abd-chat-input-row">
|
|
<input id="abd-chat-input" placeholder="Escribe un mensaje…" maxlength="200" class="abd-chat-input">
|
|
<button id="abd-chat-send" class="abd-btn abd-btn--brand">➤</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<!-- ═══ GANANCIAS TAB ═══ -->
|
|
<section class="abd-panel" id="abd-tab-ganancias" style="display:none">
|
|
|
|
<div class="abd-period-tabs">
|
|
<button class="abd-period-tab abd-period-tab--active" data-period="day">HOY</button>
|
|
<button class="abd-period-tab" data-period="week">SEMANA</button>
|
|
<button class="abd-period-tab" data-period="month">MES</button>
|
|
<button class="abd-period-tab" data-period="year">AÑO</button>
|
|
</div>
|
|
|
|
<div class="abd-earnings-cards">
|
|
<div class="abd-glass-card abd-earn-card">
|
|
<div class="abd-earn-label">Bruto</div>
|
|
<div id="abd-earn-gross" class="abd-earn-value">$—</div>
|
|
</div>
|
|
<div class="abd-glass-card abd-earn-card">
|
|
<div class="abd-earn-label">Comisión (20%)</div>
|
|
<div id="abd-earn-fee" class="abd-earn-value abd-earn-fee">$—</div>
|
|
</div>
|
|
<div class="abd-glass-card abd-earn-card abd-earn-card--main">
|
|
<div class="abd-earn-label">NETO A RECIBIR</div>
|
|
<div id="abd-earn-net" class="abd-earn-value abd-earn-net">$—</div>
|
|
<div id="abd-earn-trips" class="abd-earn-sub"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="abd-glass-card abd-chart-card">
|
|
<div class="abd-card-title">Últimos 7 días</div>
|
|
<div id="abd-bar-chart" class="abd-bar-chart"></div>
|
|
</div>
|
|
|
|
<div class="abd-glass-card abd-tax-card">
|
|
<div class="abd-card-title">Resumen para impuestos (año en curso)</div>
|
|
<div class="abd-tax-row"><span>Total devengado</span><span id="abd-tax-gross">$—</span></div>
|
|
<div class="abd-tax-row"><span>Total comisiones</span><span id="abd-tax-fee">$—</span></div>
|
|
<div class="abd-tax-row abd-tax-row--total"><span>Neto anual</span><span id="abd-tax-net">$—</span></div>
|
|
<button id="abd-export-csv" class="abd-btn abd-btn--ghost" style="width:100%;margin-top:12px">📄 Exportar CSV para Taxes</button>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<!-- ═══ MÉTRICAS TAB ═══ -->
|
|
<section class="abd-panel" id="abd-tab-metricas" style="display:none">
|
|
<div class="abd-glass-card">
|
|
<div class="abd-card-title">Últimos 30 días</div>
|
|
<div class="abd-metric-row">
|
|
<span class="abd-metric-label">% Aceptación</span>
|
|
<div class="abd-progress-wrap"><div class="abd-progress-bar" id="bar-acceptance"></div></div>
|
|
<span id="val-acceptance" class="abd-metric-val">—</span>
|
|
</div>
|
|
<div class="abd-metric-row">
|
|
<span class="abd-metric-label">% Completación</span>
|
|
<div class="abd-progress-wrap"><div class="abd-progress-bar abd-bar-green" id="bar-completion"></div></div>
|
|
<span id="val-completion" class="abd-metric-val">—</span>
|
|
</div>
|
|
<div class="abd-metric-row">
|
|
<span class="abd-metric-label">Calificación promedio</span>
|
|
<div class="abd-stars" id="abd-stars"></div>
|
|
<span id="val-rating" class="abd-metric-val">—</span>
|
|
</div>
|
|
<div class="abd-metric-row">
|
|
<span class="abd-metric-label">Horas activas (semana)</span>
|
|
<div class="abd-progress-wrap"><div class="abd-progress-bar abd-bar-blue" id="bar-hours"></div></div>
|
|
<span id="val-hours" class="abd-metric-val">—</span>
|
|
</div>
|
|
<div class="abd-metric-row">
|
|
<span class="abd-metric-label">Alertas respondidas</span>
|
|
<div class="abd-progress-wrap"><div class="abd-progress-bar abd-bar-yellow" id="bar-zones"></div></div>
|
|
<span id="val-zones" class="abd-metric-val">—</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ═══ HISTORIAL TAB ═══ -->
|
|
<section class="abd-panel" id="abd-tab-historial" style="display:none">
|
|
<div class="abd-history-filters abd-glass-card">
|
|
<input type="date" id="abd-hist-from" class="abd-date-input">
|
|
<input type="date" id="abd-hist-to" class="abd-date-input">
|
|
<button id="abd-hist-filter" class="abd-btn abd-btn--ghost">Filtrar</button>
|
|
</div>
|
|
<div id="abd-history-table-wrap" class="abd-glass-card" style="overflow-x:auto">
|
|
<table class="abd-table">
|
|
<thead><tr>
|
|
<th>Fecha</th><th>Pasajero</th><th>Ruta</th>
|
|
<th>Bruto</th><th>Comisión</th><th>Neto</th><th>Estado</th>
|
|
</tr></thead>
|
|
<tbody id="abd-history-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div id="abd-history-pagination" class="abd-pagination"></div>
|
|
</section>
|
|
|
|
<!-- ═══ PERFIL TAB ═══ -->
|
|
<section class="abd-panel" id="abd-tab-perfil" style="display:none">
|
|
<div class="abd-glass-card abd-profile-card">
|
|
<img id="abd-prof-photo" src="<?php echo esc_url( $render_photo ); ?>" alt="" class="abd-prof-avatar" <?php echo $render_photo ? '' : 'style="display:none"'; ?>>
|
|
<div id="abd-prof-name" class="abd-prof-name"></div>
|
|
<div id="abd-prof-vehicle" class="abd-prof-vehicle"></div>
|
|
<div id="abd-prof-email" class="abd-prof-meta"></div>
|
|
<div id="abd-prof-phone" class="abd-prof-meta"></div>
|
|
<div class="abd-profile-actions">
|
|
<button id="abd-btn-edit-profile" class="abd-btn abd-btn--ghost">✏️ Editar Perfil</button>
|
|
<button id="abd-btn-change-password" class="abd-btn abd-btn--ghost">🔒 Cambiar Contraseña</button>
|
|
</div>
|
|
</div>
|
|
<div class="abd-glass-card">
|
|
<div class="abd-card-title">Documentos</div>
|
|
<div class="abd-doc-row" id="doc-insurance">
|
|
<span class="abd-doc-icon"></span>
|
|
<span class="abd-doc-label">Seguro del vehículo</span>
|
|
<span class="abd-doc-expiry"></span>
|
|
<span class="abd-doc-status-badge"></span>
|
|
</div>
|
|
<div class="abd-doc-row" id="doc-license">
|
|
<span class="abd-doc-icon"></span>
|
|
<span class="abd-doc-label">Licencia de conducir</span>
|
|
<span class="abd-doc-expiry"></span>
|
|
<span class="abd-doc-status-badge"></span>
|
|
</div>
|
|
<div class="abd-doc-row" id="doc-inspection">
|
|
<span class="abd-doc-icon"></span>
|
|
<span class="abd-doc-label">Revisión técnica</span>
|
|
<span class="abd-doc-expiry"></span>
|
|
<span class="abd-doc-status-badge"></span>
|
|
</div>
|
|
</div>
|
|
<div class="abd-glass-card">
|
|
<div class="abd-card-title">Viajes programados</div>
|
|
<div id="abd-scheduled-list"><p style="color:rgba(255,255,255,.5);font-size:14px;">Sin viajes programados próximamente.</p></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ═══ SOS MODAL ═══ -->
|
|
<div id="abd-sos-modal" class="abd-modal-overlay" style="display:none">
|
|
<div class="abd-modal">
|
|
<h3>🚨 BOTÓN DE PÁNICO</h3>
|
|
<p>Se notificará al administrador con tu ubicación GPS y se iniciará grabación de audio.</p>
|
|
<p id="abd-sos-rec-status" style="color:rgba(255,255,255,.6);font-size:13px;"></p>
|
|
<video id="abd-sos-video" autoplay muted playsinline style="display:none;width:100%;border-radius:12px;margin:8px 0"></video>
|
|
<div class="abd-modal-actions">
|
|
<button id="abd-sos-confirm" class="abd-btn abd-btn--danger">⚡ ACTIVAR ALERTA</button>
|
|
<button id="abd-sos-cancel" class="abd-btn abd-btn--ghost">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ FINISH MODAL ═══ -->
|
|
<div id="abd-finish-modal" class="abd-modal-overlay" style="display:none">
|
|
<div class="abd-modal">
|
|
<h3>Finalizar Viaje</h3>
|
|
<div class="abd-modal-field">
|
|
<label>Peajes ($)</label>
|
|
<input type="number" id="abd-tolls-input" min="0" step="0.01" value="0" class="abd-modal-input">
|
|
</div>
|
|
<div class="abd-modal-field">
|
|
<label>Propina recibida ($)</label>
|
|
<input type="number" id="abd-tips-input" min="0" step="0.01" value="0" class="abd-modal-input">
|
|
</div>
|
|
<div class="abd-modal-actions">
|
|
<button id="abd-finish-confirm" class="abd-btn abd-btn--brand">✅ Confirmar y Finalizar</button>
|
|
<button id="abd-finish-cancel" class="abd-btn abd-btn--ghost">Volver</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ CAMBIAR CONTRASEÑA MODAL ═══ -->
|
|
<div id="abd-pwd-modal" class="abd-modal-overlay" style="display:none">
|
|
<div class="abd-modal">
|
|
<h3>🔒 Cambiar Contraseña</h3>
|
|
<div class="abd-modal-field">
|
|
<label>Contraseña actual</label>
|
|
<input type="password" id="abd-pwd-current" class="abd-modal-input" autocomplete="current-password">
|
|
</div>
|
|
<div class="abd-modal-field">
|
|
<label>Nueva contraseña</label>
|
|
<input type="password" id="abd-pwd-new" class="abd-modal-input" autocomplete="new-password">
|
|
</div>
|
|
<div class="abd-modal-field">
|
|
<label>Confirmar nueva contraseña</label>
|
|
<input type="password" id="abd-pwd-confirm" class="abd-modal-input" autocomplete="new-password">
|
|
</div>
|
|
<p id="abd-pwd-status" class="abd-modal-status"></p>
|
|
<div class="abd-modal-actions">
|
|
<button id="abd-pwd-save" class="abd-btn abd-btn--brand">Guardar</button>
|
|
<button id="abd-pwd-cancel" class="abd-btn abd-btn--ghost">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ EDITAR PERFIL MODAL ═══ -->
|
|
<div id="abd-editprofile-modal" class="abd-modal-overlay" style="display:none">
|
|
<div class="abd-modal">
|
|
<h3>✏️ Editar Perfil</h3>
|
|
<div class="abd-modal-field">
|
|
<label>Foto de perfil</label>
|
|
<div class="abd-photo-upload-row">
|
|
<img id="abd-ep-photo-preview" src="" alt="" class="abd-ep-photo-preview" style="display:none">
|
|
<button type="button" id="abd-ep-photo-btn" class="abd-btn abd-btn--ghost abd-btn--sm">📷 Seleccionar foto</button>
|
|
<input type="file" id="abd-ep-photo-file" accept="image/*" style="display:none">
|
|
</div>
|
|
<input type="url" id="abd-ep-photo-url" class="abd-modal-input" placeholder="O pega una URL de imagen" style="margin-top:6px;">
|
|
</div>
|
|
<div class="abd-modal-field">
|
|
<label>Teléfono</label>
|
|
<input type="tel" id="abd-ep-phone" class="abd-modal-input" autocomplete="tel">
|
|
</div>
|
|
<div class="abd-modal-field">
|
|
<label>Tipo de vehículo</label>
|
|
<input type="text" id="abd-ep-vehicle" class="abd-modal-input" placeholder="Ej: Sedan, SUV, Pickup…">
|
|
</div>
|
|
<div class="abd-modal-field">
|
|
<label>Placa del vehículo</label>
|
|
<input type="text" id="abd-ep-plate" class="abd-modal-input" placeholder="Ej: ABC-1234">
|
|
</div>
|
|
<p id="abd-ep-status" class="abd-modal-status"></p>
|
|
<div class="abd-modal-actions">
|
|
<button id="abd-ep-save" class="abd-btn abd-btn--brand">Guardar cambios</button>
|
|
<button id="abd-ep-cancel" class="abd-btn abd-btn--ghost">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
}
|
|
|
|
ABDriverDashboard2::get_instance();
|