diff --git a/README.md b/README.md index d6b82b7..b1886ba 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ Displays a searchable directory of offices using the Bridge Data Output API. - **Search Functionality:** Users can search offices by name, phone, or email. - **Customizable Display:** Adjust the number of columns and rows in the block editor. - **Cache Management:** Clear the cache manually if needed. +- **Enhanced Directory Display:** Offices are displayed as a grid of cards for better visual presentation. +- **Live Search with Debounce:** Users can search offices with instant feedback, and the search input is debounced to optimize performance. +- **Infinite Scroll:** As users scroll down, more offices are loaded automatically without needing to click pagination links. ## Data Storage diff --git a/bridge-directory/assets/css/bridge-directory.css b/bridge-directory/assets/css/bridge-directory.css new file mode 100644 index 0000000..8761c83 --- /dev/null +++ b/bridge-directory/assets/css/bridge-directory.css @@ -0,0 +1,37 @@ +.bridge-directory-grid { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +.bridge-directory-search { + margin-bottom: 20px; +} + +.bridge-directory-search input { + width: 100%; + padding: 10px; + font-size: 16px; +} + +.bridge-directory-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; +} + +.bridge-directory-card { + border: 1px solid #ccc; + padding: 15px; + background-color: #fff; +} + +.bridge-directory-card h3 { + margin-top: 0; +} + +.bridge-directory-loader { + text-align: center; + padding: 20px; + font-size: 18px; +} diff --git a/bridge-directory/assets/js/bridge-directory.js b/bridge-directory/assets/js/bridge-directory.js new file mode 100644 index 0000000..31221a3 --- /dev/null +++ b/bridge-directory/assets/js/bridge-directory.js @@ -0,0 +1,86 @@ +(function ($) { + let currentPage = 1; + let isLoading = false; + let noMoreResults = false; + let lastQuery = ''; + let debounceTimeout; + + $(document).ready(function () { + const $searchInput = $('#bridge-directory-search-input'); + const $cardsContainer = $('#bridge-directory-cards'); + const $loader = $('#bridge-directory-loader'); + + function loadResults(reset = false) { + if (isLoading || noMoreResults) return; + isLoading = true; + $loader.show(); + + $.ajax({ + url: bridgeDirectory.ajax_url, + type: 'POST', + data: { + action: 'bridge_directory_load_offices', + nonce: bridgeDirectory.nonce, + page: currentPage, + query: lastQuery, + }, + success: function (response) { + if (reset) { + $cardsContainer.empty(); + currentPage = 1; + noMoreResults = false; + } + if (response.success) { + const offices = response.data.offices; + if (offices.length === 0) { + noMoreResults = true; + } else { + appendResultsToGrid(offices); + currentPage++; + } + } + }, + complete: function () { + isLoading = false; + $loader.hide(); + }, + }); + } + + function appendResultsToGrid(offices) { + offices.forEach(function (office) { + const card = ` +
+

${office.OfficeName}

+

Phone: ${office.OfficePhone || ''}

+

Email: ${office.OfficeEmail || ''}

+

Address: ${office.OfficeAddress1 || ''} ${office.OfficeAddress2 || ''}, ${office.OfficeCity || ''}, ${office.OfficeStateOrProvince || ''} ${office.OfficePostalCode || ''}

