*/ public $status_counts = []; /** * Configure list table labels and options. * * @return void */ public function __construct() { parent::__construct([ 'singular' => 'booking', 'plural' => 'bookings', 'ajax' => false, ]); } /** * Column definitions for the bookings table. * * @since 1.0.0 * * @return array */ public function get_columns(): array { return [ 'cb' => '', 'id' => 'ID', 'property' => 'Property', 'check_in' => 'Check-in', 'nights' => 'Nights', 'guests' => 'Guests', 'amount' => 'Amount', 'status' => 'Status', 'date' => 'Date', ]; } /** * Sortable column configuration. * * @since 1.0.0 * * @return array */ protected function get_sortable_columns(): array { return [ 'id' => ['ID', false], 'check_in' => ['check_in', false], 'date' => ['date', true], ]; } /** * Status filter links above the table (cards handle this on the page). * * @since 1.0.0 * * @return array */ protected function get_views(): array { return []; } /** * Message shown when no bookings match the current filters. * * @since 1.0.0 * * @return void */ public function no_items(): void { esc_html_e('No booking entries found.', 'advanced-connector-for-uplisting'); } /** * Render a column cell for a booking row. * * Parameter types stay untyped to match {@see \WP_List_Table::column_default()}. * * @since 1.0.0 * * @param \WP_Post $item Booking post object. * @param string $column_name Column key. * @return string */ public function column_default($item, $column_name): string { $booking = new BookingDetails($item->ID); switch ($column_name) { case 'id': return '#' . esc_html((string) $item->ID) . ''; case 'property': $prop_id = (int) $booking->get_property_id(); if (!$prop_id) { return '' . esc_html__('N/A', 'advanced-connector-for-uplisting') . ''; } $permalink = get_permalink($prop_id); $title = get_the_title($prop_id); if ($permalink) { return '' . esc_html($title) . ''; } return '' . esc_html($title) . ''; case 'check_in': $check_in = (string) $booking->get_check_in(); if ($check_in === '') { return '—'; } return '' . esc_html($check_in) . ''; case 'nights': $nights = (int) $booking->get_nights_count(); return '' . esc_html( sprintf( /* translators: %d: number of nights */ _n('%d night', '%d nights', $nights, 'advanced-connector-for-uplisting'), $nights ) ) . ''; case 'guests': $adults = (int) $booking->get_adults(); $kids = (int) $booking->get_kids(); return '' . esc_html( sprintf( /* translators: 1: adults count, 2: children count */ __('%1$d adults · %2$d children', 'advanced-connector-for-uplisting'), $adults, $kids ) ) . ''; case 'amount': return '' . esc_html(bizdex_uplisting_format_price($booking->get_display_total_amount())) . ''; case 'status': return self::render_status_badge((string) ($booking->get_payment_status() ?: 'enquiry')); case 'date': return '' . '' . esc_html(get_the_date('Y/m/d', $item->ID)) . '' . '' . esc_html(get_the_date('g:i a', $item->ID)) . '' . ''; default: return ''; } } /** * Checkbox column for bulk actions. * * @param \WP_Post $item Booking post object. * @return string */ /** * Checkbox column for bulk actions. * * @since 1.0.0 * * @param \WP_Post $item Booking post object. * @return string */ public function column_cb($item): string { return sprintf( '', esc_attr((string) absint($item->ID)) ); } /** * ID column with row actions (view, delete). * * @since 1.0.0 * * @param \WP_Post $item Booking post object. * @return string */ public function column_id($item): string { $view_url = admin_url('admin.php?page=' . Bookings::VIEW_ENTRY_PAGE_SLUG . '&entry_id=' . $item->ID); $actions = [ 'view' => sprintf('View', esc_url($view_url)), 'delete' => sprintf( 'Delete', esc_url(wp_nonce_url( Bookings::get_admin_url(['action' => 'delete', 'entry_id' => $item->ID]), Bookings::DELETE_NONCE )), esc_attr__('Are you sure you want to delete this booking?', 'advanced-connector-for-uplisting') ), ]; return sprintf( '%1$s %2$s', '#' . esc_html((string) $item->ID) . '', $this->row_actions($actions) ); } /** * Bulk actions available on the bookings list. * * @since 1.0.0 * * @return array */ public function get_bulk_actions(): array { return [ 'delete' => 'Delete', 'mark_completed' => 'Mark as Completed', 'mark_pending' => 'Mark as Pending', 'mark_cancelled' => 'Mark as Cancelled', ]; } /** * Query bookings and populate table rows and pagination. * * @since 1.0.0 * * @return void */ public function prepare_items(): void { $columns = $this->get_columns(); $hidden = []; $sortable = $this->get_sortable_columns(); $this->_column_headers = [$columns, $hidden, $sortable]; $per_page = 20; $current_page = $this->get_pagenum(); $status = Request::key_in_list( 'get', 'status', array_merge(['all'], array_keys(ReservationService::get_booking_payment_statuses())), 'all' ); $property_id = Request::absint('get', 'property_id'); $date_filter = Request::key_in_list( 'get', 'date_filter', ['today', 'yesterday', 'last_7_days', 'last_30_days', 'this_month'], '' ); $search = Request::text('request', 's'); $args = [ 'post_type' => Bookings::get_booking_post_types(), 'posts_per_page' => $per_page, 'paged' => $current_page, 'post_status' => 'any', 'orderby' => 'date', 'order' => 'DESC', ]; $meta_query = []; if ($status && $status !== 'all') { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('_booking_payment_status'), 'value' => $status, 'compare' => '=', ]; } if ($property_id > 0) { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('_booking_property_id'), 'value' => $property_id, 'compare' => '=', ]; } if (!empty($meta_query)) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Booking list property filter. $args['meta_query'] = $meta_query; } if ($date_filter) { $date_query = []; switch ($date_filter) { case 'today': $date_query['after'] = 'today'; break; case 'yesterday': $date_query['after'] = 'yesterday'; $date_query['before'] = 'today'; break; case 'last_7_days': $date_query['after'] = '7 days ago'; break; case 'last_30_days': $date_query['after'] = '30 days ago'; break; case 'this_month': $date_query['year'] = gmdate('Y'); $date_query['month'] = gmdate('m'); break; } if (!empty($date_query)) { $args['date_query'] = [$date_query]; } } if ($search !== '') { $args['s'] = $search; } $query = new \WP_Query($args); $this->items = $query->posts; $this->set_pagination_args([ 'total_items' => $query->found_posts, 'per_page' => $per_page, 'total_pages' => $query->max_num_pages, ]); } /** * HTML badge for a booking payment status. * * @since 1.0.0 * * @param string $status Payment status slug. * @return string */ public static function render_status_badge(string $status): string { $status = $status !== '' ? $status : 'pending'; $labels = ReservationService::get_booking_payment_statuses(); $label = $labels[$status] ?? ucfirst($status); $icon = ReservationService::get_booking_payment_status_icon($status); return sprintf( '%3$s', esc_attr($status), esc_attr($icon), esc_html($label) ); } }