From 63c2e727d50c21e5a13bffe1382973c46011eac9 Mon Sep 17 00:00:00 2001 From: Timo Giese Date: Mon, 11 May 2026 21:16:11 +0200 Subject: [PATCH 1/2] feat: new dashboard widget and linting --- .php-cs-fixer.cache | 2 +- admin/class-spotmap-admin.php | 122 +++++++++++++++++++++ admin/css/dashboard-widget.css | 55 ++++++++++ admin/js/dashboard-widget.js | 58 ++++++++++ includes/class-spotmap-database.php | 58 ++++++++++ includes/class-spotmap-migrator.php | 2 +- includes/class-spotmap.php | 2 + src/map-engine/LodManager.ts | 16 ++- src/spotmap-admin/admin.css | 4 + src/spotmap-admin/components/FeedModal.jsx | 14 ++- src/spotmap-admin/tabs/FeedsTab.jsx | 30 +++-- src/spotmap/edit.jsx | 6 +- 12 files changed, 350 insertions(+), 19 deletions(-) create mode 100644 admin/css/dashboard-widget.css create mode 100644 admin/js/dashboard-widget.js diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache index afe6bd3..5cb76ad 100644 --- a/.php-cs-fixer.cache +++ b/.php-cs-fixer.cache @@ -1 +1 @@ -{"php":"8.3.30","version":"3.94.2:v3.94.2#7787ceff91365ba7d623ec410b8f429cdebb4f63","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":false,"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"admin\/class-spotmap-admin.php":"4565c034fb39b95f3192270a93c9acab","admin\/partials\/spotmap-admin-display.php":"b646e0023a13c761613bb9298e9801a2","examples\/import-sample-data.php":"2be03b55210115fe82c70c1c4aa1e271","includes\/class-spotmap-activator.php":"b375f6c4cc0a71ea4dc3d3b4c2b94d43","includes\/class-spotmap-api-crawler.php":"df5019ad78bb3a857ef94d16e108d877","includes\/class-spotmap-database.php":"c173ecb4c74ca49cd50d55a861b6ada7","includes\/class-spotmap-deactivator.php":"9105e8b4369a08981a16a36ca648a431","includes\/class-spotmap-ingest.php":"b7160ec9538ac0b75ddec6cd2d868008","includes\/class-spotmap-loader.php":"d62ae2526d1343c326116504177da04c","includes\/class-spotmap-migrator.php":"358dc8411db7becfc6ce9f1353c2a593","includes\/class-spotmap-options.php":"9d6323a70616448d3ac4afdb87b1a4b7","includes\/class-spotmap-providers.php":"6d64a7d7c8110bb3f76c3e71528e2a03","includes\/class-spotmap-rest-api.php":"5a4b4bf99811cec6e92f55947a3da5fe","includes\/class-spotmap.php":"c96502a4ce19aad91c1761465245231d","spotmap.php":"f39b4b0c1d3edeaa04befcd3a20c4d60","test-vrm-api.php":"d4b59c67c2d80c490454c9b092b49848","tests\/bootstrap.php":"c8d4cdb73dfb42e7190df12a3e40e4fc","tests\/SpotmapActivatorTest.php":"1a67385b90b88edf2a084830f8fcae36","tests\/SpotmapAdminTest.php":"c5a547fbbf382f6eaa75fbfde0261d60","tests\/SpotmapDatabaseTest.php":"17cc93b4be6402da2fc02df63b2abd42","tests\/SpotmapDeactivatorTest.php":"889b0c32929509533f0976b6ca1bc3fa","tests\/SpotmapMigratorTest.php":"ced54df7ec4582e63fb3f0c7971c48ae","tests\/SpotmapOptionsTest.php":"925a5d927ed3f59224163a849e1a6aa9","tests\/SpotmapOsmAndIngestTest.php":"0359e10e2d8e23c28b5e10a5a0175ed5","tests\/SpotmapPublicTest.php":"78bc0b5534edf29559afd499cf925ac9","tests\/SpotmapRenderingTest.php":"10e72081ef8859c0bde648ca216b84eb","tests\/SpotmapRestApiTest.php":"bb2d524ee74ab8d372d9046b1da98533","tests\/SpotmapTeltonikaIngestTest.php":"ad808248b3562f5ffb4dbb960916953f","tests\/wp-tests-config.php":"1d93c9a04f8c512d3c0724ef1b8c6e64","uninstall.php":"dde7eab93f2c1a83addcc81abd894452"}} \ No newline at end of file +{"php":"8.4.20","version":"3.95.1:v3.95.1#a9727678fbd12997f1d9de8f4a37824ed9df1065","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":false,"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"includes\/class-spotmap-loader.php":"d62ae2526d1343c326116504177da04c","includes\/class-spotmap-migrator.php":"6a77fbb2c913eba721dfa47a28026d16","includes\/class-spotmap-options.php":"66a6727f012eb904519c4ebca8725cf4","includes\/class-spotmap-providers.php":"0b5cd0089486cb35e296ec3735bddc5c","includes\/class-spotmap-rest-api.php":"2a0e46d747bdfd633e1c6c29f2973160","includes\/class-spotmap.php":"30204f8a0008661f8410ac5b593c7a9a","spotmap.php":"2f0fd7c249dad680bc7ac04f1ab1f57c","tests\/bootstrap.php":"2fb4b10aaa28e42866ea20371a081015","tests\/SpotmapActivatorTest.php":"1a67385b90b88edf2a084830f8fcae36","tests\/SpotmapAdminTest.php":"c5a547fbbf382f6eaa75fbfde0261d60","uninstall.php":"dde7eab93f2c1a83addcc81abd894452","admin\/class-spotmap-admin.php":"17dffc144a3bb79e11c0bdeef52d1aa5","admin\/partials\/spotmap-admin-display.php":"477ff3c74d381dd1975731545618bfe4","config\/maps.php":"d93ad37ac69a69b3dbb58490fbdd6f34","examples\/import-sample-data.php":"2be03b55210115fe82c70c1c4aa1e271","examples\/test-vrm-api.php":"d4b59c67c2d80c490454c9b092b49848","includes\/class-spotmap-activator.php":"b375f6c4cc0a71ea4dc3d3b4c2b94d43","includes\/class-spotmap-api-crawler.php":"bcc4de6dc7984493ebd2353121953f66","includes\/class-spotmap-database.php":"6418115ead7774727d50fa0841769ebd","includes\/class-spotmap-deactivator.php":"097bb5b6bffa990d0af8793f634b1f6c","includes\/class-spotmap-ingest.php":"2e00b666dcb774946a20eb26cae4c37d","tests\/SpotmapDatabaseTest.php":"44ee7775edcefe6fc04b825b47e343dc","tests\/SpotmapDeactivatorTest.php":"889b0c32929509533f0976b6ca1bc3fa","tests\/SpotmapMigratorTest.php":"ced54df7ec4582e63fb3f0c7971c48ae","tests\/SpotmapOptionsTest.php":"925a5d927ed3f59224163a849e1a6aa9","tests\/SpotmapOsmAndIngestTest.php":"0359e10e2d8e23c28b5e10a5a0175ed5","tests\/SpotmapPublicTest.php":"78bc0b5534edf29559afd499cf925ac9","tests\/SpotmapRenderingTest.php":"059257d815508b1c87acf84febc40aa8","tests\/SpotmapRestApiTest.php":"bb2d524ee74ab8d372d9046b1da98533","tests\/SpotmapTeltonikaIngestTest.php":"ad808248b3562f5ffb4dbb960916953f","tests\/wp-tests-config.php":"1d93c9a04f8c512d3c0724ef1b8c6e64"}} \ No newline at end of file diff --git a/admin/class-spotmap-admin.php b/admin/class-spotmap-admin.php index 68e5859..57f52c9 100644 --- a/admin/class-spotmap-admin.php +++ b/admin/class-spotmap-admin.php @@ -121,6 +121,128 @@ public function display_options_page() echo '
'; } + public function add_dashboard_widget() + { + wp_add_dashboard_widget( + 'spotmap_dashboard_stats', + __('Spotmap Statistics'), + [ $this, 'render_dashboard_widget' ] + ); + } + + public function render_dashboard_widget() + { + $stats = $this->db->get_dashboard_stats(); + $configured_feeds = Spotmap_Options::get_feeds(); + $has_photos = ! empty(array_filter($configured_feeds, fn ($f) => ($f['type'] ?? '') === 'media')); + $has_posts = ! empty(array_filter($configured_feeds, fn ($f) => ($f['type'] ?? '') === 'posts')); + $since = time() - 24 * HOUR_IN_SECONDS; + $active_feeds = array_values(array_filter( + $stats['feeds'], + fn ($f) => ! empty($f['last_point']) && $f['last_point'] >= $since + )); + $settings_url = admin_url('options-general.php?page=spotmap#feeds'); + $photo_count = $stats['type_counts']['MEDIA'] ?? 0; + $post_count = $stats['type_counts']['POST'] ?? 0; + ?> +
+
+
+ + +
+
+ + +
+
+ + + + + + + — + + + +
+
+ + + + + + + — + + + +
+
+ +

+ + + + + + + + + + + + + + + + + + +
+ +

+ + +
+ rest_url('spotmap/v1/'), + 'nonce' => wp_create_nonce('wp_rest'), + ]); + } + public function add_link_plugin_overview($links) { $mylinks = [ diff --git a/admin/css/dashboard-widget.css b/admin/css/dashboard-widget.css new file mode 100644 index 0000000..20f883e --- /dev/null +++ b/admin/css/dashboard-widget.css @@ -0,0 +1,55 @@ +.spotmap-dashboard-widget { + font-size: 13px; +} + +.spotmap-stats-summary { + display: flex; + gap: 24px; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid #dcdcde; +} + +.spotmap-stat { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.spotmap-stat-value { + font-size: 22px; + font-weight: 600; + line-height: 1; + color: #1d2327; +} + +.spotmap-stat-label { + font-size: 12px; + color: #646970; + margin-top: 3px; +} + +.spotmap-feeds-table { + margin: 0; +} + +.spotmap-feeds-table th { + font-weight: 600; +} + +.spotmap-section-heading { + font-weight: 600; + margin: 12px 0 6px; + color: #1d2327; +} + +.spotmap-no-activity { + color: #646970; + font-style: italic; + margin: 4px 0 8px; +} + +.spotmap-stat-disabled { + color: #c3c4c7; +} + diff --git a/admin/js/dashboard-widget.js b/admin/js/dashboard-widget.js new file mode 100644 index 0000000..4ba8ff9 --- /dev/null +++ b/admin/js/dashboard-widget.js @@ -0,0 +1,58 @@ +/* global moment, spotmapDashboard, location */ +( function () { + document.addEventListener( 'DOMContentLoaded', function () { + if ( typeof moment !== 'undefined' ) { + document + .querySelectorAll( '[data-spotmap-ts]' ) + .forEach( function ( el ) { + const ts = parseInt( el.dataset.spotmapTs, 10 ); + if ( ! ts ) { + return; + } + const m = moment.unix( ts ); + if ( moment().diff( m, 'hours' ) < 24 ) { + el.textContent = m.fromNow(); + } + } ); + } + + document + .querySelectorAll( '.spotmap-enable-btn' ) + .forEach( function ( btn ) { + btn.addEventListener( 'click', function () { + const type = btn.dataset.spotmapEnable; + const name = btn.dataset.spotmapName; + btn.disabled = true; + btn.textContent = 'Enabling…'; + + fetch( spotmapDashboard.restUrl + 'feeds', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': spotmapDashboard.nonce, + }, + body: JSON.stringify( { type, name } ), + } ) + .then( function ( res ) { + if ( ! res.ok ) { + return res.json().then( function ( d ) { + throw new Error( + d.message || 'Request failed' + ); + } ); + } + location.reload(); + } ) + .catch( function ( err ) { + btn.disabled = false; + btn.textContent = 'enable'; + const errEl = document.createElement( 'span' ); + errEl.style.color = '#d63638'; + errEl.style.marginLeft = '4px'; + errEl.textContent = err.message; + btn.parentNode.appendChild( errEl ); + } ); + } ); + } ); + } ); +} )(); diff --git a/includes/class-spotmap-database.php b/includes/class-spotmap-database.php index 78a1d35..b11872d 100644 --- a/includes/class-spotmap-database.php +++ b/includes/class-spotmap-database.php @@ -120,6 +120,64 @@ public function get_all_feednames() return array_values(array_unique(array_merge($from_options, $from_db))); } + /** + * Returns summary stats for the admin dashboard widget. + * + * @return array{total_points: int, today_points: int, feeds: list} + */ + public function get_dashboard_stats(): array + { + global $wpdb; + $table = $wpdb->prefix . 'spotmap_points'; + + $total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"); + + $tz = wp_timezone(); + $today_start = (new DateTime('today midnight', $tz))->getTimestamp(); + $today_end = (new DateTime('tomorrow midnight', $tz))->getTimestamp() - 1; + $today = (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(*) FROM {$table} WHERE time BETWEEN %d AND %d", + $today_start, + $today_end + ) + ); + + $rows = $wpdb->get_results( + "SELECT feed_name, COUNT(*) AS cnt, MAX(time) AS last_point + FROM {$table} + WHERE feed_name IS NOT NULL + GROUP BY feed_name + ORDER BY last_point DESC", + ARRAY_A + ); + + $feeds = []; + foreach ($rows as $row) { + $feeds[] = [ + 'name' => $row['feed_name'], + 'count' => (int) $row['cnt'], + 'last_point' => (int) $row['last_point'], + ]; + } + + $type_rows = $wpdb->get_results( + "SELECT type, COUNT(*) AS cnt FROM {$table} WHERE type IN ('POST', 'MEDIA') GROUP BY type", + ARRAY_A + ); + $type_counts = []; + foreach ($type_rows as $row) { + $type_counts[ $row['type'] ] = (int) $row['cnt']; + } + + return [ + 'total_points' => $total, + 'today_points' => $today, + 'feeds' => $feeds, + 'type_counts' => $type_counts, + ]; + } + /** * Returns a map of feed_name => point count for all feeds in the DB, * optionally restricted to a date range. diff --git a/includes/class-spotmap-migrator.php b/includes/class-spotmap-migrator.php index 79bea25..41071c7 100644 --- a/includes/class-spotmap-migrator.php +++ b/includes/class-spotmap-migrator.php @@ -88,7 +88,7 @@ private static function migrate_to_1_0_0(): void // Clear stale full-size URLs from MEDIA rows — the URL is now resolved at // query time from the attachment ID stored in `model`. // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $wpdb->query( "UPDATE `{$table}` SET `message` = NULL WHERE `type` = 'MEDIA'" ); + $wpdb->query("UPDATE `{$table}` SET `message` = NULL WHERE `type` = 'MEDIA'"); // Normalize EXTREME-TRACK and UNLIMITED-TRACK → TRACK (0.11.x rows). // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared diff --git a/includes/class-spotmap.php b/includes/class-spotmap.php index 836605b..3645de1 100644 --- a/includes/class-spotmap.php +++ b/includes/class-spotmap.php @@ -97,6 +97,8 @@ private function define_admin_hooks() // guard themselves and no-op when no media feed is configured. $this->loader->add_action('add_attachment', $this->admin, 'add_images_to_map'); $this->loader->add_action('delete_attachment', $this->admin, 'delete_images_from_map'); + $this->loader->add_action('wp_dashboard_setup', $this->admin, 'add_dashboard_widget'); + $this->loader->add_action('admin_enqueue_scripts', $this->admin, 'enqueue_dashboard_scripts'); } /** diff --git a/src/map-engine/LodManager.ts b/src/map-engine/LodManager.ts index cb44d84..bdd5a4a 100644 --- a/src/map-engine/LodManager.ts +++ b/src/map-engine/LodManager.ts @@ -1,6 +1,10 @@ import type { SpotmapLayers, SpotPoint } from './types'; import type { MarkerManager } from './MarkerManager'; -import { LOD_DEBOUNCE_MS, LINE_SMOOTH_FACTOR, LOD_MIN_TRACK_POINTS } from './constants'; +import { + LOD_DEBOUNCE_MS, + LINE_SMOOTH_FACTOR, + LOD_MIN_TRACK_POINTS, +} from './constants'; import { debug as debugLog } from './utils'; interface FeedLodState { @@ -60,7 +64,10 @@ export class LodManager { let totalTrack = 0; outer: for ( const feed of Object.values( this.layers.feeds ) ) { for ( const p of feed.points ) { - if ( p.type === 'TRACK' && ++totalTrack >= LOD_MIN_TRACK_POINTS ) { + if ( + p.type === 'TRACK' && + ++totalTrack >= LOD_MIN_TRACK_POINTS + ) { break outer; } } @@ -127,7 +134,10 @@ export class LodManager { } const runStart = i; - while ( i < feed.points.length && feed.points[ i ].type === 'TRACK' ) { + while ( + i < feed.points.length && + feed.points[ i ].type === 'TRACK' + ) { i++; } const run = feed.points.slice( runStart, i ); diff --git a/src/spotmap-admin/admin.css b/src/spotmap-admin/admin.css index 7948c13..0d9fffc 100644 --- a/src/spotmap-admin/admin.css +++ b/src/spotmap-admin/admin.css @@ -1 +1,5 @@ @import url('../../node_modules/@wordpress/dataviews/build-style/style.css'); + +.spotmap-feeds-dataviews table tbody tr:nth-child(even) td { + background-color: #f6f7f7; +} diff --git a/src/spotmap-admin/components/FeedModal.jsx b/src/spotmap-admin/components/FeedModal.jsx index 559b9b8..7260b98 100644 --- a/src/spotmap-admin/components/FeedModal.jsx +++ b/src/spotmap-admin/components/FeedModal.jsx @@ -438,11 +438,17 @@ export default function FeedModal( { __next40pxDefaultSize /> { looksLikeUrl && ( - - Enter only the last part of the URL — e.g.{' '} + + Enter only the last part of the URL — e.g.{ ' ' } - { fieldValue.split( '/' ).filter( Boolean ).pop() ?? 'Username' } - {' '} + { fieldValue + .split( '/' ) + .filter( Boolean ) + .pop() ?? 'Username' } + { ' ' } — not the full address. ) } diff --git a/src/spotmap-admin/tabs/FeedsTab.jsx b/src/spotmap-admin/tabs/FeedsTab.jsx index aac6661..d065aa6 100644 --- a/src/spotmap-admin/tabs/FeedsTab.jsx +++ b/src/spotmap-admin/tabs/FeedsTab.jsx @@ -26,7 +26,7 @@ const DEFAULT_VIEW = { perPage: 25, page: 1, sort: { field: 'name', direction: 'asc' }, - fields: [ 'name', 'typeLabel', 'pointCount', 'status' ], + fields: [ 'name', 'typeLabel', 'pointCount', 'status', 'lastPoint' ], filters: [], search: '', layout: {}, @@ -362,13 +362,27 @@ export default function FeedsTab( { elements: STATUS_ELEMENTS, filterBy: { operators: [ 'is' ] }, render: ( { item } ) => { - if ( item.status === 'paused' ) { - return 'Paused'; - } - if ( item.status === 'orphaned' ) { - return 'DB only'; - } - return 'Active'; + const statusMap = { + paused: { bg: '#dba617', label: 'Paused' }, + orphaned: { bg: '#8c8f94', label: 'DB only' }, + active: { bg: '#00a32a', label: 'Active' }, + }; + const s = statusMap[ item.status ] ?? statusMap.active; + return ( + + { s.label } + + ); }, }, { diff --git a/src/spotmap/edit.jsx b/src/spotmap/edit.jsx index 9689f62..fd8f1f0 100644 --- a/src/spotmap/edit.jsx +++ b/src/spotmap/edit.jsx @@ -266,7 +266,8 @@ export default function Edit( { attributes, setAttributes } ) { // If the DB already holds more than 150 000 points, default to no feeds // so the editor doesn't immediately try to render a huge dataset. - const enabledNames = totalPoints > EDITOR_POINT_LIMIT ? [] : feedNames; + const enabledNames = + totalPoints > EDITOR_POINT_LIMIT ? [] : feedNames; const defaultFeedObjects = enabledNames.map( ( name, i ) => ( { name, ...DEFAULT_FEED_STYLE, @@ -854,7 +855,8 @@ export default function Edit( { attributes, setAttributes } ) { }, } ) } > - { selectedPoints !== null && selectedPoints > EDITOR_POINT_LIMIT ? ( + { selectedPoints !== null && + selectedPoints > EDITOR_POINT_LIMIT ? (
Date: Mon, 11 May 2026 21:49:06 +0200 Subject: [PATCH 2/2] fix issues --- .php-cs-fixer.cache | 2 +- admin/class-spotmap-admin.php | 52 ++++++++++++----------------- admin/js/dashboard-widget.js | 3 +- includes/class-spotmap-database.php | 1 - src/spotmap-admin/tabs/FeedsTab.jsx | 41 +++++++++++------------ 5 files changed, 44 insertions(+), 55 deletions(-) diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache index 5cb76ad..69c820d 100644 --- a/.php-cs-fixer.cache +++ b/.php-cs-fixer.cache @@ -1 +1 @@ -{"php":"8.4.20","version":"3.95.1:v3.95.1#a9727678fbd12997f1d9de8f4a37824ed9df1065","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":false,"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"includes\/class-spotmap-loader.php":"d62ae2526d1343c326116504177da04c","includes\/class-spotmap-migrator.php":"6a77fbb2c913eba721dfa47a28026d16","includes\/class-spotmap-options.php":"66a6727f012eb904519c4ebca8725cf4","includes\/class-spotmap-providers.php":"0b5cd0089486cb35e296ec3735bddc5c","includes\/class-spotmap-rest-api.php":"2a0e46d747bdfd633e1c6c29f2973160","includes\/class-spotmap.php":"30204f8a0008661f8410ac5b593c7a9a","spotmap.php":"2f0fd7c249dad680bc7ac04f1ab1f57c","tests\/bootstrap.php":"2fb4b10aaa28e42866ea20371a081015","tests\/SpotmapActivatorTest.php":"1a67385b90b88edf2a084830f8fcae36","tests\/SpotmapAdminTest.php":"c5a547fbbf382f6eaa75fbfde0261d60","uninstall.php":"dde7eab93f2c1a83addcc81abd894452","admin\/class-spotmap-admin.php":"17dffc144a3bb79e11c0bdeef52d1aa5","admin\/partials\/spotmap-admin-display.php":"477ff3c74d381dd1975731545618bfe4","config\/maps.php":"d93ad37ac69a69b3dbb58490fbdd6f34","examples\/import-sample-data.php":"2be03b55210115fe82c70c1c4aa1e271","examples\/test-vrm-api.php":"d4b59c67c2d80c490454c9b092b49848","includes\/class-spotmap-activator.php":"b375f6c4cc0a71ea4dc3d3b4c2b94d43","includes\/class-spotmap-api-crawler.php":"bcc4de6dc7984493ebd2353121953f66","includes\/class-spotmap-database.php":"6418115ead7774727d50fa0841769ebd","includes\/class-spotmap-deactivator.php":"097bb5b6bffa990d0af8793f634b1f6c","includes\/class-spotmap-ingest.php":"2e00b666dcb774946a20eb26cae4c37d","tests\/SpotmapDatabaseTest.php":"44ee7775edcefe6fc04b825b47e343dc","tests\/SpotmapDeactivatorTest.php":"889b0c32929509533f0976b6ca1bc3fa","tests\/SpotmapMigratorTest.php":"ced54df7ec4582e63fb3f0c7971c48ae","tests\/SpotmapOptionsTest.php":"925a5d927ed3f59224163a849e1a6aa9","tests\/SpotmapOsmAndIngestTest.php":"0359e10e2d8e23c28b5e10a5a0175ed5","tests\/SpotmapPublicTest.php":"78bc0b5534edf29559afd499cf925ac9","tests\/SpotmapRenderingTest.php":"059257d815508b1c87acf84febc40aa8","tests\/SpotmapRestApiTest.php":"bb2d524ee74ab8d372d9046b1da98533","tests\/SpotmapTeltonikaIngestTest.php":"ad808248b3562f5ffb4dbb960916953f","tests\/wp-tests-config.php":"1d93c9a04f8c512d3c0724ef1b8c6e64"}} \ No newline at end of file +{"php":"8.4.20","version":"3.95.1:v3.95.1#a9727678fbd12997f1d9de8f4a37824ed9df1065","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":false,"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"includes\/class-spotmap-loader.php":"d62ae2526d1343c326116504177da04c","includes\/class-spotmap-migrator.php":"6a77fbb2c913eba721dfa47a28026d16","includes\/class-spotmap-options.php":"66a6727f012eb904519c4ebca8725cf4","includes\/class-spotmap-providers.php":"0b5cd0089486cb35e296ec3735bddc5c","includes\/class-spotmap-rest-api.php":"2a0e46d747bdfd633e1c6c29f2973160","includes\/class-spotmap.php":"30204f8a0008661f8410ac5b593c7a9a","spotmap.php":"2f0fd7c249dad680bc7ac04f1ab1f57c","tests\/bootstrap.php":"2fb4b10aaa28e42866ea20371a081015","tests\/SpotmapActivatorTest.php":"1a67385b90b88edf2a084830f8fcae36","tests\/SpotmapAdminTest.php":"c5a547fbbf382f6eaa75fbfde0261d60","uninstall.php":"dde7eab93f2c1a83addcc81abd894452","admin\/class-spotmap-admin.php":"819f7701ad258cedd76a7882e2d2f56a","admin\/partials\/spotmap-admin-display.php":"477ff3c74d381dd1975731545618bfe4","config\/maps.php":"d93ad37ac69a69b3dbb58490fbdd6f34","examples\/import-sample-data.php":"2be03b55210115fe82c70c1c4aa1e271","examples\/test-vrm-api.php":"d4b59c67c2d80c490454c9b092b49848","includes\/class-spotmap-activator.php":"b375f6c4cc0a71ea4dc3d3b4c2b94d43","includes\/class-spotmap-api-crawler.php":"bcc4de6dc7984493ebd2353121953f66","includes\/class-spotmap-database.php":"9bc66cdb07c245a52788ad87e3670883","includes\/class-spotmap-deactivator.php":"097bb5b6bffa990d0af8793f634b1f6c","includes\/class-spotmap-ingest.php":"2e00b666dcb774946a20eb26cae4c37d","tests\/SpotmapDatabaseTest.php":"44ee7775edcefe6fc04b825b47e343dc","tests\/SpotmapDeactivatorTest.php":"889b0c32929509533f0976b6ca1bc3fa","tests\/SpotmapMigratorTest.php":"ced54df7ec4582e63fb3f0c7971c48ae","tests\/SpotmapOptionsTest.php":"925a5d927ed3f59224163a849e1a6aa9","tests\/SpotmapOsmAndIngestTest.php":"0359e10e2d8e23c28b5e10a5a0175ed5","tests\/SpotmapPublicTest.php":"78bc0b5534edf29559afd499cf925ac9","tests\/SpotmapRenderingTest.php":"059257d815508b1c87acf84febc40aa8","tests\/SpotmapRestApiTest.php":"bb2d524ee74ab8d372d9046b1da98533","tests\/SpotmapTeltonikaIngestTest.php":"ad808248b3562f5ffb4dbb960916953f","tests\/wp-tests-config.php":"1d93c9a04f8c512d3c0724ef1b8c6e64"}} \ No newline at end of file diff --git a/admin/class-spotmap-admin.php b/admin/class-spotmap-admin.php index 57f52c9..5ada7c1 100644 --- a/admin/class-spotmap-admin.php +++ b/admin/class-spotmap-admin.php @@ -141,7 +141,6 @@ public function render_dashboard_widget() $stats['feeds'], fn ($f) => ! empty($f['last_point']) && $f['last_point'] >= $since )); - $settings_url = admin_url('options-general.php?page=spotmap#feeds'); $photo_count = $stats['type_counts']['MEDIA'] ?? 0; $post_count = $stats['type_counts']['POST'] ?? 0; ?> @@ -156,36 +155,10 @@ public function render_dashboard_widget()
- - - - - - - — - - - + render_optional_stat($has_photos, $photo_count, __('Geotagged photos'), __('Photos'), 'media'); ?>
- - - - - - - — - - - + render_optional_stat($has_posts, $post_count, __('Geotagged posts'), __('Blog posts'), 'posts', __('Blog Posts')); ?>
@@ -228,13 +201,13 @@ public function enqueue_dashboard_scripts($hook) 'spotmap-dashboard-widget', plugin_dir_url(__DIR__) . 'admin/css/dashboard-widget.css', [], - '1.0.0' + SPOTMAP_VERSION ); wp_enqueue_script( 'spotmap-dashboard-widget', plugin_dir_url(__DIR__) . 'admin/js/dashboard-widget.js', [ 'moment' ], - '1.0.0', + SPOTMAP_VERSION, true ); wp_localize_script('spotmap-dashboard-widget', 'spotmapDashboard', [ @@ -243,6 +216,23 @@ public function enqueue_dashboard_scripts($hook) ]); } + private function render_optional_stat(bool $enabled, int $count, string $active_label, string $inactive_label, string $enable_type, ?string $enable_name = null): void + { + if ($enabled) { + echo '' . number_format($count) . ''; + echo '' . esc_html($active_label) . ''; + return; + } + $name = $enable_name ?? $inactive_label; + echo ''; + echo '' . esc_html($inactive_label) . ' — ' + . ''; + } + public function add_link_plugin_overview($links) { $mylinks = [ diff --git a/admin/js/dashboard-widget.js b/admin/js/dashboard-widget.js index 4ba8ff9..88b79c3 100644 --- a/admin/js/dashboard-widget.js +++ b/admin/js/dashboard-widget.js @@ -22,6 +22,7 @@ btn.addEventListener( 'click', function () { const type = btn.dataset.spotmapEnable; const name = btn.dataset.spotmapName; + const originalText = btn.textContent; btn.disabled = true; btn.textContent = 'Enabling…'; @@ -45,7 +46,7 @@ } ) .catch( function ( err ) { btn.disabled = false; - btn.textContent = 'enable'; + btn.textContent = originalText; const errEl = document.createElement( 'span' ); errEl.style.color = '#d63638'; errEl.style.marginLeft = '4px'; diff --git a/includes/class-spotmap-database.php b/includes/class-spotmap-database.php index b11872d..6fb85cd 100644 --- a/includes/class-spotmap-database.php +++ b/includes/class-spotmap-database.php @@ -160,7 +160,6 @@ public function get_dashboard_stats(): array 'last_point' => (int) $row['last_point'], ]; } - $type_rows = $wpdb->get_results( "SELECT type, COUNT(*) AS cnt FROM {$table} WHERE type IN ('POST', 'MEDIA') GROUP BY type", ARRAY_A diff --git a/src/spotmap-admin/tabs/FeedsTab.jsx b/src/spotmap-admin/tabs/FeedsTab.jsx index d065aa6..bea5e88 100644 --- a/src/spotmap-admin/tabs/FeedsTab.jsx +++ b/src/spotmap-admin/tabs/FeedsTab.jsx @@ -32,11 +32,22 @@ const DEFAULT_VIEW = { layout: {}, }; -const STATUS_ELEMENTS = [ - { value: 'active', label: 'Active' }, - { value: 'paused', label: 'Paused' }, - { value: 'orphaned', label: 'DB only' }, -]; +const STATUS_CONFIG = { + active: { label: 'Active', bg: '#00a32a' }, + paused: { label: 'Paused', bg: '#dba617' }, + orphaned: { label: 'DB only', bg: '#8c8f94' }, +}; +const STATUS_ELEMENTS = Object.entries( STATUS_CONFIG ).map( + ( [ value, { label } ] ) => ( { value, label } ) +); +const STATUS_BADGE_STYLE = { + color: '#fff', + padding: '2px 7px', + borderRadius: '3px', + fontSize: '11px', + fontWeight: 600, + whiteSpace: 'nowrap', +}; const formatTimestamp = ( ts ) => ts @@ -362,25 +373,13 @@ export default function FeedsTab( { elements: STATUS_ELEMENTS, filterBy: { operators: [ 'is' ] }, render: ( { item } ) => { - const statusMap = { - paused: { bg: '#dba617', label: 'Paused' }, - orphaned: { bg: '#8c8f94', label: 'DB only' }, - active: { bg: '#00a32a', label: 'Active' }, - }; - const s = statusMap[ item.status ] ?? statusMap.active; + const { label, bg } = + STATUS_CONFIG[ item.status ] ?? STATUS_CONFIG.active; return ( - { s.label } + { label } ); },