+ ${office.SocialMediaWebsiteUrlOrId ? `

Website: ${office.SocialMediaWebsiteUrlOrId}

` : ''} +
+ `; + $cardsContainer.append(card); + }); + } + + function debounceSearch() { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(function () { + lastQuery = $searchInput.val(); + currentPage = 1; + noMoreResults = false; + loadResults(true); + }, 300); + } + + $searchInput.on('input', debounceSearch); + + $(window).on('scroll', function () { + if ($(window).scrollTop() + $(window).height() >= $(document).height() - 100) { + loadResults(); + } + }); + + // Initial load + loadResults(); + }); +})(jQuery); diff --git a/bridge-directory/bridge-directory.php b/bridge-directory/bridge-directory.php index a05b6c1..b834c4a 100644 --- a/bridge-directory/bridge-directory.php +++ b/bridge-directory/bridge-directory.php @@ -39,6 +39,9 @@ $data_sync = new Data_Sync( $db_handler ); $data_sync->schedule_incremental_sync(); +// Initialize AJAX Handler +$ajax_handler = new AJAX_Handler( $search_handler ); + // Activation and Deactivation Hooks register_activation_hook( __FILE__, [ 'BridgeDirectory\DB_Handler', 'activate' ] ); register_deactivation_hook( __FILE__, [ 'BridgeDirectory\DB_Handler', 'deactivate' ] ); @@ -56,4 +59,3 @@ function bridge_directory_custom_cron_schedule( $schedules ) { ]; return $schedules; } - \ No newline at end of file diff --git a/bridge-directory/includes/class-ajax-handler.php b/bridge-directory/includes/class-ajax-handler.php new file mode 100644 index 0000000..0d82bec --- /dev/null +++ b/bridge-directory/includes/class-ajax-handler.php @@ -0,0 +1,27 @@ +search_handler = $search_handler; + + add_action( 'wp_ajax_bridge_directory_load_offices', [ $this, 'load_offices' ] ); + add_action( 'wp_ajax_nopriv_bridge_directory_load_offices', [ $this, 'load_offices' ] ); + } + + public function load_offices() { + check_ajax_referer( 'bridge_directory_nonce', 'nonce' ); + + $page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1; + $query = isset( $_POST['query'] ) ? sanitize_text_field( $_POST['query'] ) : ''; + $limit = 20; // Number of results per page + + $offices = $this->search_handler->search_offices( $query, $page, $limit ); + + wp_send_json_success( [ 'offices' => $offices ] ); + } +} diff --git a/bridge-directory/includes/class-block-register.php b/bridge-directory/includes/class-block-register.php index c626121..cb12d36 100644 --- a/bridge-directory/includes/class-block-register.php +++ b/bridge-directory/includes/class-block-register.php @@ -12,6 +12,7 @@ public function __construct( $search_handler ) { public function register() { add_action( 'init', [ $this, 'register_blocks' ] ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } public function register_blocks() { @@ -30,52 +31,50 @@ public function register_blocks() { 'type' => 'number', 'default' => 3, ], - 'rows' => [ - 'type' => 'number', - 'default' => 5, - ], ], 'render_callback' => [ $this, 'render_block' ], ] ); } - public function render_block( $attributes ) { - $query = isset( $_GET['bridge_search'] ) ? sanitize_text_field( $_GET['bridge_search'] ) : ''; - $offices = $this->search_handler->search_offices( $query ); + public function enqueue_scripts() { + if ( has_block( 'bridge-directory/office-list' ) ) { + wp_enqueue_script( + 'bridge-directory-frontend', + plugins_url( 'assets/js/bridge-directory.js', __DIR__ ), + [ 'jquery' ], + '1.0.0', + true + ); - ob_start(); - ?> + wp_enqueue_style( + 'bridge-directory-style', + plugins_url( 'assets/css/bridge-directory.css', __DIR__ ), + [], + '1.0.0' + ); -
- - -
+ wp_localize_script( 'bridge-directory-frontend', 'bridgeDirectory', [ + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'columns' => get_option( 'bridge_directory_columns', 3 ), + 'nonce' => wp_create_nonce( 'bridge_directory_nonce' ), + ] ); + } + } -
- = $attributes['rows'] * $attributes['columns'] ) { - break; - } - ?> -
-

-

Phone:

-

Email:

-

Address:

- -

Website:

- -
- + public function render_block( $attributes ) { + ob_start(); + ?> +
+ +
+ +
+
- db_handler = $db_handler; } - public function search_offices( $query ) { + public function search_offices( $query = '', $page = 1, $limit = 20 ) { global $wpdb; + $offset = ( $page - 1 ) * $limit; $table_name = $this->db_handler->table_name; $sql = "SELECT * FROM {$table_name}"; + $where_clauses = []; if ( ! empty( $query ) ) { $like_query = '%' . $wpdb->esc_like( $query ) . '%'; - $sql .= $wpdb->prepare( - " WHERE OfficeName LIKE %s OR OfficePhone LIKE %s OR OfficeEmail LIKE %s", + $where_clauses[] = $wpdb->prepare( + "(OfficeName LIKE %s OR OfficePhone LIKE %s OR OfficeEmail LIKE %s)", $like_query, $like_query, $like_query ); } + if ( ! empty( $where_clauses ) ) { + $sql .= ' WHERE ' . implode( ' AND ', $where_clauses ); + } + + $sql .= $wpdb->prepare( ' LIMIT %d OFFSET %d', $limit, $offset ); + $results = $wpdb->get_results( $sql, ARRAY_A ); return $results; } diff --git a/bridge-directory/vendor/composer/installed.php b/bridge-directory/vendor/composer/installed.php index 99aa7db..7eb3e2a 100644 --- a/bridge-directory/vendor/composer/installed.php +++ b/bridge-directory/vendor/composer/installed.php @@ -1,20 +1,20 @@ array( - 'name' => '__root__', - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => null, - 'type' => 'library', + 'name' => 'justinh-rahb/bridge-directory', + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '48aaf176c17a698321aeff4ea84371fcbab1577b', + 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => true, ), 'versions' => array( - '__root__' => array( - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => null, - 'type' => 'library', + 'justinh-rahb/bridge-directory' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '48aaf176c17a698321aeff4ea84371fcbab1577b', + 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false,