' . esc_html( $desc ) . '

'; echo '
'; self::render_table_html( $columns, $page_url ); echo '
'; } /** * Render table HTML — called both on initial page load and AJAX refresh. */ public static function render_table_html( array $columns, string $page_url ) { $hours = (int) get_option( 'aisp_ca_activity_hours', 72 ); $customers = AISP_Customer_Activity_Tracker::get_active_customers( $hours, 200 ); if ( empty( $customers ) ) { echo '

' . esc_html__( 'No customers with an active cart right now.', 'aisp-customer-activity' ) . '

'; return; } $all_labels = array_merge( [ 'online_status' => __( 'Online Status', 'aisp-customer-activity' ), 'name' => __( 'Name', 'aisp-customer-activity' ), ], AISP_Customer_Activity_Settings::available_columns() ); echo ''; echo ''; foreach ( $columns as $col ) { if ( isset( $all_labels[ $col ] ) ) { echo ''; } } echo ''; echo ''; echo ''; echo ''; foreach ( $customers as $customer ) { self::render_table_row( $customer, $columns, $page_url ); } echo '
' . esc_html( $all_labels[ $col ] ) . '' . esc_html__( 'Actions', 'aisp-customer-activity' ) . '' . esc_html__( 'Note / Reminder', 'aisp-customer-activity' ) . '
'; } private static function render_table_row( object $customer, array $columns, string $redirect_url ) { $company = get_user_meta( $customer->ID, 'billing_company', true ); $first = get_user_meta( $customer->ID, 'billing_first_name', true ); $last = get_user_meta( $customer->ID, 'billing_last_name', true ); $full_name = trim( "$first $last" ); $last_activity = (int) get_user_meta( $customer->ID, 'aisp_ca_last_activity', true ); $cart_count = (int) get_user_meta( $customer->ID, 'aisp_ca_cart_count', true ); $cart_total = get_user_meta( $customer->ID, 'aisp_ca_cart_total', true ); $is_online = AISP_Customer_Activity_Tracker::is_online( $customer->ID ); $edit_url = get_edit_user_link( $customer->ID ); $hide_url = AISP_Customer_Activity_Tracker::hide_url( $customer->ID, $redirect_url ); $email = $customer->user_email; $phone = get_user_meta( $customer->ID, 'billing_phone', true ); // Fallback to WordPress native first_name / last_name if billing is empty. if ( empty( $full_name ) ) { $first = get_user_meta( $customer->ID, 'first_name', true ); $last = get_user_meta( $customer->ID, 'last_name', true ); $full_name = trim( "$first $last" ); } $activity_time = $last_activity ? date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $last_activity ) : '-'; echo ''; foreach ( $columns as $col ) { switch ( $col ) { case 'online_status': if ( $is_online ) { echo '🟢 ' . esc_html__( 'Online', 'aisp-customer-activity' ) . ''; } else { echo '⚪ ' . esc_html__( 'Offline', 'aisp-customer-activity' ) . ''; } break; case 'name': $display = $full_name ?: $customer->display_name; echo ''; echo '' . esc_html( $display ) . ''; if ( $email ) { echo '
✉ ' . esc_html( $email ) . ''; } if ( $phone ) { echo '
📞 ' . esc_html( $phone ) . ''; } echo ''; break; case 'company': echo '' . esc_html( $company ?: '—' ) . ''; break; case 'last_activity': echo '' . esc_html( $activity_time ) . ''; break; case 'cart_items': echo '' . esc_html( sprintf( // translators: %d: number of cart items. _n( '%d item', '%d items', $cart_count, 'aisp-customer-activity' ), $cart_count ) ) . ''; break; case 'cart_total': echo '' . wp_kses_post( wc_price( $cart_total ) ) . ''; break; } } // Actions always visible. echo ''; echo '' . esc_html__( 'Profile', 'aisp-customer-activity' ) . ''; echo ' | '; echo '' . esc_html__( 'Remove', 'aisp-customer-activity' ) . ''; echo ''; // Note / Reminder — always visible, editable. $note_data = AISP_Customer_Activity_Notes::get_bulk( [ $customer->ID ] ); $note = $note_data[ (int) $customer->ID ]['note'] ?? ''; $reminder = $note_data[ (int) $customer->ID ]['reminder'] ?? ''; AISP_Customer_Activity_Notes::render_note_cell( (int) $customer->ID, $note, $reminder ); echo ''; } // ------------------------------------------------------------------------- // Tab 2 — Settings // ------------------------------------------------------------------------- private static function render_settings_tab() { $settings_page = 'aisp-ca-settings-tab'; self::register_settings_sections( $settings_page ); ?>
' . esc_html__( 'Select which user roles will be tracked. WordPress core roles (administrator, editor, etc.) are always excluded.', 'aisp-customer-activity' ) . '

