Pavel SkvortsovPavel Skvortsov
← Back
04

WP Plugin - Quick Price Manager

A custom WordPress admin plugin for bulk price management of ASIC miners. Inline editing, CSV import/export, badge management, and stale price alerts - all on one page.

WordPressPHPACFMySQL

"Update prices for 100 devices without opening a single product page."

4
hashrate variants per device (H1-H4)
AJAX
save without page reload
CSV
bulk import + export with hashrate columns
7 days
stale price threshold with color alerts
How it works
Admin page render
Inline price input
CSV import → fill fields
AJAX POST → PHP handler
update_post_meta + ACF
_price_updated_at timestamp
Project Structure
inc/price-manager.phpadmin page render, AJAX handlers, ACF hook, stale price notice
ms2026_save_prices_ajax()validates nonce, updates h1_price-h4_price via post_meta + ACF, stamps _price_updated_at
ms2026_update_badge_ajax()validates badge value against ACF choices whitelist before saving
ms2026_acf_track_price_update()ACF save_post hook - compares old vs new prices, only updates timestamp on real change
ms2026_outdated_prices_notice()WP_Query finds products where _price_updated_at is older than 7 days - admin notice with link
Notable detail

CSV import runs entirely client-side - the file is parsed in the browser, matching rows to product blocks by model name, and highlighting changed fields in yellow before any data is sent to the server.

Code
// CSV import - client-side, no server round-trip
  importBtn.addEventListener('click', function () {
    var reader = new FileReader();
    reader.onload = function (e) {
      var rows = e.target.result.split(/\r?\n/);
      // Auto-detect separator: ; or ,
      var sep = rows[0].indexOf(';') !== -1 ? ';' : ',';
      // New format: model_name;h1_th;h2_th;h3_th;h4_th;h1_price
      // Old format: model_name;h1_price;h2_price
      var priceStart = rows[0].toLowerCase().includes('h1_th') ? 5 : 1;
  
      rows.slice(1).forEach(function (row) {
        var cols = row.split(sep).map(c => c.trim().replace(/^"|"$/g, ''));
        var modelName = cols[0];
        document.querySelectorAll('.ms2026-price-block').forEach(function (block) {
          if (block.dataset.model.toLowerCase() === modelName.toLowerCase()) {
            block.querySelectorAll('.ms2026-price-input').forEach(function (inp, j) {
              var newPrice = cols[priceStart + j] || '';
              if (newPrice) {
                inp.value = newPrice;
                inp.style.backgroundColor = '#fcf3cf'; // highlight changed
              }
            });
          }
        });
      });
    };
    reader.readAsText(fileInput.files[0], 'UTF-8');
  });
  
  // AJAX save - only sends fields that were actually changed
  saveBtn.addEventListener('click', function () {
    var data = {};
    document.querySelectorAll('.ms2026-price-input').forEach(function (inp) {
      if (!inp.value.trim()) return; // skip unchanged
      var match = inp.name.match(/prices\[(\d+)\]\[(h\d+_price)\]/);
      if (match) {
        if (!data[match[1]]) data[match[1]] = {};
        data[match[1]][match[2]] = inp.value.trim();
      }
    });
    // POST to wp_ajax_ms2026_save_prices
  });
Screenshots
admin
Features
  • Single admin page lists all miner products with all hashrate variants inline
  • Inline price editing - type new price, click Save - AJAX saves without reload
  • CSV import: upload file - fields auto-fill highlighted in yellow - review - save
  • CSV export: downloads current prices with hashrate columns (model_name; h1_th-h4_th; h1_price-h4_price)
  • Badge management per product - dropdown updates via separate AJAX call instantly
  • Filter by brand and algorithm - URL-based, no JS required
  • Stale price indicator: green < 3 days - yellow 3-7 days - red > 7 days
  • Admin notice on product list: "N products with outdated prices - Update prices"
  • Prices saved via update_post_meta + update_field (ACF) simultaneously for full compatibility
  • ACF hook tracks price changes - _price_updated_at only updates when value actually changes