Add migrations for Google Ads settings and demo data

- Create migration for global settings table and add google_ads_customer_id and google_ads_start_date columns to clients table.
- Add migration to include product_url column in products_data table.
- Insert demo data for campaigns, products, and their history for client 'pomysloweprezenty.pl'.
- Implement client management interface with modals for adding and editing clients, including Google Ads Customer ID and data retrieval start date.
This commit is contained in:
2026-02-15 17:46:32 +01:00
parent 32f25dc48c
commit afe9d6216d
32 changed files with 8088 additions and 2288 deletions

View File

@@ -1,361 +1,336 @@
<div class="admin-form theme-primary">
<div class="panel heading-border panel-primary">
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<label class="field select">
<select id="client_id" name="client_id">
<option value="">--- wybierz klienta ---</option>
<? foreach ( $this -> clients as $client ):?>
<option value="<?= $client['id'];?>"><?= $client['name'];?></option>
<? endforeach;?>
</select>
<i class="arrow double"></i>
</label>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-10">
<label class="field select">
<select id="campaign_id" name="campaign_id">
<option value="">--- wybierz kampanię ---</option>
</select>
<i class="arrow double"></i>
</label>
</div>
<div class="col-md-2">
<button type="button" id="delete_campaign" class="btn btn-danger btn-block" title="Usuń kampanię">
<i class="fa fa-trash"></i>
</button>
</div>
</div>
</div>
<div class="campaigns-page">
<div class="campaigns-header">
<h2><i class="fa-solid fa-chart-line"></i> Kampanie</h2>
</div>
<!-- Filtry -->
<div class="campaigns-filters">
<div class="filter-group">
<label for="client_id"><i class="fa-solid fa-building"></i> Klient</label>
<select id="client_id" name="client_id" class="form-control">
<option value=""> wybierz klienta </option>
<?php foreach ( $this -> clients as $client ): ?>
<option value="<?= $client['id']; ?>"><?= htmlspecialchars( $client['name'] ); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="filter-group">
<label for="campaign_id"><i class="fa-solid fa-bullhorn"></i> Kampania</label>
<div class="filter-with-action">
<select id="campaign_id" name="campaign_id" class="form-control">
<option value=""> wybierz kampanię </option>
</select>
<button type="button" id="delete_campaign" class="btn-icon btn-icon-delete" title="Usuń kampanię">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
<div class="admin-form theme-primary">
<div class="panel heading-border panel-primary">
<div class="panel-body">
<figure class="highcharts-figure">
<div id="container"></div>
</figure>
</div>
<!-- Wykres -->
<div class="campaigns-chart-wrap">
<div id="container"></div>
</div>
<!-- Tabela historii -->
<div class="campaigns-table-wrap">
<table class="table" id="products">
<thead>
<tr>
<th>Data</th>
<th>ROAS (30 dni)</th>
<th>ROAS (all time)</th>
<th>Wartość konwersji (30 dni)</th>
<th>Wydatki (30 dni)</th>
<th>Komentarz</th>
<th>Strategia ustalania stawek</th>
<th>Budżet</th>
<th style="width: 60px; text-align: center;">Akcje</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="admin-form theme-primary">
<div class="panel heading-border panel-primary">
<div class="panel-body">
<table class="table" id="products">
<thead>
<tr>
<th scope="col">Data</th>
<th scope="col">ROAS (30 dni)</th>
<th scope="col">ROAS (all time)</th>
<th scope="col">Wartość konwersji (30 dni)</th>
<th scope="col">Wydatki (30 dni)</th>
<th scope="col">Komentarz</th>
<th scope="col">Strategia ustalania stawek</th>
<th scope="col">Budżet</th>
<th scope="col">Akcje</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<script type="text/javascript">
var client_id = '';
var client_id = '';
function reloadChart()
function reloadChart()
{
var campaign_id = $( '#campaign_id' ).val();
if ( !campaign_id ) return;
$.ajax({
url: '/campaigns/get_campaign_history_data_table_chart/',
method: 'POST',
data: { campaign_id: campaign_id },
success: function( response )
{
const parsedData = JSON.parse( response );
let plotLines = [];
parsedData.comments.forEach( function( comment ) {
plotLines.push({
color: '#333333',
width: 1,
value: parsedData.dates.indexOf( comment.date_add.split(' ')[0] ),
dashStyle: 'Solid',
label: {
text: comment.comment,
align: 'left',
style: { color: '#333333', fontSize: '13px' }
},
zIndex: 5
});
});
Highcharts.chart( 'container', {
chart: {
style: { fontFamily: '"Open Sans", sans-serif' },
backgroundColor: 'transparent'
},
title: { text: '' },
subtitle: { text: '' },
yAxis: {
title: { text: '' },
gridLineColor: '#E2E8F0'
},
xAxis: {
categories: parsedData.dates,
labels: {
style: { fontSize: '12px', color: '#8899A6' },
formatter: function() {
var date = new Date( Date.parse( this.value ) );
var day = date.getDate();
var month = date.getMonth() + 1;
var year = date.getFullYear();
if ( day === 1 || this.isLast ) {
return year + '-' + ( month < 10 ? '0' + month : month ) + '-' + ( day < 10 ? '0' + day : day );
}
return null;
}
},
plotLines: plotLines
},
legend: {
layout: 'horizontal',
align: 'center',
verticalAlign: 'bottom',
itemStyle: { fontSize: '13px', color: '#4E5E6A' }
},
plotOptions: {
series: {
label: { connectorAllowed: false },
pointStart: 0
}
},
colors: ['#6690F4', '#57B951', '#FF8C00', '#CC0000', '#8B5CF6'],
series: parsedData.chart_data,
tooltip: { style: { fontSize: '13px' } },
credits: { enabled: false }
});
},
error: function( jqXHR, textStatus, errorThrown ) {
console.error( 'Error AJAX:', textStatus, errorThrown );
}
});
}
$( function()
{
// Załaduj kampanie po wyborze klienta
$( 'body' ).on( 'change', '#client_id', function()
{
var campaign_id = $( '#campaign_id' ).val();
if ( !campaign_id ) return;
client_id = $( this ).val();
var campaigns_select = $( '#campaign_id' );
$.ajax({
url: '/campaigns/get_campaign_history_data_table_chart/',
method: 'POST',
data: {
campaign_id: campaign_id
},
success: function(response) {
const parsedData = JSON.parse(response);
url: '/campaigns/get_campaigns_list/client_id=' + client_id,
type: 'GET',
success: function( response )
{
var data = JSON.parse( response );
campaigns_select.empty();
campaigns_select.append( '<option value="">— wybierz kampanię —</option>' );
let plotLines = [];
var campaigns = Object.entries( data.campaigns );
parsedData.comments.forEach(function(comment) {
plotLines.push({
color: '#333333',
width: 1,
value: parsedData.dates.indexOf(comment.date_add.split(' ')[0]),
dashStyle: 'Solid',
label: {
text: comment.comment,
align: 'left',
style: {
color: '#333333',
fontSize: '14px'
}
},
zIndex: 5
});
campaigns.sort( function( a, b ) {
if ( a[1] === "--- konto ---" ) return -1;
if ( b[1] === "--- konto ---" ) return 1;
return a[1] > b[1] ? 1 : ( a[1] < b[1] ? -1 : 0 );
});
Highcharts.chart('container', {
title: {
text: ``,
},
subtitle: {
text: ``,
},
yAxis: {
title: {
text: ''
},
},
xAxis: {
categories: parsedData.dates,
labels: {
style: {
fontSize: '14px'
},
formatter: function() {
var date = new Date(Date.parse(this.value));
var day = date.getDate();
var month = date.getMonth() + 1;
var year = date.getFullYear();
if (day === 1 || this.isLast) {
return `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`;
} else {
return null;
}
}
},
plotLines: plotLines
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
itemStyle: {
fontSize: '14px'
}
},
plotOptions: {
series: {
label: {
connectorAllowed: false
},
pointStart: 0,
},
},
series: parsedData.chart_data,
tooltip: {
style: {
fontSize: '14px'
}
}
campaigns.forEach( function( [key, value] ) {
campaigns_select.append( '<option value="' + value.id + '">' + value.campaign_name + '</option>' );
});
},
error: function (jqXHR, textStatus, errorThrown) {
console.error('Error AJAX:', textStatus, errorThrown);
<?php if ( $campaign_id ): ?>
campaigns_select.val( '<?= $campaign_id; ?>' ).trigger( 'change' );
<?php endif; ?>
}
});
}
$( function()
{
// load campaigns from server when client is selected
$( 'body' ).on( 'change', '#client_id', function()
{
client_id = $( this ).val();
var campaigns_select = $( '#campaign_id' );
$.ajax(
{
url: '/campaigns/get_campaigns_list/client_id=' + client_id,
type: 'GET',
success: function( response )
{
var data = JSON.parse(response);
campaigns_select.empty();
campaigns_select.append('<option value="">- wybierz kampanię -</option>');
var campaigns = Object.entries(data.campaigns);
// Sortowanie kampanii: "--- konto ---" zawsze na początku, reszta alfabetycznie
campaigns.sort(function(a, b) {
if (a[1] === "--- konto ---") {
return -1; // "a" idzie na początek
} else if (b[1] === "--- konto ---") {
return 1; // "b" idzie na początek
} else {
// Porównanie ciągów znaków bez użycia localeCompare
return a[1] > b[1] ? 1 : (a[1] < b[1] ? -1 : 0);
}
});
// Dodawanie opcji do selecta
campaigns.forEach(function([key, value]) {
campaigns_select.append('<option value="' + value.id + '">' + value.campaign_name + '</option>');
});
<? if ( $campaign_id ): ?>
campaigns_select.val('<?= $campaign_id; ?>').trigger('change');
<? endif; ?>
}
});
});
$( 'body' ).on( 'click', '#delete_campaign', function()
{
var campaign_id = $( '#campaign_id' ).val();
var campaign_name = $( '#campaign_id option:selected' ).text();
if ( !campaign_id )
{
$.alert({
title: 'Uwaga',
content: 'Najpierw wybierz kampanię do usunięcia.',
type: 'orange'
});
return;
}
$.confirm({
title: 'Potwierdzenie usunięcia',
content: 'Czy na pewno chcesz usunąć kampanię <strong>' + campaign_name + '</strong>?<br><br>Ta operacja jest nieodwracalna i usunie również całą historię kampanii.',
type: 'red',
buttons: {
confirm: {
text: 'Usuń',
btnClass: 'btn-red',
keys: ['enter'],
action: function()
{
$.ajax({
url: '/campaigns/delete_campaign/campaign_id=' + campaign_id,
type: 'POST',
success: function( response )
{
var data = JSON.parse( response );
if ( data.success )
{
$.alert({
title: 'Sukces',
content: 'Kampania została usunięta.',
type: 'green',
autoClose: 'ok|2000'
});
$( '#client_id' ).trigger( 'change' );
}
else
{
$.alert({
title: 'Błąd',
content: data.message || 'Nie udało się usunąć kampanii.',
type: 'red'
});
}
}
});
}
},
cancel: {
text: 'Anuluj'
}
}
});
});
$( 'body' ).on( 'click', '.delete-history-entry', function()
{
var btn = $( this );
var history_id = btn.data( 'id' );
var date = btn.data( 'date' );
$.confirm({
title: 'Potwierdzenie usunięcia',
content: 'Czy na pewno chcesz usunąć wpis z dnia <strong>' + date + '</strong>?',
type: 'red',
buttons: {
confirm: {
text: 'Usuń',
btnClass: 'btn-red',
keys: ['enter'],
action: function()
{
$.ajax({
url: '/campaigns/delete_history_entry/history_id=' + history_id,
type: 'POST',
success: function( response )
{
var data = JSON.parse( response );
if ( data.success )
{
$.alert({
title: 'Sukces',
content: 'Wpis został usunięty.',
type: 'green',
autoClose: 'ok|2000'
});
$( '#products' ).DataTable().ajax.reload( null, false );
reloadChart();
}
else
{
$.alert({
title: 'Błąd',
content: data.message || 'Nie udało się usunąć wpisu.',
type: 'red'
});
}
}
});
}
},
cancel: {
text: 'Anuluj'
}
}
});
});
$( 'body' ).on( 'change', '#campaign_id', function()
{
var campaign_id = $( this ).val();
table = $( '#products' ).DataTable();
table.destroy();
new DataTable( '#products', {
ajax: {
type: 'POST',
url: '/campaigns/get_campaign_history_data_table/campaign_id=' + campaign_id,
},
processing: true,
serverSide: true,
columns: [
{ width: '100px', name: 'date', orderable: false },
{ width: '200px', name: 'roas30', orderable: false, className: "dt-type-numeric" },
{ width: '200px', name: 'roas_all_time', orderable: false, className: "dt-type-numeric" },
{ width: '250px', name: 'conversion_value', orderable: false, className: "dt-type-numeric" },
{ width: '200px', name: 'spend30', orderable: false, className: "dt-type-numeric" },
{ width: 'auto', name: 'bidding_strategy', orderable: false },
{ width: 'auto', name: 'comment', orderable: false },
{ width: '150px', name: 'budget', orderable: false, className: "dt-type-numeric" },
{ width: '80px', name: 'actions', orderable: false, className: "dt-center" }
],
});
reloadChart();
})
});
</script>
// Usuwanie kampanii
$( 'body' ).on( 'click', '#delete_campaign', function()
{
var campaign_id = $( '#campaign_id' ).val();
var campaign_name = $( '#campaign_id option:selected' ).text();
if ( !campaign_id )
{
$.alert({
title: 'Uwaga',
content: 'Najpierw wybierz kampanię do usunięcia.',
type: 'orange'
});
return;
}
$.confirm({
title: 'Potwierdzenie usunięcia',
content: 'Czy na pewno chcesz usunąć kampanię <strong>' + campaign_name + '</strong>?<br><br>Ta operacja jest nieodwracalna i usunie również całą historię kampanii.',
type: 'red',
buttons: {
confirm: {
text: 'Usuń',
btnClass: 'btn-red',
keys: ['enter'],
action: function()
{
$.ajax({
url: '/campaigns/delete_campaign/campaign_id=' + campaign_id,
type: 'POST',
success: function( response )
{
var data = JSON.parse( response );
if ( data.success )
{
$.alert({
title: 'Sukces',
content: 'Kampania została usunięta.',
type: 'green',
autoClose: 'ok|2000'
});
$( '#client_id' ).trigger( 'change' );
}
else
{
$.alert({
title: 'Błąd',
content: data.message || 'Nie udało się usunąć kampanii.',
type: 'red'
});
}
}
});
}
},
cancel: { text: 'Anuluj' }
}
});
});
// Usuwanie wpisu historii
$( 'body' ).on( 'click', '.delete-history-entry', function()
{
var btn = $( this );
var history_id = btn.data( 'id' );
var date = btn.data( 'date' );
$.confirm({
title: 'Potwierdzenie usunięcia',
content: 'Czy na pewno chcesz usunąć wpis z dnia <strong>' + date + '</strong>?',
type: 'red',
buttons: {
confirm: {
text: 'Usuń',
btnClass: 'btn-red',
keys: ['enter'],
action: function()
{
$.ajax({
url: '/campaigns/delete_history_entry/history_id=' + history_id,
type: 'POST',
success: function( response )
{
var data = JSON.parse( response );
if ( data.success )
{
$.alert({
title: 'Sukces',
content: 'Wpis został usunięty.',
type: 'green',
autoClose: 'ok|2000'
});
$( '#products' ).DataTable().ajax.reload( null, false );
reloadChart();
}
else
{
$.alert({
title: 'Błąd',
content: data.message || 'Nie udało się usunąć wpisu.',
type: 'red'
});
}
}
});
}
},
cancel: { text: 'Anuluj' }
}
});
});
// Załaduj dane po wyborze kampanii
$( 'body' ).on( 'change', '#campaign_id', function()
{
var campaign_id = $( this ).val();
table = $( '#products' ).DataTable();
table.destroy();
new DataTable( '#products', {
ajax: {
type: 'POST',
url: '/campaigns/get_campaign_history_data_table/campaign_id=' + campaign_id,
},
processing: true,
serverSide: true,
searching: false,
lengthChange: false,
pageLength: 15,
columns: [
{ width: '130px', name: 'date', orderable: false, className: "nowrap" },
{ width: '120px', name: 'roas30', orderable: false, className: "dt-type-numeric" },
{ width: '120px', name: 'roas_all_time', orderable: false, className: "dt-type-numeric" },
{ width: '180px', name: 'conversion_value', orderable: false, className: "dt-type-numeric" },
{ width: '140px', name: 'spend30', orderable: false, className: "dt-type-numeric" },
{ width: 'auto', name: 'comment', orderable: false },
{ width: 'auto', name: 'bidding_strategy', orderable: false },
{ width: '100px', name: 'budget', orderable: false, className: "dt-type-numeric" },
{ width: '60px', name: 'actions', orderable: false, className: "dt-center" }
],
language: {
processing: 'Ładowanie...',
emptyTable: 'Brak danych do wyświetlenia',
info: 'Wpisy _START_ - _END_ z _TOTAL_',
infoEmpty: '',
lengthMenu: 'Pokaż _MENU_ wpisów',
paginate: {
first: 'Pierwsza',
last: 'Ostatnia',
next: 'Dalej',
previous: 'Wstecz'
}
}
});
reloadChart();
});
});
</script>