'; }, $page ); add_settings_field( 'aisp_ca_tracked_roles', __( 'Track these roles', 'aisp-customer-activity' ), function() { $trackable = AISP_Customer_Activity_Settings::get_trackable_roles(); $checked = AISP_Customer_Activity_Settings::get_tracked_roles(); echo '
'; foreach ( $trackable as $slug => $role ) { $label = translate_user_role( $role['name'] ); printf( '', esc_attr( $slug ), checked( in_array( $slug, $checked, true ), true, false ), esc_html( $label ) ); } echo '
'; echo '

' . esc_html__( 'Only users with at least one checked role will be tracked.', 'aisp-customer-activity' ) . '

'; }, $page, 'aisp_ca_s_roles' ); // ── Display ───────────────────────────────────────────────────────────── add_settings_section( 'aisp_ca_s_display', __( 'Display', 'aisp-customer-activity' ), '__return_false', $page ); add_settings_field( 'aisp_ca_activity_hours', __( 'Activity window', 'aisp-customer-activity' ), function() { $value = (int) get_option( 'aisp_ca_activity_hours', 72 ); $presets = [ 24 => __( '1 day', 'aisp-customer-activity' ), 48 => __( '2 days', 'aisp-customer-activity' ), 72 => __( '3 days', 'aisp-customer-activity' ), 168 => __( '7 days', 'aisp-customer-activity' ), 336 => __( '14 days', 'aisp-customer-activity' ), 720 => __( '30 days', 'aisp-customer-activity' ), 0 => __( 'All', 'aisp-customer-activity' ), ]; echo ''; echo '
'; foreach ( $presets as $h => $lbl ) { $is_active = ( $h === $value ) ? ' button-primary' : ''; echo ' '; } echo '
'; echo '

' . esc_html__( 'Show customers with an active cart who were active within this period. "All" keeps all data indefinitely.', 'aisp-customer-activity' ) . '

'; }, $page, 'aisp_ca_s_display' ); add_settings_field( 'aisp_ca_widget_limit', __( 'Max customers in widget', 'aisp-customer-activity' ), function() { $value = (int) get_option( 'aisp_ca_widget_limit', 5 ); echo ''; echo '

' . esc_html__( 'Maximum number of customers shown in the dashboard widget.', 'aisp-customer-activity' ) . '

'; }, $page, 'aisp_ca_s_display' ); add_settings_field( 'aisp_ca_refresh_interval', __( 'Table auto-refresh', 'aisp-customer-activity' ), function() { $seconds = (int) get_option( 'aisp_ca_refresh_interval', 300 ); $minutes = $seconds > 0 ? max( 1, (int) round( $seconds / 60 ) ) : 0; echo ''; echo ''; echo ' ' . esc_html__( 'minutes', 'aisp-customer-activity' ) . ''; echo '

' . esc_html__( 'Automatically refresh the activity table without reloading the page. Set to 0 to disable.', 'aisp-customer-activity' ) . '

'; }, $page, 'aisp_ca_s_display' ); // ── Widget columns ─────────────────────────────────────────────────────── add_settings_section( 'aisp_ca_s_widget_cols', __( 'Widget — Visible Fields', 'aisp-customer-activity' ), function() { echo '

' . esc_html__( 'Choose what appears in the dashboard widget. Online Status and Name are always visible.', 'aisp-customer-activity' ) . '

'; }, $page ); add_settings_field( 'aisp_ca_widget_columns', __( 'Show in widget', 'aisp-customer-activity' ), function() { self::render_column_checkboxes( 'widget' ); }, $page, 'aisp_ca_s_widget_cols' ); // ── Page columns ───────────────────────────────────────────────────────── add_settings_section( 'aisp_ca_s_page_cols', __( 'Activity Page — Visible Columns', 'aisp-customer-activity' ), function() { echo '

