feat: Enhance campaign terms management with keyword addition and match type change functionality

- Added a toolbar for adding keywords in the campaign terms view.
- Implemented match type change functionality with a confirmation dialog.
- Added delete functionality for keywords with confirmation.
- Updated styles for new buttons and icons in the campaign terms view.
- Enhanced product view with warning icons for product alerts and corresponding modal display.
- Updated product table to include a new column for warnings and adjusted column visibility settings.
- Documented project overview, code style conventions, suggested commands, and task completion checklist.
This commit is contained in:
2026-02-24 01:30:13 +01:00
parent 63857639ff
commit 4aefa5f445
18 changed files with 1087 additions and 59 deletions

View File

@@ -160,6 +160,11 @@
<div class="campaigns-extra-card-title">
<i class="fa-solid fa-key"></i> Frazy dodane do kampanii/grup reklam
</div>
<div class="terms-keywords-toolbar">
<button type="button" id="terms_add_keyword_btn" class="terms-negative-bulk-btn">
<i class="fa-solid fa-plus"></i> Dodaj fraze
</button>
</div>
<div class="campaigns-extra-table-wrap">
<table class="table table-sm campaigns-extra-table" id="terms_keywords_table">
<thead>
@@ -167,6 +172,7 @@
<th>Fraza</th>
<th>Match type</th>
<th>Grupa reklam</th>
<th>Akcja</th>
<th>Klik. all</th>
<th>Koszt all</th>
<th>Wartosc all</th>
@@ -180,7 +186,7 @@
</tr>
</thead>
<tbody>
<tr><td colspan="13" class="campaigns-empty-row">Brak danych.</td></tr>
<tr><td colspan="14" class="campaigns-empty-row">Brak danych.</td></tr>
</tbody>
</table>
</div>
@@ -1364,6 +1370,13 @@ function terms_clear_negative_selection()
}
var MATCH_TYPE_LABELS = { 'BROAD': 'Przyblizone', 'PHRASE': 'Do wyrazenia', 'EXACT': 'Scisle' };
function render_match_type_label( data, type ) {
if ( type !== 'display' ) return data || '';
var val = String( data || '' ).toUpperCase();
return MATCH_TYPE_LABELS[val] || val;
}
function build_ad_groups_table( rows )
{
destroy_table_if_exists( '#terms_ad_groups_table' );
@@ -1530,7 +1543,7 @@ function build_negative_terms_table( rows )
return '<input type="checkbox" class="terms-negative-select-row" data-negative-keyword-id="' + negative_keyword_id + '" aria-label="Zaznacz fraze do usuniecia">';
} },
{ data: 'keyword_text', defaultContent: '', width: '520px' },
{ data: 'match_type', defaultContent: '' },
{ data: 'match_type', defaultContent: '', render: render_match_type_label },
{ data: 'id', orderable: false, searchable: false, render: function( data, type ) {
if ( type !== 'display' ) return data;
return '<button type="button" class="terms-remove-negative-btn" data-negative-keyword-id="' + data + '" title="Usun z wykluczajacych"><i class="fa-solid fa-trash"></i></button>';
@@ -1597,11 +1610,16 @@ function build_keywords_table( rows )
lengthChange: false,
pageLength: 15,
pagingType: 'simple_numbers',
order: [[ 3, 'desc' ]],
order: [[ 4, 'desc' ]],
columns: [
{ data: 'keyword_text', defaultContent: '' },
{ data: 'match_type', defaultContent: '' },
{ data: 'match_type', defaultContent: '', render: render_match_type_label },
{ data: 'ad_group_name', defaultContent: '-' },
{ data: 'id', orderable: false, searchable: false, render: function( data, type, row ) {
if ( type !== 'display' ) return data;
return '<button type="button" class="terms-change-match-type-btn" data-keyword-id="' + data + '" title="Zmien dopasowanie"><i class="fa-solid fa-pen-to-square"></i></button>' +
' <button type="button" class="terms-delete-keyword-btn" data-keyword-id="' + data + '" title="Usun fraze"><i class="fa-solid fa-trash"></i></button>';
} },
{ data: 'clicks_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 0 ) : Number( data || 0 ); } },
{ data: 'cost_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
{ data: 'conversion_value_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
@@ -1619,10 +1637,11 @@ function build_keywords_table( rows )
columnDefs: [
{ targets: 0, className: 'text-cell phrase-nowrap', width: '220px' },
{ targets: [1,2], className: 'text-cell', width: '140px' },
{ targets: [3,8], className: 'num-cell', width: '70px' },
{ targets: [4,5,9,10], className: 'num-cell', width: '85px' },
{ targets: [6,11], className: 'num-cell', width: '80px' },
{ targets: [7,12], className: 'num-cell', width: '130px' }
{ targets: 3, className: 'dt-center', width: '90px' },
{ targets: [4,9], className: 'num-cell', width: '70px' },
{ targets: [5,6,10,11], className: 'num-cell', width: '85px' },
{ targets: [7,12], className: 'num-cell', width: '80px' },
{ targets: [8,13], className: 'num-cell', width: '130px' }
],
language: {
emptyTable: 'Brak danych do wyswietlenia',
@@ -2338,6 +2357,292 @@ $( function()
terms_ad_groups_table.columns.adjust().draw( false );
});
$( 'body' ).on( 'click', '.terms-change-match-type-btn', function()
{
var keyword_id = parseInt( $( this ).data( 'keyword-id' ), 10 );
var row_data = terms_keywords_table ? terms_keywords_table.row( $( this ).closest( 'tr' ) ).data() : null;
var keyword_text = row_data ? String( row_data.keyword_text || '' ) : '';
var current_match = row_data ? String( row_data.match_type || 'BROAD' ).toUpperCase() : 'BROAD';
if ( !keyword_id ) return;
$.confirm({
title: 'Zmien dopasowanie frazy',
columnClass: 'col-md-4 col-md-offset-4',
content:
'<div style="margin-bottom:10px;"><strong>' + $('<span>').text( keyword_text ).html() + '</strong></div>' +
'<div class="form-group" style="margin-bottom:0;">' +
'<label for="kw_new_match_type" style="display:block;margin-bottom:6px;">Nowy typ dopasowania</label>' +
'<select id="kw_new_match_type" class="form-control">' +
'<option value="BROAD"' + ( current_match === 'BROAD' ? ' selected' : '' ) + '>Dopasowanie przyblizone</option>' +
'<option value="PHRASE"' + ( current_match === 'PHRASE' ? ' selected' : '' ) + '>Dopasowanie do wyrazenia</option>' +
'<option value="EXACT"' + ( current_match === 'EXACT' ? ' selected' : '' ) + '>Dopasowanie scisle</option>' +
'</select>' +
'<small style="display:block;margin-top:6px;color:#64748B;">Obecne: ' + current_match + '. Zmiana usunie stary keyword i doda nowy w Google Ads.</small>' +
'</div>',
type: 'blue',
buttons: {
confirm: {
text: 'Zmien',
btnClass: 'btn-blue',
action: function()
{
var new_match_type = this.$content.find( '#kw_new_match_type' ).val() || 'BROAD';
var modal = this;
if ( new_match_type === current_match )
{
$.alert({ title: 'Info', columnClass: 'col-md-4 col-md-offset-4', content: 'Wybrany typ dopasowania jest taki sam jak obecny.', type: 'blue' });
return;
}
modal.showLoading( true );
$.ajax({
url: '/campaign_terms/update_keyword_match_type/',
type: 'POST',
data: { keyword_id: keyword_id, new_match_type: new_match_type },
success: function( response )
{
var data = JSON.parse( response );
modal.close();
if ( data.success )
{
var debugHtml = terms_build_debug_details_html( data.debug || null );
var successDialog = $.alert({
title: 'Sukces',
columnClass: 'col-md-4 col-md-offset-4',
content: ( data.message || 'Dopasowanie zmienione.' ) + debugHtml,
type: 'green'
});
setTimeout( function() { successDialog.close(); }, 8000 );
load_phrase_tables();
}
else
{
var debugHtml = terms_build_debug_details_html( data.debug || null );
$.alert({
title: 'Blad',
columnClass: 'col-md-4 col-md-offset-4',
content: ( data.message || 'Nie udalo sie zmienic dopasowania.' ) + ( data.error ? '<br><small>' + data.error + '</small>' : '' ) + debugHtml,
type: 'red'
});
}
},
error: function()
{
modal.close();
$.alert({ title: 'Blad', columnClass: 'col-md-4 col-md-offset-4', content: 'Blad komunikacji z serwerem.', type: 'red' });
}
});
return false;
}
},
cancel: { text: 'Anuluj' }
}
});
});
$( 'body' ).on( 'click', '.terms-delete-keyword-btn', function()
{
var keyword_id = parseInt( $( this ).data( 'keyword-id' ), 10 );
var row_data = terms_keywords_table ? terms_keywords_table.row( $( this ).closest( 'tr' ) ).data() : null;
var keyword_text = row_data ? String( row_data.keyword_text || '' ) : '';
if ( !keyword_id ) return;
$.confirm({
title: 'Usun fraze',
columnClass: 'col-md-4 col-md-offset-4',
content: 'Czy na pewno usunac fraze <strong>' + $('<span>').text( keyword_text ).html() + '</strong>? Operacja zostanie wyslana do Google Ads API.',
type: 'red',
buttons: {
confirm: {
text: 'Usun',
btnClass: 'btn-red',
keys: [ 'enter' ],
action: function()
{
var modal = this;
modal.showLoading( true );
$.ajax({
url: '/campaign_terms/delete_keyword/',
type: 'POST',
data: { keyword_id: keyword_id },
success: function( response )
{
var data = JSON.parse( response );
modal.close();
var debugHtml = terms_build_debug_details_html( data.debug || null );
if ( data.success )
{
$.alert({
title: 'Sukces',
columnClass: 'col-md-4 col-md-offset-4',
content: ( data.message || 'Fraza zostala usunieta.' ) + debugHtml,
type: 'green'
});
load_phrase_tables();
}
else
{
$.alert({
title: 'Blad',
columnClass: 'col-md-4 col-md-offset-4',
content: ( data.message || 'Nie udalo sie usunac frazy.' ) + ( data.error ? '<br><small>' + data.error + '</small>' : '' ) + debugHtml,
type: 'red'
});
}
},
error: function()
{
modal.close();
$.alert({ title: 'Blad', columnClass: 'col-md-4 col-md-offset-4', content: 'Blad komunikacji z serwerem.', type: 'red' });
}
});
return false;
}
},
cancel: { text: 'Anuluj' }
}
});
});
$( 'body' ).on( 'click', '#terms_add_keyword_btn', function()
{
var campaign_id = $( '#terms_campaign_id' ).val();
if ( !campaign_id )
{
$.alert({ title: 'Uwaga', columnClass: 'col-md-4 col-md-offset-4', content: 'Wybierz kampanie przed dodaniem frazy.', type: 'orange' });
return;
}
var ad_group_options = '';
$( '#terms_ad_group_id option' ).each( function()
{
var val = $( this ).val();
if ( val )
ad_group_options += '<option value="' + val + '">' + $('<span>').text( $( this ).text() ).html() + '</option>';
});
if ( !ad_group_options )
{
$.alert({ title: 'Uwaga', columnClass: 'col-md-4 col-md-offset-4', content: 'Brak grup reklam. Wybierz kampanie z grupami.', type: 'orange' });
return;
}
var selected_ad_group = $( '#terms_ad_group_id' ).val() || '';
$.confirm({
title: 'Dodaj fraze do grupy reklam',
columnClass: 'col-md-4 col-md-offset-4',
content:
'<div class="form-group" style="margin-bottom:10px;">' +
'<label for="add_kw_text" style="display:block;margin-bottom:6px;">Fraza</label>' +
'<input id="add_kw_text" type="text" class="form-control" placeholder="Wpisz fraze..." />' +
'</div>' +
'<div class="form-group" style="margin-bottom:10px;">' +
'<label for="add_kw_match_type" style="display:block;margin-bottom:6px;">Typ dopasowania</label>' +
'<select id="add_kw_match_type" class="form-control">' +
'<option value="BROAD" selected>Dopasowanie przyblizone</option>' +
'<option value="PHRASE">Dopasowanie do wyrazenia</option>' +
'<option value="EXACT">Dopasowanie scisle</option>' +
'</select>' +
'</div>' +
'<div class="form-group" style="margin-bottom:0;">' +
'<label for="add_kw_ad_group" style="display:block;margin-bottom:6px;">Grupa reklam</label>' +
'<select id="add_kw_ad_group" class="form-control">' + ad_group_options + '</select>' +
'</div>',
type: 'blue',
onContentReady: function()
{
if ( selected_ad_group )
this.$content.find( '#add_kw_ad_group' ).val( selected_ad_group );
},
buttons: {
confirm: {
text: 'Dodaj',
btnClass: 'btn-blue',
action: function()
{
var keyword_text = $.trim( this.$content.find( '#add_kw_text' ).val() || '' );
var match_type = this.$content.find( '#add_kw_match_type' ).val() || 'BROAD';
var ad_group_id = this.$content.find( '#add_kw_ad_group' ).val() || '';
var modal = this;
if ( keyword_text === '' )
{
$.alert({ title: 'Uwaga', columnClass: 'col-md-4 col-md-offset-4', content: 'Wpisz fraze do dodania.', type: 'orange' });
return false;
}
if ( !ad_group_id )
{
$.alert({ title: 'Uwaga', columnClass: 'col-md-4 col-md-offset-4', content: 'Wybierz grupe reklam.', type: 'orange' });
return false;
}
modal.showLoading( true );
$.ajax({
url: '/campaign_terms/add_keyword/',
type: 'POST',
data: {
campaign_id: campaign_id,
ad_group_id: ad_group_id,
keyword_text: keyword_text,
match_type: match_type
},
success: function( response )
{
var data = JSON.parse( response );
modal.close();
if ( data.success )
{
var debugHtml = terms_build_debug_details_html( data.debug || null );
var successDialog = $.alert({
title: 'Sukces',
columnClass: 'col-md-4 col-md-offset-4',
content: ( data.message || 'Fraza zostala dodana.' )
+ '<br><small style="display:block;margin-top:8px;color:#64748B;">Zmiana moze byc widoczna w panelu Google Ads po 1-3 minutach.</small>'
+ debugHtml,
type: 'green'
});
setTimeout( function() { successDialog.close(); }, 10000 );
load_phrase_tables();
}
else
{
var debugHtml = terms_build_debug_details_html( data.debug || null );
$.alert({
title: 'Blad',
columnClass: 'col-md-4 col-md-offset-4',
content: ( data.message || 'Nie udalo sie dodac frazy.' ) + ( data.error ? '<br><small>' + data.error + '</small>' : '' ) + debugHtml,
type: 'red'
});
}
},
error: function()
{
modal.close();
$.alert({ title: 'Blad', columnClass: 'col-md-4 col-md-offset-4', content: 'Blad komunikacji z serwerem.', type: 'red' });
}
});
return false;
}
},
cancel: { text: 'Anuluj' }
}
});
});
terms_restore_ad_groups_collapsed();
reset_all_tables();