2025-12-23 07:48:45 +01:00
/ * *
* Admin JavaScript for Informatiq Smart Pricing
* /
( function ( $ ) {
'use strict' ;
var InformatiqSP = {
/ * *
* Initialize
* /
init : function ( ) {
this . bindEvents ( ) ;
} ,
/ * *
* Bind event handlers
* /
bindEvents : function ( ) {
$ ( '#informatiq-sp-manual-sync' ) . on ( 'click' , this . handleManualSync ) ;
$ ( '#informatiq-sp-test-connection' ) . on ( 'click' , this . handleTestConnection ) ;
2026-01-21 08:52:57 +01:00
$ ( '#informatiq-sp-revoke-auth' ) . on ( 'click' , this . handleRevokeAuth ) ;
2026-01-23 18:22:17 +01:00
$ ( '#informatiq-sp-compare-products' ) . on ( 'click' , this . handleCompareProducts ) ;
2025-12-23 07:48:45 +01:00
} ,
/ * *
* Handle manual sync button click
* /
handleManualSync : function ( e ) {
e . preventDefault ( ) ;
var $button = $ ( this ) ;
var $status = $ ( '#informatiq-sp-sync-status' ) ;
// Confirm action
if ( ! confirm ( informatiqSP . strings . confirmSync || 'Are you sure you want to run a manual price sync? This may take several minutes.' ) ) {
return ;
}
// Disable button and show loading state
$button . prop ( 'disabled' , true ) . addClass ( 'informatiq-sp-loading' ) ;
// Show status message
$status
. removeClass ( 'notice-success notice-error' )
. addClass ( 'notice-info' )
. html ( '<p>' + ( informatiqSP . strings . syncInProgress || 'Sync in progress...' ) + '</p>' )
. show ( ) ;
// Make AJAX request
$ . ajax ( {
url : informatiqSP . ajaxUrl ,
type : 'POST' ,
data : {
action : 'informatiq_sp_manual_sync' ,
nonce : informatiqSP . nonce
} ,
success : function ( response ) {
if ( response . success ) {
$status
. removeClass ( 'notice-info notice-error' )
. addClass ( 'notice-success' )
. html ( '<p>' + response . data . message + '</p>' ) ;
// Reload page after 2 seconds to show updated logs
setTimeout ( function ( ) {
location . reload ( ) ;
} , 2000 ) ;
} else {
$status
. removeClass ( 'notice-info notice-success' )
. addClass ( 'notice-error' )
. html ( '<p>Error: ' + ( response . data . message || 'Unknown error' ) + '</p>' ) ;
}
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
$status
. removeClass ( 'notice-info notice-success' )
. addClass ( 'notice-error' )
. html ( '<p>Error: ' + errorThrown + '</p>' ) ;
} ,
complete : function ( ) {
$button . prop ( 'disabled' , false ) . removeClass ( 'informatiq-sp-loading' ) ;
}
} ) ;
} ,
/ * *
* Handle test connection button click
* /
handleTestConnection : function ( e ) {
e . preventDefault ( ) ;
var $button = $ ( this ) ;
var $status = $ ( '#informatiq-sp-sync-status' ) ;
// Disable button and show loading state
$button . prop ( 'disabled' , true ) . addClass ( 'informatiq-sp-loading' ) ;
// Show status message
$status
. removeClass ( 'notice-success notice-error' )
. addClass ( 'notice-info' )
. html ( '<p>' + ( informatiqSP . strings . testInProgress || 'Testing connection...' ) + '</p>' )
. show ( ) ;
// Make AJAX request
$ . ajax ( {
url : informatiqSP . ajaxUrl ,
type : 'POST' ,
data : {
action : 'informatiq_sp_test_connection' ,
nonce : informatiqSP . nonce
} ,
success : function ( response ) {
if ( response . success ) {
$status
. removeClass ( 'notice-info notice-error' )
. addClass ( 'notice-success' )
. html ( '<p>' + response . data . message + '</p>' ) ;
} else {
$status
. removeClass ( 'notice-info notice-success' )
. addClass ( 'notice-error' )
. html ( '<p>Error: ' + ( response . data . message || 'Unknown error' ) + '</p>' ) ;
}
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
$status
. removeClass ( 'notice-info notice-success' )
. addClass ( 'notice-error' )
. html ( '<p>Error: ' + errorThrown + '</p>' ) ;
} ,
complete : function ( ) {
$button . prop ( 'disabled' , false ) . removeClass ( 'informatiq-sp-loading' ) ;
// Hide status message after 5 seconds
setTimeout ( function ( ) {
$status . fadeOut ( ) ;
} , 5000 ) ;
}
} ) ;
2026-01-21 08:52:57 +01:00
} ,
/ * *
* Handle revoke authorization button click
* /
handleRevokeAuth : function ( e ) {
e . preventDefault ( ) ;
var $button = $ ( this ) ;
// Confirm action
if ( ! confirm ( informatiqSP . strings . revokeConfirm || 'Are you sure you want to revoke Google authorization?' ) ) {
return ;
}
// Disable button and show loading state
$button . prop ( 'disabled' , true ) . text ( informatiqSP . strings . revokeInProgress || 'Revoking...' ) ;
// Make AJAX request
$ . ajax ( {
url : informatiqSP . ajaxUrl ,
type : 'POST' ,
data : {
action : 'informatiq_sp_revoke_auth' ,
nonce : informatiqSP . nonce
} ,
success : function ( response ) {
if ( response . success ) {
// Reload page to show updated status
location . reload ( ) ;
} else {
alert ( 'Error: ' + ( response . data . message || 'Unknown error' ) ) ;
$button . prop ( 'disabled' , false ) . text ( 'Revoke Authorization' ) ;
}
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
alert ( 'Error: ' + errorThrown ) ;
$button . prop ( 'disabled' , false ) . text ( 'Revoke Authorization' ) ;
}
} ) ;
2026-01-22 19:16:57 +01:00
} ,
2026-01-23 18:22:17 +01:00
// Store comparison data for pagination and bulk updates.
comparisonData : null ,
currentPage : 1 ,
perPage : 50 ,
2026-01-22 19:16:57 +01:00
/ * *
* Handle compare products button click
* /
handleCompareProducts : function ( e ) {
e . preventDefault ( ) ;
2026-01-23 18:22:17 +01:00
var self = InformatiqSP ;
2026-01-22 19:16:57 +01:00
var $button = $ ( this ) ;
var $spinner = $button . next ( '.spinner' ) ;
var $results = $ ( '#informatiq-sp-comparison-results' ) ;
2026-01-22 19:24:05 +01:00
var $status = $ ( '#informatiq-sp-sync-status' ) ;
// Show loading status
$status
. removeClass ( 'notice-success notice-error' )
. addClass ( 'notice-info' )
2026-01-23 18:22:17 +01:00
. html ( '<p><strong>Loading price insights from Google...</strong> This may take a moment.</p>' )
2026-01-22 19:24:05 +01:00
. show ( ) ;
2026-01-22 19:16:57 +01:00
$button . prop ( 'disabled' , true ) ;
$spinner . addClass ( 'is-active' ) ;
$ . ajax ( {
url : informatiqSP . ajaxUrl ,
type : 'POST' ,
2026-01-23 18:22:17 +01:00
timeout : 180000 , // 3 minute timeout
2026-01-22 19:16:57 +01:00
data : {
action : 'informatiq_sp_compare_products' ,
nonce : informatiqSP . nonce
} ,
success : function ( response ) {
2026-01-23 18:22:17 +01:00
console . log ( 'Price insights response:' , response ) ;
2026-01-22 19:24:05 +01:00
$status . hide ( ) ;
2026-01-22 19:16:57 +01:00
if ( response . success ) {
2026-01-23 18:22:17 +01:00
self . comparisonData = response . data ;
self . currentPage = 1 ;
self . renderComparison ( ) ;
2026-01-22 19:16:57 +01:00
$results . show ( ) ;
} else {
2026-01-22 19:24:05 +01:00
$status
. removeClass ( 'notice-info notice-success' )
. addClass ( 'notice-error' )
. html ( '<p>Error: ' + ( response . data . message || 'Unknown error' ) + '</p>' )
. show ( ) ;
2026-01-22 19:16:57 +01:00
}
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
2026-01-23 18:22:17 +01:00
console . error ( 'AJAX error:' , textStatus , errorThrown ) ;
2026-01-22 19:24:05 +01:00
$status
. removeClass ( 'notice-info notice-success' )
. addClass ( 'notice-error' )
2026-01-23 18:22:17 +01:00
. html ( '<p>Error: ' + errorThrown + '</p>' )
2026-01-22 19:24:05 +01:00
. show ( ) ;
2026-01-22 19:16:57 +01:00
} ,
complete : function ( ) {
$button . prop ( 'disabled' , false ) ;
$spinner . removeClass ( 'is-active' ) ;
}
} ) ;
2026-01-23 18:22:17 +01:00
} ,
/ * *
* Render comparison table with pagination
* /
renderComparison : function ( ) {
var self = this ;
var data = this . comparisonData ;
var $tbody = $ ( '#informatiq-sp-comparison-tbody' ) ;
var $summary = $ ( '#informatiq-sp-comparison-summary' ) ;
var $pagination = $ ( '#informatiq-sp-pagination' ) ;
if ( ! data || ! data . products ) return ;
var products = data . products ;
var totalPages = Math . ceil ( products . length / this . perPage ) ;
var start = ( this . currentPage - 1 ) * this . perPage ;
var end = start + this . perPage ;
var pageProducts = products . slice ( start , end ) ;
// Stats
var stats = {
total : products . length ,
withInsight : 0 ,
canIncrease : 0 ,
shouldDecrease : 0 ,
totalPotentialGain : 0
} ;
products . forEach ( function ( p ) {
if ( p . has _insight ) {
stats . withInsight ++ ;
if ( p . potential _gain > 0 ) {
stats . canIncrease ++ ;
stats . totalPotentialGain += p . potential _gain ;
} else if ( p . potential _gain < 0 ) {
stats . shouldDecrease ++ ;
}
}
} ) ;
// Render summary
var summaryHtml = '<strong>Summary:</strong> ' + stats . withInsight + ' of ' + stats . total + ' products have Google price insights. ' ;
summaryHtml += '<span style="color:#00a32a;">' + stats . canIncrease + ' can increase price</span>, ' ;
summaryHtml += '<span style="color:#d63638;">' + stats . shouldDecrease + ' should decrease price</span>. ' ;
if ( stats . totalPotentialGain > 0 ) {
summaryHtml += '<br><strong style="color:#00a32a;">Total potential gain: +' + data . currency + stats . totalPotentialGain . toFixed ( 2 ) + ' per sale cycle</strong>' ;
}
$summary . html ( summaryHtml ) ;
// Render table rows
var html = '' ;
if ( pageProducts . length === 0 ) {
html = '<tr><td colspan="9">No products found.</td></tr>' ;
} else {
pageProducts . forEach ( function ( product ) {
var localPrice = product . local _price ? data . currency + parseFloat ( product . local _price ) . toFixed ( 2 ) : '-' ;
var suggestedPrice = product . suggested _price ? data . currency + parseFloat ( product . suggested _price ) . toFixed ( 2 ) : '-' ;
var priceLabel = product . price _type === 'sale' ? ' <small>(sale)</small>' : '' ;
2026-01-23 18:42:00 +01:00
// Percentage difference styling
var percentDiffHtml = '-' ;
var percentDiffStyle = '' ;
if ( product . percent _diff !== null ) {
var pct = parseFloat ( product . percent _diff ) ;
if ( pct > 0 ) {
percentDiffHtml = '+' + pct . toFixed ( 1 ) + '%' ;
percentDiffStyle = 'color: #00a32a; font-weight: bold;' ;
} else if ( pct < 0 ) {
percentDiffHtml = pct . toFixed ( 1 ) + '%' ;
percentDiffStyle = 'color: #d63638;' ;
2026-01-23 18:22:17 +01:00
} else {
2026-01-23 18:42:00 +01:00
percentDiffHtml = '0%' ;
2026-01-23 18:22:17 +01:00
}
}
// Predicted changes
var imprChange = product . predicted _impressions _change !== null
? ( product . predicted _impressions _change > 0 ? '+' : '' ) + product . predicted _impressions _change . toFixed ( 1 ) + '%'
: '-' ;
var clickChange = product . predicted _clicks _change !== null
? ( product . predicted _clicks _change > 0 ? '+' : '' ) + product . predicted _clicks _change . toFixed ( 1 ) + '%'
: '-' ;
var convChange = product . predicted _conversions _change !== null
? ( product . predicted _conversions _change > 0 ? '+' : '' ) + product . predicted _conversions _change . toFixed ( 1 ) + '%'
: '-' ;
var imprStyle = product . predicted _impressions _change > 0 ? 'color:#00a32a;' : ( product . predicted _impressions _change < 0 ? 'color:#d63638;' : '' ) ;
var clickStyle = product . predicted _clicks _change > 0 ? 'color:#00a32a;' : ( product . predicted _clicks _change < 0 ? 'color:#d63638;' : '' ) ;
var convStyle = product . predicted _conversions _change > 0 ? 'color:#00a32a;' : ( product . predicted _conversions _change < 0 ? 'color:#d63638;' : '' ) ;
// Checkbox and action button
var checkbox = product . has _insight && product . should _update
? '<input type="checkbox" class="informatiq-sp-select-product" data-product-id="' + product . id + '" data-new-price="' + product . suggested _price + '">'
: '' ;
var actionBtn = product . has _insight && product . should _update
? '<button type="button" class="button button-small informatiq-sp-update-single" data-product-id="' + product . id + '" data-new-price="' + product . suggested _price + '">Update</button>'
: '-' ;
html += '<tr>' ;
html += '<td>' + checkbox + '</td>' ;
html += '<td><a href="post.php?post=' + product . id + '&action=edit" title="SKU: ' + product . sku + '">' + self . truncate ( product . name , 40 ) + '</a></td>' ;
html += '<td>' + localPrice + priceLabel + '</td>' ;
html += '<td>' + suggestedPrice + '</td>' ;
2026-01-23 18:42:00 +01:00
html += '<td style="' + percentDiffStyle + '">' + percentDiffHtml + '</td>' ;
2026-01-23 18:22:17 +01:00
html += '<td style="' + imprStyle + '">' + imprChange + '</td>' ;
html += '<td style="' + clickStyle + '">' + clickChange + '</td>' ;
html += '<td style="' + convStyle + '">' + convChange + '</td>' ;
html += '<td>' + actionBtn + '</td>' ;
html += '</tr>' ;
} ) ;
}
$tbody . html ( html ) ;
// Render pagination
if ( totalPages > 1 ) {
var paginationHtml = '<span>Page ' + this . currentPage + ' of ' + totalPages + '</span> ' ;
if ( this . currentPage > 1 ) {
paginationHtml += '<button type="button" class="button button-small informatiq-sp-page" data-page="' + ( this . currentPage - 1 ) + '">« Prev</button> ' ;
}
if ( this . currentPage < totalPages ) {
paginationHtml += '<button type="button" class="button button-small informatiq-sp-page" data-page="' + ( this . currentPage + 1 ) + '">Next »</button>' ;
}
$pagination . html ( paginationHtml ) . show ( ) ;
} else {
$pagination . hide ( ) ;
}
// Bind events for this page
self . bindComparisonEvents ( ) ;
} ,
/ * *
* Truncate text
* /
truncate : function ( str , len ) {
if ( ! str ) return '' ;
return str . length > len ? str . substring ( 0 , len ) + '...' : str ;
} ,
/ * *
* Bind comparison table events
* /
bindComparisonEvents : function ( ) {
var self = this ;
// Single update buttons
$ ( '.informatiq-sp-update-single' ) . off ( 'click' ) . on ( 'click' , function ( ) {
var $btn = $ ( this ) ;
var productId = $btn . data ( 'product-id' ) ;
var newPrice = $btn . data ( 'new-price' ) ;
$btn . prop ( 'disabled' , true ) . text ( 'Updating...' ) ;
$ . ajax ( {
url : informatiqSP . ajaxUrl ,
type : 'POST' ,
data : {
action : 'informatiq_sp_update_price' ,
nonce : informatiqSP . nonce ,
product _id : productId ,
new _price : newPrice
} ,
success : function ( response ) {
if ( response . success ) {
$btn . text ( 'Done!' ) . addClass ( 'button-primary' ) ;
$btn . closest ( 'tr' ) . css ( 'background-color' , '#d4edda' ) ;
} else {
alert ( 'Error: ' + response . data . message ) ;
$btn . prop ( 'disabled' , false ) . text ( 'Update' ) ;
}
} ,
error : function ( ) {
alert ( 'Request failed' ) ;
$btn . prop ( 'disabled' , false ) . text ( 'Update' ) ;
}
} ) ;
} ) ;
// Pagination
$ ( '.informatiq-sp-page' ) . off ( 'click' ) . on ( 'click' , function ( ) {
self . currentPage = $ ( this ) . data ( 'page' ) ;
self . renderComparison ( ) ;
$ ( 'html, body' ) . animate ( { scrollTop : $ ( '#informatiq-sp-comparison-results' ) . offset ( ) . top - 50 } , 200 ) ;
} ) ;
// Select all checkboxes
$ ( '#informatiq-sp-select-all, #informatiq-sp-select-all-header' ) . off ( 'change' ) . on ( 'change' , function ( ) {
var checked = $ ( this ) . prop ( 'checked' ) ;
$ ( '.informatiq-sp-select-product' ) . prop ( 'checked' , checked ) ;
$ ( '#informatiq-sp-select-all, #informatiq-sp-select-all-header' ) . prop ( 'checked' , checked ) ;
self . updateBulkButton ( ) ;
} ) ;
// Individual checkbox
$ ( '.informatiq-sp-select-product' ) . off ( 'change' ) . on ( 'change' , function ( ) {
self . updateBulkButton ( ) ;
} ) ;
// Bulk update button
$ ( '#informatiq-sp-bulk-update' ) . off ( 'click' ) . on ( 'click' , function ( ) {
self . handleBulkUpdate ( ) ;
} ) ;
} ,
/ * *
* Update bulk button state
* /
updateBulkButton : function ( ) {
var count = $ ( '.informatiq-sp-select-product:checked' ) . length ;
$ ( '#informatiq-sp-bulk-update' ) . prop ( 'disabled' , count === 0 ) . text ( 'Bulk Update Selected (' + count + ')' ) ;
} ,
/ * *
* Handle bulk update
* /
handleBulkUpdate : function ( ) {
var updates = [ ] ;
$ ( '.informatiq-sp-select-product:checked' ) . each ( function ( ) {
updates . push ( {
product _id : $ ( this ) . data ( 'product-id' ) ,
new _price : $ ( this ) . data ( 'new-price' )
} ) ;
} ) ;
if ( updates . length === 0 ) return ;
if ( ! confirm ( 'Update prices for ' + updates . length + ' products?' ) ) return ;
var $btn = $ ( '#informatiq-sp-bulk-update' ) ;
$btn . prop ( 'disabled' , true ) . text ( 'Updating...' ) ;
$ . ajax ( {
url : informatiqSP . ajaxUrl ,
type : 'POST' ,
data : {
action : 'informatiq_sp_bulk_update_prices' ,
nonce : informatiqSP . nonce ,
updates : updates
} ,
success : function ( response ) {
if ( response . success ) {
alert ( response . data . message ) ;
location . reload ( ) ;
} else {
alert ( 'Error: ' + response . data . message ) ;
$btn . prop ( 'disabled' , false ) . text ( 'Bulk Update Selected' ) ;
}
} ,
error : function ( ) {
alert ( 'Request failed' ) ;
$btn . prop ( 'disabled' , false ) . text ( 'Bulk Update Selected' ) ;
}
} ) ;
} ,
2025-12-23 07:48:45 +01:00
} ;
// Initialize when document is ready
$ ( document ) . ready ( function ( ) {
InformatiqSP . init ( ) ;
} ) ;
} ) ( jQuery ) ;