' . esc_html__( 'Choose what columns appear in the full activity table. Online Status and Name are always visible.', 'aisp-customer-activity' ) . '

'; }, $page ); add_settings_field( 'aisp_ca_page_columns', __( 'Show in table', 'aisp-customer-activity' ), function() { self::render_column_checkboxes( 'page' ); }, $page, 'aisp_ca_s_page_cols' ); // ── Online detection ───────────────────────────────────────────────────── add_settings_section( 'aisp_ca_s_online', __( 'Online Detection', 'aisp-customer-activity' ), '__return_false', $page ); add_settings_field( 'aisp_ca_online_minutes', __( 'Online threshold', 'aisp-customer-activity' ), function() { $value = (int) get_option( 'aisp_ca_online_minutes', 5 ); echo ''; echo ' ' . esc_html__( 'minutes', 'aisp-customer-activity' ) . ''; echo '

' . esc_html__( 'A customer is shown as online if active within this many minutes.', 'aisp-customer-activity' ) . '

'; }, $page, 'aisp_ca_s_online' ); } /** Shared toggle-button renderer for column settings — only configurable columns. */ private static function render_column_checkboxes( string $context ) { $all = AISP_Customer_Activity_Settings::available_columns(); $saved = get_option( "aisp_ca_{$context}_columns", [] ); $checked = ! empty( $saved ) ? (array) $saved : array_keys( $all ); $name = "aisp_ca_{$context}_columns"; echo '
'; foreach ( $all as $key => $label ) { $is_active = in_array( $key, $checked, true ); printf( '', esc_attr( $name ), esc_attr( $key ), checked( $is_active, true, false ), $is_active ? ' button-primary' : '', esc_html( $label ) ); } echo '
'; } // ------------------------------------------------------------------------- // AJAX — table refresh // ------------------------------------------------------------------------- public static function ajax_refresh_table() { check_ajax_referer( 'aisp_ca_refresh', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( 'Permission denied.', 403 ); } $columns = AISP_Customer_Activity_Settings::get_columns( 'page' ); $page_url = admin_url( 'admin.php?page=' . self::MENU_SLUG ); ob_start(); self::render_table_html( $columns, $page_url ); $html = ob_get_clean(); wp_send_json_success( [ 'html' => $html ] ); } // ------------------------------------------------------------------------- // Scripts // ------------------------------------------------------------------------- public static function enqueue_scripts( string $hook ) { if ( 'toplevel_page_' . self::MENU_SLUG !== $hook ) { return; } $tab = self::current_tab(); $interval = (int) get_option( 'aisp_ca_refresh_interval', 300 ); wp_register_script( 'aisp-ca-admin', AISP_CA_PLUGIN_URL . 'assets/js/aisp-ca-admin.js', [], AISP_CA_VERSION, true ); wp_enqueue_script( 'aisp-ca-admin' ); $config = [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'aisp_ca_refresh' ), 'refreshInterval' => ( self::TAB_ACTIVITY === $tab && $interval > 0 ) ? $interval * 1000 : 0, 'noRolesMsg' => __( 'No roles selected — no customers will be tracked. Continue?', 'aisp-customer-activity' ), ]; wp_add_inline_script( 'aisp-ca-admin', 'var aispCaConfig = ' . wp_json_encode( $config ) . ';', 'before' ); // Enqueue notes script on Dormant tab and Active Cart tab. if ( self::TAB_DORMANT === $tab || self::TAB_ACTIVITY === $tab ) { wp_enqueue_script( 'aisp-ca-notes', AISP_CA_PLUGIN_URL . 'assets/js/aisp-ca-notes.js', [ 'jquery' ], AISP_CA_VERSION, true ); wp_localize_script( 'aisp-ca-notes', 'aispCaNotes', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'aisp_ca_save_note' ), 'i18n' => [ 'saving' => __( 'Saving…', 'aisp-customer-activity' ), 'saved' => __( 'Saved!', 'aisp-customer-activity' ), 'error' => __( 'Error.', 'aisp-customer-activity' ), 'addNote' => __( '📝 Add note', 'aisp-customer-activity' ), 'edit' => __( '✏️ Edit', 'aisp-customer-activity' ), ], ] ); } } }