--- title: Locations Table React Conversion date: 2026-07-23T19:31:21-04:00 status: completed project: store-locator-plus tags: - locations - react - mui-datagrid - wordpress-rest - persistence - playwright source_type: ai-summary directory: locations slug: locations-table-react-conversion --- # Topic Summary The Store Locator Plus WordPress Locations manager was converted from the legacy table to React DataGridPro with module-compatible REST adapters, server-side filtering/sorting/pagination, bulk actions, and durable per-user grid state. Five feedback rounds were implemented through core version `2607.23.04`, including quick-filter stability, pinned columns, toolbar controls, Reset, responsive height, and live WordPress verification. # AI Handoff ## project_scope - Core repo: `/Users/lancecleveland/PhpstormProjects/WordPress/wp-content/plugins/store-locator-plus`. - Product scope: full Locations-manager parity across the core plugin and all SLP/MySLP modules in `wp-content/plugins`; compatibility is limited to this project’s modules. - Source requirements came from `https://internal.storelocatorplus.com/locations-table-react-conversion/`, read through `/wp-json/wp/v2/posts?slug=locations-table-react-conversion`. - Revision policy: work on `develop`, use the `start-revision` workflow, increment both `store-locator-plus.php` and `readme.txt`, commit the revision marker, then commit implementation. The human owner decides when a revision is officially shipped. ## repo_map - `src/LocationsTable/LocationsTable.tsx`: React mount/composition. - `src/LocationsTable/LocationsDataGrid.tsx`: DataGridPro columns, toolbar, selection, pinning, row actions, zebra classes, dialogs, viewport-aware sizing. - `src/LocationsTable/LocationsTableContext.tsx`: controlled grid models, REST loading, bulk actions, local/user-meta persistence, Reset. - `src/LocationsTable/LocationsGridState.ts`: grid-state normalization/sanitization/equality/defaults. - `src/LocationsTable/LocationsGridState.test.ts`: Jest coverage for model normalization, equality, selection safety, custom-setting detection, and pinning. - `src/components/locations/LocationsTableHeader.tsx`: bulk-action area and custom-settings Reset alert. - `src/components/locations/LocationsBulkActions.tsx`: module-compatible bulk controls. - `include/module/rest/SLP_REST_Locations_Manager.php`: authenticated Locations GET/bulk/state routes and request sanitization. - `include/module/admin/SLP_Admin_Locations.php`: legacy hook adapter, column/row schema, server SQL filtering, sorting, pagination, and module integration. - `build/LocationsTable/LocationsTable.js` and `.asset.php`: committed production output; rebuild after TypeScript changes. ## architecture - `LocationsTableProvider` owns controlled pagination, sort, filter/quick-filter, selection, column order/visibility, and pinned-column models. - Locations data uses `GET store-locator-plus/v2/admin/locations`; DataGridPro runs pagination, sorting, column filters, and quick filters in server mode. - `SLP_REST_Locations_Manager` bootstraps the legacy admin screen and existing hooks so add-ons continue extending columns, rendered values, row actions, and bulk actions. - Bulk mutations use `POST store-locator-plus/v2/admin/locations/bulk`; the adapter validates action names against module-filtered bulk actions. - Grid preferences use `GET/PUT store-locator-plus/v2/admin/locations/state`, localStorage key `csl-slplus-locations-grid-state:${origin}:${userId}`, and user meta key `SLPLUS_PREFIX . '-locations-grid-state'`. - Persisted state version remains `1` for backward compatibility; current shape contains `columnOrder`, `columnVisibilityModel`, `filterModel`, `pinnedColumns`, `sortModel`, `updatedAt`, and `version`. - Reset writes defaults to user meta first, then localStorage/provider state; it also resets page/page-size and row selection. ## key_components - `LocationsDataGrid`: - Uses `showToolbar` and a custom `LocationsGridToolbar`. - Current HEAD order after automated commit `ae501a60`: `GridToolbarFilterButton`, `GridToolbarDensitySelector`, then `GridToolbarQuickFilter`. - Uses `pinnedColumns`/`onPinnedColumnsChange`, `checkboxSelection`, server models, and `_actions`. - Row classes add `slp-grid-row-even`/`slp-grid-row-odd` while preserving module classes such as `featured`, `invalid`, and `private`. - Outermost layout height is `window.innerHeight - elementTop - 24`, minimum `320`. - `LocationsGridState`: - `normalizeLocationsGridViewState()` removes unavailable fields and duplicate left/right pins. - `gridModelsEqual()` canonicalizes filter defaults before comparison to reject semantically identical MUI callbacks. - `pinnedColumnsEqual()` prevents controlled pin-model churn. - `hasCustomLocationsGridSettings()` includes order, visibility, column/quick filters, and pins. - `LocationsTableHeader`: - Alert appears at the upper right of the bulk-action row. - Text: `Custom table settings are in place. Some columns may be hidden or reordered.` - Alert action button: `Reset`; request failures retain the alert with an error message. ## important_nuances - Turn 5 infinite-refresh root cause was not only React equality: quick search included an extended field named `rank`, a MySQL reserved word. SQL failed, WordPress emitted database-error HTML with HTTP 200, the frontend treated missing `columns` as `[]`, then alternated between full-field and empty-field requests. - Fixes for that loop: - `SLP_Admin_Locations::get_rest_grid_filter_clause()` wraps already-validated field identifiers in backticks. - `LocationsTableContext` rejects responses unless both `response.data.columns` and `response.data.rows` are arrays; established column state is not erased. - Provider-level semantic filter equality rejects equivalent model callbacks. - Reproduction improved from 133 filtered GETs in six seconds to one filtered GET; local `charleston` search returned valid JSON and seven rows with no browser/database errors. - Quick-filter query parameters: `quick_filter_fields`, `quick_filter_logic`, and `quick_filter_values`. Visible, filterable fields are sent; hidden columns are omitted unless `quickFilterExcludeHiddenColumns` is false. - Quick-filter SQL semantics: each value searches all validated fields with `OR`; multiple quick values use the MUI quick-filter logic operator; column filters and quick filters combine with `AND`. - WordPress admin CSS previously shrank the invisible MUI checkbox input to a 16px target at the cell corner. `LocationsDataGrid` explicitly stretches `.MuiDataGrid-checkboxInput > input[type='checkbox']` to the full control. - Latitude/longitude default column placement is immediately after Distance. The removed legacy `LocationsFilter` UI should not be reintroduced. - Build emits existing webpack size warnings for large bundles; these were non-blocking. - Local PHP is available inside container `wp-slp`; core plugin is bind-mounted at `/var/www/html/wp-content/plugins/store-locator-plus`. - Local WordPress admin URL is `http://localhost/wp-admin/admin.php?page=slp_manage_locations`. Credentials are documented as environment exports in `documentation/DEVNOTES.MD`; do not print them. ## decisions_and_rationale - Preserve legacy PHP hooks behind REST instead of duplicating module logic in React; this retains compatibility with SLP add-ons and MySLP modules. - Persist an intentionally limited, sanitized DataGrid state instead of raw `apiRef.exportState()`; this avoids storing transient/internal MUI state and tolerates columns added or removed by modules. - Keep server filtering/sorting/pagination because full location datasets and extended fields cannot safely rely on client-only rows. - Pass visible quick-filter fields explicitly so server behavior follows MUI’s hidden-column semantics. - Treat malformed HTTP-200 payloads as service failures rather than empty tables; this prevents schema oscillation and request loops. - Commit generated LocationsTable assets because the plugin loads `build/LocationsTable/LocationsTable.js` in WordPress. ## playwright_test_guidance - Login: - Navigate to `http://localhost/wp-login.php`. - Read `WPADMIN_USER` and `WPADMIN_PASSWORD` from `documentation/DEVNOTES.MD` without logging values. - Open `http://localhost/wp-admin/admin.php?page=slp_manage_locations`. - Stable selectors: - Grid: `.MuiDataGrid-root`. - Rows: `.MuiDataGrid-row[data-id]`. - Headers: `.MuiDataGrid-columnHeader[data-field="sl_city"]` and other `data-field` values. - Quick input: `.MuiDataGrid-root input[placeholder*="Search"]`. - Toolbar buttons by aria-label: `Show filters`, `Density`, `Search`. - Custom-state notice: role `alert` containing `Custom table settings`. - Reset: role `button`, exact name `Reset`. - Checkbox input: `.MuiDataGrid-checkboxInput > input[type="checkbox"]`. - Quick-filter regression: - Count GET responses whose pathname ends `/wp-json/store-locator-plus/v2/admin/locations`. - After typing one debounced term, assert one request with non-empty `quick_filter_values`; do not count the normal initial schema/state loads. - Assert response content type is JSON and body contains array `columns` and `rows`. - Include extended/reserved fields in the site configuration; `rank` must not generate SQL errors. - Verify a non-name search such as a Zip value returns rows whose Zip matches. - Pinning: - Hover `.MuiDataGrid-columnHeader[data-field="sl_city"]`, click `City column menu`, then menu item `Pin to left`. - Wait for state `PUT`; assert localStorage and state GET return `pinnedColumns.left: ["sl_city"]`. - Reload and assert the City header remains pinned. - Click Reset; assert localStorage/user meta pins are empty and the custom-state alert disappears. - Layout/UI: - Assert toolbar order against current HEAD: `Show filters`, `Density`, `Search`. - Assert even/odd row classes alternate and computed backgrounds differ. - Assert outer grid height is within 2px of `max(320, floor(innerHeight - top - 24))`. - Assert Reset alert is right of bulk actions and shares the header row at desktop width. - Verify checkbox click/check/uncheck at its visual center. - Existing validation commands: - `npx tsc --noEmit --pretty false --types react,react-dom,node,jest` - `npm run test:unit -- --runInBand` - `npm run build` - `docker exec wp-slp php -l /var/www/html/wp-content/plugins/store-locator-plus/include/module/rest/SLP_REST_Locations_Manager.php` - `docker exec wp-slp php -l /var/www/html/wp-content/plugins/store-locator-plus/include/module/admin/SLP_Admin_Locations.php` ## state_of_work - Completed through Store Locator Plus version `2607.23.04`. - Major commits: - `d5b06eb0` Replace locations manager table with React DataGrid Pro. - `71fdc55a` Address locations DataGrid conversion feedback. - `f8bfca25` Complete Turn 2 locations grid feedback. - `05e55fae` / `638dd8ac` Start `2607.23.02` and resolve Turn 3. - `a28d2e6c` / `c7cbe904` Start `2607.23.03` and resolve Turn 4. - `48c52eb4` / `0a7f1dd4` Start `2607.23.04` and resolve Turn 5. - `ae501a60` automated follow-up: move Quick Search after Filters and Density and rebuild. - Current repository is clean; `develop`, `staging`, `origin/develop`, and `origin/staging` all point to `ae501a60`. Current checked-out branch at summary time is `staging`. - Turn 5 implementation was validated with TypeScript, 6 Jest tests, production build, PHP lint, and live browser checks. The later `ae501a60` change is a small toolbar-order/build update. ## open_items - No unresolved Turn 1–5 article tasks were known at summary time. - No permanent Playwright spec was added; validation used inline Playwright scripts against the local WordPress runtime. - Official staging/prerelease/production “shipped” status remains a human-owner decision. ## next_actions - For new feedback, fetch the latest article section through the WordPress REST endpoint before editing. - Checkout and pull `develop` before starting another revision; do not continue product work from `staging`. - Use `start-revision` to increment the core plugin version and stable tag without requesting redundant confirmation. - Preserve the current toolbar order from `ae501a60` unless new feedback explicitly changes it. - Add a permanent Playwright regression spec for quick-filter request count, JSON response shape, pin persistence, Reset, and toolbar order when the project test location is chosen. ## Human Summary The Locations manager React conversion and five feedback rounds are complete through version `2607.23.04`. The implementation retains add-on compatibility through the legacy admin hooks while React controls the DataGrid and REST handles server data and per-user state. The most important resolved defect was an infinite quick-filter loop caused by unquoted extended SQL fields and malformed HTTP-200 responses. Quick filtering is now stable, pinned columns persist and reset correctly, and both `develop` and `staging` contain the completed work plus the final toolbar-position update.