db = new Booktics_Database(); } /** * Register all routes * * @return void */ public function register_routes(): void { register_rest_route( 'booktics/v1', '/dashboard', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_dashboard_data' ), 'permission_callback' => array( $this, 'check_permission' ), 'args' => array( 'total_bookings' => array( 'type' => 'object', 'required' => false, 'properties' => array( 'start_date' => array( 'type' => 'string', 'format' => 'date', ), 'end_date' => array( 'type' => 'string', 'format' => 'date', ), ), ), 'total_revenue' => array( 'type' => 'object', 'required' => false, 'properties' => array( 'start_date' => array( 'type' => 'string', 'format' => 'date', ), 'end_date' => array( 'type' => 'string', 'format' => 'date', ), ), ), 'total_customers' => array( 'type' => 'object', 'required' => false, 'properties' => array( 'start_date' => array( 'type' => 'string', 'format' => 'date', ), 'end_date' => array( 'type' => 'string', 'format' => 'date', ), ), ), 'booking_reports' => array( 'type' => 'object', 'required' => false, 'properties' => array( 'start_date' => array( 'type' => 'string', 'format' => 'date', ), 'end_date' => array( 'type' => 'string', 'format' => 'date', ), ), ), ), ) ); } /** * Get dashboard data * * @param WP_REST_Request $request * * @return WP_HTTP_Response */ public function get_dashboard_data( WP_REST_Request $request ) { $data = array(); foreach ( self::REPORT_TYPES as $report_type ) { $report_data = $request->get_param( $report_type ); if ( is_array( $report_data ) && isset( $report_data['start_date'], $report_data['end_date'] ) ) { $date_range = sanitize_text_field( $report_data['start_date'] ) . ' to ' . sanitize_text_field( $report_data['end_date'] ); $data[ $report_type ] = $this->get_report_data( $report_type, $date_range ); } } if ( empty( $data ) ) { $data = new \stdClass(); } return $this->response( $data ); } /** * Get report data based on type * * @param string $report_type * @param string $date_range * * @return mixed */ private function get_report_data( $report_type, $date_range ) { switch ( $report_type ) { case 'total_bookings': return $this->get_total_bookings( $date_range ); case 'total_revenue': return $this->get_total_revenue( $date_range ); case 'total_customers': return $this->get_total_customers( $date_range ); case 'booking_reports': return $this->get_booking_reports( $date_range ); default: return null; } } /** * Get total bookings count for a date range * * @param string $date_range * * @return array */ private function get_total_bookings( $date_range ) { $appointments = new Appointment_Model(); $dates = $this->parse_date_range( $date_range ); if ( ! $dates ) { return array( 'total' => 0, 'percentage' => '0.00', 'trend' => 'neutral', ); } // Get current period bookings $current_query = array( 'post_status' => array( 'completed', 'approved' ), 'date_query' => array( 'after' => $dates['start'], 'before' => $dates['end'], 'inclusive' => true, ), ); $current_query = apply_filters( 'booktics_appointment_args', $current_query ); $current_results = $appointments->all( $current_query ); $current_bookings = count( $current_results ); // Calculate previous period dates $previous_dates = $this->get_previous_period_dates( $dates ); // Get previous period bookings $previous_query = array( 'date_query' => array( 'after' => $previous_dates['start'], 'before' => $previous_dates['end'], ), 'post_status' => array( 'completed', 'approved' ), ); $previous_query = apply_filters( 'booktics_appointment_args', $previous_query ); $previous_results = $appointments->all( $previous_query ); $previous_bookings = count( $previous_results ); // Calculate percentage change $percentage_change = 0; $trend = 'neutral'; if ( 0 != $previous_bookings ) { $percentage_change = ( ( $current_bookings - $previous_bookings ) / $previous_bookings ) * 100; $trend = $this->get_trend( $percentage_change ); } return array( 'total' => $current_bookings, 'percentage_changed' => number_format( $percentage_change, 2 ), 'trend' => $trend, ); } /** * Get total revenue for a date range * * @param string $date_range * * @return array */ private function get_total_revenue( $date_range ) { $orders = new Order_Model( $this->db ); $dates = $this->parse_date_range( $date_range ); if ( ! $dates ) { return array( 'total' => '0.00', 'percentage_changed' => '0.00', 'trend' => 'neutral', ); } // Get order IDs to filter by (if needed) $order_ids = $this->get_order_ids(); // Get current period revenue using Order_Model method $current_revenue = $orders->get_revenue_by_date_range( $dates['start'], $dates['end'], $order_ids ); // Calculate previous period dates $previous_dates = $this->get_previous_period_dates( $dates ); // Get previous period revenue using Order_Model method $previous_revenue = $orders->get_revenue_by_date_range( $previous_dates['start'], $previous_dates['end'], $order_ids ); // Calculate percentage change $percentage_change = 0; $trend = 'neutral'; // Ensure both values are valid numbers before calculation if ( is_numeric( $previous_revenue ) && 0 != $previous_revenue && is_numeric( $current_revenue ) ) { $percentage_change = ( ( $current_revenue - $previous_revenue ) / $previous_revenue ) * 100; // Check if the result is a valid number if ( !is_finite( $percentage_change ) ) { $percentage_change = 0; } else { $trend = $this->get_trend( $percentage_change ); } } return array( 'total' => round( (float) $current_revenue, 2 ), 'percentage_changed' => round( (float) $percentage_change, 2 ), 'trend' => $trend, ); } /** * Get order ids for a date range * * @param array $dates * * @return array */ private function get_order_ids() { $order_ids = array(); $args = array(); $args = apply_filters( 'booktics_appointment_args', $args ); $appointments = ( new Appointment_Model() )->all( $args ); foreach ( $appointments as $appointment ) { $order_ids[] = $appointment->order_id; } return $order_ids; } /** * Get total customers count for a date range * * @param string $date_range * * @return array */ private function get_total_customers( $date_range ) { $db = new Booktics_Database(); $guests = new Guest_Model( $db ); $dates = $this->parse_date_range( $date_range ); if ( ! $dates ) { return array( 'total' => 0, 'percentage_changed' => '0.00', 'trend' => 'neutral', ); } if ( empty( $dates['start'] ) || empty( $dates['end'] ) ) { return array( 'total' => 0, 'percentage_changed' => '0.00', 'trend' => 'neutral', ); } // Get current period customers $current_results = $guests->count( array( 'created_at' => array( '>=', $dates['start'] . ' 00:00:00' ), 'created_at' => array( '<=', $dates['end'] . ' 23:59:59' ), ) ); // Calculate previous period dates $previous_dates = $this->get_previous_period_dates( $dates ); // Get previous period customers $previous_results = $guests->count( array( 'created_at' => array( '>=', $previous_dates['start'] . ' 00:00:00' ), 'created_at' => array( '<=', $previous_dates['end'] . ' 23:59:59' ), ) ); // Calculate percentage change $percentage_change = 0; $trend = 'neutral'; if ( 0 != $previous_results ) { $percentage_change = ( ( $current_results - $previous_results ) / $previous_results ) * 100; $trend = $this->get_trend( $percentage_change ); } return array( 'total' => $current_results, 'percentage_changed' => number_format( $percentage_change, 2 ), 'trend' => $trend, ); } /** * Get booking reports by date with completed and cancelled counts * * @param string $date_range * * @return array */ private function get_booking_reports( $date_range ) { $appointments = new Appointment_Model(); $dates = $this->parse_date_range( $date_range ); if ( ! $dates ) { return array(); } // Generate date range array $start_date = new \DateTime( $dates['start'] ); $end_date = new \DateTime( $dates['end'] ); $interval = new \DateInterval( 'P1D' ); $period = new \DatePeriod( $start_date, $interval, $end_date ); $reports = array(); foreach ( $period as $date ) { $date_str = $date->format( 'Y-m-d' ); $reports[ $date_str ] = array( 'completed' => 0, 'cancel' => 0, ); } // Query appointments using PostModel's methods $query = array( 'post_type' => $appointments->get_post_type(), 'post_status' => array( 'completed', 'cancelled', 'canceled' ), 'date_query' => array( array( 'after' => $dates['start'], 'before' => $dates['end'], 'inclusive' => true, ), ), ); $query = apply_filters( 'booktics_appointment_args', $query ); $appointments = Appointment_Model::all( $query ); foreach ( $appointments as $appointment ) { $date_str = $appointment->date; if ( isset( $reports[ $date_str ] ) ) { if ( $appointment->status === 'completed' ) { ++$reports[ $date_str ]['completed']; } elseif ( $appointment->status === 'cancelled' || $appointment->status === 'canceled' ) { //both spellings are used for backward compatibility ++$reports[ $date_str ]['cancel']; } } } return $reports; } /** * Parse date range string into start and end dates * * @param string $date_range * * @return array|null */ private function parse_date_range( $date_range ) { if ( empty( $date_range ) ) { return null; } $dates = explode( 'to', $date_range ); if ( count( $dates ) !== 2 ) { return null; } $start = trim( $dates[0] ); $end = trim( $dates[1] ); return array( 'start' => $start, 'end' => $end, ); } /** * Get the trend based on the percentage change. * * @param float $percentage_change Percentage change from previous period * * @return string 'positive', 'negative', or 'neutral' */ private function get_trend( $percentage_change ) { if ( $percentage_change > 0 ) { return 'positive'; } elseif ( $percentage_change < 0 ) { return 'negative'; } return 'neutral'; } /** * Calculate the previous period start and end dates given the current period start and end dates. * * @param array $dates Array of start and end dates * * @return array Array of previous period start and end dates */ private function get_previous_period_dates( $dates ) { $start_time_in_seconds = strtotime( $dates['start'] ); $end_time_in_seconds = strtotime( $dates['end'] ); // Calculate the gap in days (inclusive) $gap_days = round( ( $end_time_in_seconds - $start_time_in_seconds ) / DAY_IN_SECONDS ); // Subtract the gap from both start and end $previous_start = strtotime( "-$gap_days days", $start_time_in_seconds ); $previous_end = strtotime( "-$gap_days days", $end_time_in_seconds ); return array( 'start' => gmdate( 'Y-m-d', $previous_start ), 'end' => gmdate( 'Y-m-d', $previous_end ), ); } /** * Check if user has permission to access dashboard data * * @return bool */ public function check_permission() { return current_user_can( 'manage_options' ); } }