' . 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 '' . esc_html( $all_labels[ $col ] ) . ' ';
}
}
echo '' . esc_html__( 'Actions', 'aisp-customer-activity' ) . ' ';
echo '' . esc_html__( 'Note / Reminder', 'aisp-customer-activity' ) . ' ';
echo ' ';
echo '';
foreach ( $customers as $customer ) {
self::render_table_row( $customer, $columns, $page_url );
}
echo '
';
}
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(
' %3$s %1$s ',
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 '' . esc_html( $lbl ) . ' ';
}
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(
'%5$s ',
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' ),
],
] );
}
}
}