PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.0
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.0
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / includes / Admin / views / subscription-list.php

subscription-list.php in Subscriptions for WooCommerce with Stripe Recurring Payments 1.10.0, at includes/Admin/views/subscription-list.php

610 lines 22.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Subscription admin list view.
4 *
5 * @package SpringDevs\Subscription\Admin
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 if ( ! isset( $date_filter ) ) {
13 $date_filter = '';
14 }
15
16 $filters_active = ! empty( $status ) || ! empty( $date_filter ) || ! empty( $search );
17 $months = array();
18 for ( $i = 0; $i < 12; $i++ ) {
19 $month = strtotime( "-$i month" );
20 $months[ date( 'Y-m', $month ) ] = date( 'F Y', $month );
21 }
22 ?>
23 <div class="wp-subscription-admin-content list-page subscrpt-subs-list">
24
25 <!-- Page header -->
26 <div style="margin-bottom:20px;">
27 <h1 style="font-size:1.375rem;font-weight:700;color:var(--wpsubs-text);margin:0 0 6px;line-height:1.2;"><?php esc_html_e( 'Subscriptions', 'subscription' ); ?></h1>
28 <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0 0 12px;line-height:1.5;"><?php esc_html_e( 'Manage all your WooCommerce subscriptions.', 'subscription' ); ?></p>
29 <div style="border-top:1px dashed #d0d3d7;"></div>
30 </div>
31
32 <form method="post" id="subscriptions-form">
33 <?php wp_nonce_field( 'subscrpt_list_action' ); ?>
34 <input type="hidden" name="page" value="wp-subscription" />
35
36 <!-- Toolbar -->
37 <div class="wpsubs-toolbar">
38
39 <?php
40 wpsubs_render_adv_select(
41 array(
42 'name' => 'subscrpt_status',
43 'placeholder' => __( 'All Status', 'subscription' ),
44 'value' => $status,
45 'options' => array(
46 array(
47 'value' => 'active',
48 'label' => __( 'Active', 'subscription' ),
49 ),
50 array(
51 'value' => 'pending',
52 'label' => __( 'Pending', 'subscription' ),
53 ),
54 array(
55 'value' => 'cancelled',
56 'label' => __( 'Cancelled', 'subscription' ),
57 ),
58 array(
59 'value' => 'expired',
60 'label' => __( 'Expired', 'subscription' ),
61 ),
62 array(
63 'value' => 'draft',
64 'label' => __( 'Draft', 'subscription' ),
65 ),
66 array(
67 'value' => 'trash',
68 'label' => __( 'Trash', 'subscription' ),
69 ),
70 ),
71 )
72 );
73 ?>
74
75 <?php
76 $date_options = array();
77 foreach ( $months as $val => $label ) {
78 $date_options[] = array(
79 'value' => $val,
80 'label' => $label,
81 );
82 }
83 wpsubs_render_adv_select(
84 array(
85 'name' => 'date_filter',
86 'placeholder' => __( 'All Dates', 'subscription' ),
87 'value' => $date_filter,
88 'options' => $date_options,
89 )
90 );
91 ?>
92
93 <div class="wpsubs-search">
94 <div class="wpsubs-input-wrap wpsubs-input-wrap--icon-l">
95 <svg class="wpsubs-input-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/></svg>
96 <input type="search" name="s" value="<?php echo esc_attr( $search ); ?>" placeholder="<?php esc_attr_e( 'Search subscriptions...', 'subscription' ); ?>" class="wpsubs-input" />
97 </div>
98 </div>
99
100 <button type="submit" name="filter_action" value="filter" class="wpsubs-btn wpsubs-btn--outline">
101 <?php esc_html_e( 'Filter', 'subscription' ); ?>
102 </button>
103
104 <?php if ( $filters_active ) : ?>
105 <a href="<?php echo esc_url( remove_query_arg( array( 'subscrpt_status', 'date_filter', 's', 'title', 'paged' ) ) ); ?>" class="wpsubs-btn wpsubs-btn--outline">
106 <?php esc_html_e( 'Clear', 'subscription' ); ?>
107 </a>
108 <?php endif; ?>
109
110 <div class="wpsubs-toolbar__spacer"></div>
111
112 <?php
113 $current_per_page = isset( $_GET['per_page'] ) ? intval( wp_unslash( $_GET['per_page'] ) ) : 20;
114 wpsubs_render_adv_select(
115 array(
116 'name' => 'per_page',
117 'value' => (string) $current_per_page,
118 'options' => array(
119 array(
120 'value' => '10',
121 'label' => __( '10 / page', 'subscription' ),
122 ),
123 array(
124 'value' => '20',
125 'label' => __( '20 / page', 'subscription' ),
126 ),
127 array(
128 'value' => '50',
129 'label' => __( '50 / page', 'subscription' ),
130 ),
131 array(
132 'value' => '100',
133 'label' => __( '100 / page', 'subscription' ),
134 ),
135 ),
136 'align' => 'right',
137 'id' => 'wpsubs-per-page-select',
138 )
139 );
140 ?>
141
142 <?php
143 // Bulk action advanced-select + hidden submit.
144 $bulk_options = 'trash' === $status
145 ? array(
146 array(
147 'value' => 'restore',
148 'label' => __( 'Restore selected', 'subscription' ),
149 ),
150 array(
151 'value' => 'delete',
152 'label' => __( 'Delete permanently', 'subscription' ),
153 'danger' => true,
154 'confirm' => __( 'Delete selected subscriptions permanently? This cannot be undone.', 'subscription' ),
155 ),
156 )
157 : array(
158 array(
159 'value' => 'trash',
160 'label' => __( 'Move to trash', 'subscription' ),
161 'danger' => true,
162 'confirm' => __( 'Move selected subscriptions to trash?', 'subscription' ),
163 ),
164 );
165
166 wpsubs_render_adv_select(
167 array(
168 'name' => 'action',
169 'placeholder' => __( 'Select Action', 'subscription' ),
170 'value' => '-1',
171 'options' => $bulk_options,
172 'id' => 'wpsubs-bulk-action-select',
173 'align' => 'right',
174 )
175 );
176 ?>
177 <button type="submit" name="bulk_action" value="1" id="wpsubs-bulk-action-submit" style="display:none;" aria-hidden="true"></button>
178
179 <?php
180 if ( 'trash' === $status && ! empty( $subscriptions ) ) :
181 $empty_trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=clean_trash&sub_id=all' ), 'wpsubs_action_clean_trash' );
182 ?>
183 <a href="<?php echo esc_url( $empty_trash_url ); ?>" class="wpsubs-btn wpsubs-btn--danger" onclick="return confirm('<?php esc_attr_e( 'Permanently delete all trash items? This cannot be undone.', 'subscription' ); ?>')">
184 <?php esc_html_e( 'Empty Trash', 'subscription' ); ?>
185 </a>
186 <?php endif; ?>
187
188 </div><!-- /.wpsubs-toolbar -->
189
190 <!-- Table card -->
191 <h2 class="screen-reader-text"><?php esc_html_e( 'Subscriptions list', 'subscription' ); ?></h2>
192 <div class="wpsubs-table-card">
193 <table class="wpsubs-table">
194 <thead>
195 <tr>
196 <th class="wpsubs-col--check">
197 <input type="checkbox" id="cb-select-all" class="wpsubs-checkbox">
198 </th>
199 <th style="width:1%;white-space:nowrap;"><?php esc_html_e( 'Status', 'subscription' ); ?></th>
200 <th><?php esc_html_e( 'Subscription', 'subscription' ); ?></th>
201 <th><?php esc_html_e( 'Customer', 'subscription' ); ?></th>
202 <th><?php esc_html_e( 'Amount', 'subscription' ); ?></th>
203 <th><?php esc_html_e( 'Started', 'subscription' ); ?></th>
204 <th><?php esc_html_e( 'Next Renewal', 'subscription' ); ?></th>
205 <th class="wpsubs-col--actions"><?php esc_html_e( 'Actions', 'subscription' ); ?></th>
206 </tr>
207 </thead>
208 <tbody>
209
210 <?php if ( ! empty( $subscriptions ) ) : ?>
211 <?php
212 foreach ( $subscriptions as $subscription ) :
213 $subscription_id = $subscription->ID;
214 $subscription_data = SpringDevs\Subscription\Illuminate\Helper::get_subscription_data( $subscription_id );
215
216 $subscrpt_status = $subscription_data['status'] ?? '';
217 $subscrpt_status = empty( $subscrpt_status ) ? get_post_status( $subscription_id ) : $subscrpt_status;
218
219 $order_id = $subscription_data['order']['order_id'] ?? 0;
220 $order_item_id = $subscription_data['order']['order_item_id'] ?? 0;
221 $order = $order_id ? wc_get_order( $order_id ) : null;
222 $order_item = $order ? $order->get_item( $order_item_id ) : null;
223 $product_name = $order_item ? $order_item->get_name() : '-';
224
225 $product_id = $subscription_data['product']['variation_id'] ?: ( $subscription_data['product']['product_id'] ?? 0 );
226 $product_url = $product_id ? get_edit_post_link( $product_id ) : '';
227
228 $customer = $order ? $order->get_formatted_billing_full_name() : '-';
229 $customer_id = $order ? $order->get_customer_id() : 0;
230 $customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : '';
231 $customer_email = $order ? $order->get_billing_email() : '';
232
233 $start_date = $subscription_data['start_date'] ? strtotime( $subscription_data['start_date'] ) : 0;
234 $renewal_date = $subscription_data['next_date'] ? strtotime( $subscription_data['next_date'] ) : 0;
235
236 $is_trash = 'trash' === $subscription->post_status;
237 $is_grace_period = isset( $subscription_data['grace_period'] );
238 $grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0;
239
240 // Amount + timing
241 $quantity = $order_item ? (int) $order_item->get_quantity() : 1;
242 $price = '' !== ( $subscription_data['price'] ?? '' )
243 ? (float) $subscription_data['price'] * max( 1, $quantity )
244 : '';
245 $timing_per = $subscription_data['schedule']['timing_per'] ?? '';
246 $timing_option = $subscription_data['schedule']['timing_option'] ?? '';
247 $timing_label = '';
248 if ( $timing_per && $timing_option ) {
249 $timing_label = ( (int) $timing_per > 1 )
250 ? $timing_per . ' ' . $timing_option . 's'
251 : $timing_option;
252 }
253
254 // Avatar
255 $name_parts = array_values( array_filter( explode( ' ', trim( $customer ) ) ) );
256 $initials = '?';
257 if ( $name_parts ) {
258 $initials = '';
259 foreach ( array_slice( $name_parts, 0, 2 ) as $part ) {
260 $initials .= strtoupper( $part[0] );
261 }
262 }
263 $color_slot = ord( strtolower( $initials[0] ?? 'a' ) ) % 8;
264
265 // Action URLs
266 $nonce_action = 'wpsubs_action_' . $subscription->ID;
267 $view_url = get_edit_post_link( $subscription->ID );
268 $trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=trash&sub_id=' . $subscription->ID ), $nonce_action );
269 $delete_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=delete&sub_id=' . $subscription->ID ), $nonce_action );
270 $restore_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=restore&sub_id=' . $subscription->ID ), $nonce_action );
271
272 // Status badge
273 $badge_mod_map = array(
274 'active' => 'active',
275 'pending' => 'pending',
276 'pe_cancelled' => 'pending-cancel',
277 'cancelled' => 'cancelled',
278 'expired' => 'expired',
279 'draft' => 'draft',
280 'trash' => 'trash',
281 );
282 $badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired';
283 $verbose_status = SpringDevs\Subscription\Illuminate\Helper::get_verbose_status( $subscrpt_status );
284
285 if ( $is_grace_period && $grace_remaining > 0 ) {
286 $badge_mod = 'active';
287 $verbose_status = __( 'Active', 'subscription' );
288 }
289 ?>
290 <tr>
291 <!-- Checkbox -->
292 <td class="wpsubs-col--check">
293 <input type="checkbox" name="subscription_ids[]" value="<?php echo esc_attr( $subscription->ID ); ?>" class="wpsubs-checkbox wpsubs-row-check">
294 </td>
295
296 <!-- Status badge -->
297 <td style="white-space:nowrap;">
298 <span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_mod ); ?>">
299 <?php echo esc_html( $verbose_status ); ?>
300 <?php if ( $is_grace_period && $grace_remaining > 0 ) : ?>
301 <span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="<?php echo esc_attr( sprintf( __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ) ); ?>"></span>
302 <?php endif; ?>
303 </span>
304 </td>
305
306 <!-- Subscription: product name + subscription ID -->
307 <td>
308 <a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-cell-title" style="font-weight:600;"><?php echo esc_html( $product_name ); ?></a>
309 <span class="wpsubs-cell-id">#<?php echo (int) $subscription->ID; ?></span>
310 </td>
311
312 <!-- Customer avatar + name + email -->
313 <td>
314 <div class="wpsubs-customer">
315 <div class="wpsubs-avatar" data-color="<?php echo (int) $color_slot; ?>">
316 <?php echo esc_html( $initials ); ?>
317 </div>
318 <div class="wpsubs-customer__info">
319 <?php if ( $customer_url ) : ?>
320 <a href="<?php echo esc_url( $customer_url ); ?>" class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></a>
321 <?php else : ?>
322 <span class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></span>
323 <?php endif; ?>
324 <?php if ( $customer_email ) : ?>
325 <span class="wpsubs-customer__sub"><?php echo esc_html( $customer_email ); ?></span>
326 <?php endif; ?>
327 </div>
328 </div>
329 </td>
330
331 <!-- Subscription amount + timing -->
332 <td style="white-space:nowrap;">
333 <?php if ( '' !== $price ) : ?>
334 <span class="wpsubs-cell-title" style="font-variant-numeric:tabular-nums;"><?php echo wp_kses_post( wc_price( $price ) ); ?></span>
335 <?php if ( $quantity > 1 ) : ?>
336 <span class="wpsubs-cell-id"><?php echo wp_kses_post( wc_price( (float) $subscription_data['price'] ) ); ?> &times; <?php echo (int) $quantity; ?></span>
337 <?php endif; ?>
338 <?php if ( $timing_label ) : ?>
339 <span class="wpsubs-cell-id">/ <?php echo esc_html( $timing_label ); ?></span>
340 <?php endif; ?>
341 <?php else : ?>
342 <span class="wpsubs-cell--muted">&#8212;</span>
343 <?php endif; ?>
344 </td>
345
346 <!-- Start date -->
347 <td class="wpsubs-col--nowrap">
348 <?php if ( $start_date ) : ?>
349 <span class="wpsubs-cell-title"><?php echo esc_html( wp_date( 'n/j/Y', $start_date ) ); ?></span>
350 <span class="wpsubs-cell-id"><?php echo esc_html( wp_date( 'g:i a', $start_date ) ); ?></span>
351 <?php else : ?>
352 <span class="wpsubs-cell--muted">&#8212;</span>
353 <?php endif; ?>
354 </td>
355
356 <!-- Renewal date -->
357 <td class="wpsubs-col--nowrap">
358 <?php if ( $renewal_date ) : ?>
359 <span class="wpsubs-cell-title"><?php echo esc_html( wp_date( 'n/j/Y', $renewal_date ) ); ?></span>
360 <span class="wpsubs-cell-id"><?php echo esc_html( wp_date( 'g:i a', $renewal_date ) ); ?></span>
361 <?php else : ?>
362 <span class="wpsubs-cell--muted">&#8212;</span>
363 <?php endif; ?>
364 </td>
365
366 <!-- Actions -->
367 <td class="wpsubs-cell--actions">
368 <div class="wpsubs-row-actions">
369 <button type="button" class="wpsubs-row-actions__trigger" aria-label="<?php esc_attr_e( 'Row actions', 'subscription' ); ?>">···</button>
370 <div class="wpsubs-dropdown">
371 <a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'View / Edit', 'subscription' ); ?></a>
372 <div class="wpsubs-dropdown__divider"></div>
373 <?php if ( ! $is_trash ) : ?>
374 <a href="<?php echo esc_url( $trash_url ); ?>" class="wpsubs-dropdown__item wpsubs-dropdown__item--danger" onclick="return confirm('<?php esc_attr_e( 'Move this subscription to trash?', 'subscription' ); ?>')"><?php esc_html_e( 'Trash', 'subscription' ); ?></a>
375 <?php else : ?>
376 <a href="<?php echo esc_url( $restore_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'Restore', 'subscription' ); ?></a>
377 <a href="<?php echo esc_url( $delete_url ); ?>" class="wpsubs-dropdown__item wpsubs-dropdown__item--danger" onclick="return confirm('<?php esc_attr_e( 'Delete permanently? This cannot be undone.', 'subscription' ); ?>')"><?php esc_html_e( 'Delete Permanently', 'subscription' ); ?></a>
378 <?php endif; ?>
379 </div>
380 </div>
381 </td>
382 </tr>
383 <?php endforeach; ?>
384
385 <?php else : ?>
386 <tr>
387 <td colspan="8">
388 <div class="wpsubs-empty">
389 <div class="wpsubs-empty__icon">📋</div>
390 <div class="wpsubs-empty__title"><?php esc_html_e( 'No subscriptions found', 'subscription' ); ?></div>
391 <div class="wpsubs-empty__desc"><?php esc_html_e( 'Subscriptions will appear here once customers complete checkout.', 'subscription' ); ?></div>
392 </div>
393 </td>
394 </tr>
395 <?php endif; ?>
396
397 </tbody>
398 </table>
399
400 <!-- Pagination inside table card -->
401 <?php
402 $start_item = $total > 0 ? ( ( $paged - 1 ) * $per_page ) + 1 : 0;
403 $end_item = min( $paged * $per_page, $total );
404 $base_url = remove_query_arg( 'paged' );
405 $total_pages = max( 1, $max_num_pages );
406
407 // Build page range: first, last, current ± 1 neighbours, ellipsis for gaps.
408 $nearby = array();
409 for ( $i = 1; $i <= $total_pages; $i++ ) {
410 if ( $i === 1 || $i === $total_pages || abs( $i - $paged ) <= 1 ) {
411 $nearby[] = $i;
412 }
413 }
414 $page_range = array();
415 $prev_page = null;
416 foreach ( $nearby as $p ) {
417 if ( null !== $prev_page ) {
418 $gap = $p - $prev_page;
419 if ( $gap === 2 ) {
420 $page_range[] = $prev_page + 1; // single hidden page — show it directly
421 } elseif ( $gap > 2 ) {
422 $page_range[] = null; // ellipsis
423 }
424 }
425 $page_range[] = $p;
426 $prev_page = $p;
427 }
428 ?>
429 <div class="wpsubs-pagination">
430 <span class="wpsubs-pagination__info">
431 <?php
432 echo esc_html(
433 sprintf(
434 /* translators: 1: first item number, 2: last item number, 3: total count */
435 __( 'Showing %1$s–%2$s of %3$s subscriptions', 'subscription' ),
436 number_format_i18n( $start_item ),
437 number_format_i18n( $end_item ),
438 number_format_i18n( $total )
439 )
440 );
441 ?>
442 </span>
443 <div class="wpsubs-pagination__nav">
444
445 <?php // Previous button ?>
446 <?php if ( $paged > 1 ) : ?>
447 <a href="
448 <?php
449 echo esc_url(
450 add_query_arg(
451 array(
452 'paged' => $paged - 1,
453 'per_page' => $per_page,
454 ),
455 $base_url
456 )
457 );
458 ?>
459 " class="wpsubs-pagination__btn" aria-label="<?php esc_attr_e( 'Previous page', 'subscription' ); ?>">&#8249;</a>
460 <?php else : ?>
461 <span class="wpsubs-pagination__btn wpsubs-pagination__btn--disabled" aria-hidden="true">&#8249;</span>
462 <?php endif; ?>
463
464 <?php // Numbered pages + ellipsis ?>
465 <?php foreach ( $page_range as $p ) : ?>
466 <?php if ( null === $p ) : ?>
467 <span class="wpsubs-pagination__ellipsis" aria-hidden="true"></span>
468 <?php elseif ( $p === $paged ) : ?>
469 <span class="wpsubs-pagination__btn wpsubs-pagination__btn--active" aria-current="page"><?php echo (int) $p; ?></span>
470 <?php else : ?>
471 <a href="
472 <?php
473 echo esc_url(
474 add_query_arg(
475 array(
476 'paged' => $p,
477 'per_page' => $per_page,
478 ),
479 $base_url
480 )
481 );
482 ?>
483 " class="wpsubs-pagination__btn"><?php echo (int) $p; ?></a>
484 <?php endif; ?>
485 <?php endforeach; ?>
486
487 <?php // Next button ?>
488 <?php if ( $paged < $total_pages ) : ?>
489 <a href="
490 <?php
491 echo esc_url(
492 add_query_arg(
493 array(
494 'paged' => $paged + 1,
495 'per_page' => $per_page,
496 ),
497 $base_url
498 )
499 );
500 ?>
501 " class="wpsubs-pagination__btn" aria-label="<?php esc_attr_e( 'Next page', 'subscription' ); ?>">&#8250;</a>
502 <?php else : ?>
503 <span class="wpsubs-pagination__btn wpsubs-pagination__btn--disabled" aria-hidden="true">&#8250;</span>
504 <?php endif; ?>
505
506 </div>
507 </div>
508
509 </div><!-- /.wpsubs-table-card -->
510
511 </form>
512
513 </div>
514
515 <script>
516 ( function () {
517 var selectAll = document.getElementById( 'cb-select-all' );
518 var form = document.getElementById( 'subscriptions-form' );
519
520 function rowChecks() {
521 return form ? Array.from( form.querySelectorAll( '.wpsubs-row-check' ) ) : [];
522 }
523
524 function syncSelectAll() {
525 if ( ! selectAll ) return;
526 var checks = rowChecks();
527 var checked = checks.filter( function ( cb ) { return cb.checked; } );
528 checks.forEach( function ( cb ) {
529 var row = cb.closest( 'tr' );
530 if ( row ) row.classList.toggle( 'wpsubs-row--selected', cb.checked );
531 } );
532 selectAll.indeterminate = checked.length > 0 && checked.length < checks.length;
533 selectAll.checked = checks.length > 0 && checked.length === checks.length;
534 }
535
536 if ( selectAll ) {
537 selectAll.addEventListener( 'change', function () {
538 rowChecks().forEach( function ( cb ) { cb.checked = selectAll.checked; } );
539 syncSelectAll();
540 } );
541 }
542
543 if ( form ) {
544 form.addEventListener( 'change', function ( e ) {
545 if ( e.target.classList.contains( 'wpsubs-row-check' ) ) syncSelectAll();
546 } );
547 }
548
549 // ── Advanced-select event handlers ──────────────────────────
550 var bulkSubmit = document.getElementById( 'wpsubs-bulk-action-submit' );
551 document.addEventListener( 'wpsubs:select', function ( e ) {
552 var id = e.target && e.target.id;
553
554 // Bulk action: submit via hidden button
555 if ( id === 'wpsubs-bulk-action-select' && bulkSubmit ) {
556 bulkSubmit.click();
557 return;
558 }
559
560 // Per-page: auto-submit as a filter action
561 if ( id === 'wpsubs-per-page-select' && form ) {
562 var fi = document.createElement( 'input' );
563 fi.type = 'hidden';
564 fi.name = 'filter_action';
565 fi.value = 'filter';
566 form.appendChild( fi );
567 form.submit();
568 }
569 } );
570
571 // ── Row action dropdowns ─────────────────────────────────────
572 function closeAllRowMenus() {
573 document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) {
574 el.classList.remove( 'wpsubs-row-actions--open' );
575 var d = el.querySelector( '.wpsubs-dropdown' );
576 if ( d ) d.classList.remove( 'wpsubs-dropdown--open' );
577 } );
578 }
579
580 document.addEventListener( 'click', function ( e ) {
581 var trigger = e.target.closest( '.wpsubs-row-actions__trigger' );
582
583 document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) {
584 if ( el !== ( trigger && trigger.closest( '.wpsubs-row-actions' ) ) ) {
585 el.classList.remove( 'wpsubs-row-actions--open' );
586 var d = el.querySelector( '.wpsubs-dropdown' );
587 if ( d ) d.classList.remove( 'wpsubs-dropdown--open' );
588 }
589 } );
590
591 if ( ! trigger ) return;
592
593 e.stopPropagation();
594 var wrapper = trigger.closest( '.wpsubs-row-actions' );
595 var dropdown = wrapper && wrapper.querySelector( '.wpsubs-dropdown' );
596 if ( ! wrapper || ! dropdown ) return;
597
598 var isOpen = wrapper.classList.contains( 'wpsubs-row-actions--open' );
599 wrapper.classList.toggle( 'wpsubs-row-actions--open', ! isOpen );
600 dropdown.classList.toggle( 'wpsubs-dropdown--open', ! isOpen );
601 } );
602
603 document.addEventListener( 'keydown', function ( e ) {
604 if ( e.key === 'Escape' ) closeAllRowMenus();
605 } );
606
607 syncSelectAll();
608 }() );
609 </script>
610