Files
adsPRO/templates/products/product_history.php
Jacek Pyziak efbdcce08a feat: Add XML file management functionality
- Created XmlFiles control class for handling XML file views and regeneration.
- Implemented method to retrieve clients with XML feeds in the factory class.
- Added database migration to include google_merchant_account_id in clients table.
- Created migrations for products_keyword_planner_terms and products_merchant_sync_log tables.
- Added campaign_keywords table migration for managing campaign keyword data.
- Developed main view template for displaying XML files and their statuses.
- Introduced a debug script for analyzing product URLs and their statuses.
2026-02-18 21:23:53 +01:00

368 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div class="products-page product-history-page">
<div class="products-header">
<h2><i class="fa-solid fa-chart-line"></i> Historia produktu #<?= (int) $this -> product_id; ?></h2>
<a href="/products" class="btn btn-primary btn-sm">
<i class="fa-solid fa-arrow-left"></i> Powrot do produktow
</a>
</div>
<div class="product-history-meta">
<span>Klient: #<?= (int) $this -> client_id; ?></span>
<span>Kampania: <?= (int) ( $this -> campaign_id ?? 0 ); ?></span>
<span>Grupa reklam: <?= (int) ( $this -> ad_group_id ?? 0 ); ?></span>
</div>
<div class="product-history-chart-wrap">
<div class="chart-with-form">
<div class="chart-area">
<div class="product-history-chart" id="container"></div>
</div>
<aside class="comment-form">
<form id="product-comment-form" autocomplete="off">
<div class="form-group">
<label for="comment_date">Data</label>
<input type="date" id="comment_date" name="date" required>
</div>
<div class="form-group">
<label for="comment_text">Komentarz</label>
<textarea id="comment_text" name="comment" placeholder="Wpisz komentarz..." required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-sm" id="save_comment">
<i class="fa-solid fa-floppy-disk"></i> Zapisz komentarz
</button>
</form>
</aside>
</div>
</div>
<div class="products-table-wrap">
<table class="table table-sm" id="products">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Wyswietlenia</th>
<th scope="col">Klikniecia</th>
<th scope="col">CTR</th>
<th scope="col">Koszt</th>
<th scope="col">Konwersje</th>
<th scope="col">Wartosc konwersji</th>
<th scope="col">ROAS</th>
<th scope="col">Data</th>
<th scope="col">Komentarz</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script type="text/javascript">
$(function() {
var client_id = <?= $this -> client_id;?>;
var product_id = <?= $this -> product_id;?>;
var campaign_id = <?= (int) ( $this -> campaign_id ?? 0 ); ?>;
var ad_group_id = <?= (int) ( $this -> ad_group_id ?? 0 ); ?>;
// Ustaw domyślnie dzisiejszą datę w formularzu (YYYY-MM-DD)
(function presetToday() {
var el = document.getElementById('comment_date');
if (!el) return;
var d = new Date();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
el.value = d.getFullYear() + '-' + m + '-' + day;
})();
// Inicjalizacja tabeli
var table = new DataTable('#products', {
ajax: {
type: 'POST',
url: '/products/get_product_history_table/client_id=' + client_id + '&product_id=' + product_id + '&campaign_id=' + campaign_id + '&ad_group_id=' + ad_group_id,
},
autoWidth: false,
lengthChange: false,
pageLength: 30,
processing: true,
serverSide: true,
searching: false,
columns: [
{ width: '100px', orderable: false },
{ width: '100px', name: 'impressions' },
{ width: '100px', name: 'clicks' },
{ width: '100px', name: 'ctr', className: "dt-type-numeric" },
{ width: '100px', name: 'cost', className: "dt-type-numeric" },
{ width: '100px', name: 'conversions' },
{ width: '175px', name: 'conversions_value', className: "dt-type-numeric" },
{ width: '100px', name: 'roas', className: "dt-type-numeric", orderable: false },
{ width: '100px', name: 'date_add' },
{ width: '250px', name: 'comment', orderable: false },
],
order: [ [ 8, 'desc' ] ],
language: {
processing: 'Ladowanie...',
emptyTable: 'Brak danych do wyswietlenia',
info: 'Wiersze _START_ - _END_ z _TOTAL_',
infoEmpty: '',
paginate: {
first: 'Pierwsza',
last: 'Ostatnia',
next: 'Dalej',
previous: 'Wstecz'
}
}
});
// WYKRES
$.ajax({
url: '/products/get_product_history_table_chart/',
method: 'POST',
data: { client_id: client_id, product_id: product_id, campaign_id: campaign_id, ad_group_id: ad_group_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, // trzyma przy lewej krawędzi linii
rotation: 0,
align: 'left',
style: { color: '#333333', fontSize: '14px' }
},
zIndex: 5
});
});
const chart = Highcharts.chart('container', {
title: { text: `` },
subtitle: { 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' } }
});
chart.xAxis[0].update({
plotLines: (chart.xAxis[0].options.plotLines || []).map(function(pl) {
return Highcharts.merge(pl, { label: { text: '' } });
})
}, false);
// 2) Dodaj „niewidzialne" markery do hovera
var points = (parsedData.comments || []).map(function(c) {
var idx = parsedData.dates.indexOf((c.date_add || '').split(' ')[0]);
return { x: idx, y: 0, comment: c.comment, comment_id: c.id };
});
chart.addSeries({
type: 'scatter',
name: 'Komentarz:',
data: points,
yAxis: 0,
tooltip: {
pointFormatter: function () {
return '<span style="font-size: 12px;">' + (this.comment || '') + '</span>';
}
},
marker: {
enabled: true,
radius: 4,
lineWidth: 0,
fillOpacity: 0 // marker „niewidoczny"
},
states: {
hover: { enabled: true, halo: { size: 5 } }
},
enableMouseTracking: true,
cursor: 'pointer',
point: {
events: {
click: function () {
var commentId = this.comment_id;
var commentText = this.comment;
if (confirm('Czy na pewno chcesz usunąć komentarz: "' + commentText + '"?')) {
$.ajax({
url: '/products/comment_delete/',
method: 'POST',
data: {
comment_id: commentId
},
success: function(res) {
res = JSON.parse(res);
if (res.status === 'ok') {
location.reload();
} else {
alert('Nie udało się usunąć komentarza. Spróbuj ponownie.');
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Błąd usuwania komentarza:', textStatus, errorThrown);
alert('Nie udało się usunąć komentarza. Spróbuj ponownie.');
}
});
}
}
}
}
}, false);
chart.redraw();
// MIN ROAS linia + dopasowanie osi
<?php if ($this->min_roas): ?>
(function() {
var limitVal = Number(<?= json_encode($this->min_roas) ?>);
var roasSeries = chart.series.find(function(s) {
return (s.name || '').toLowerCase() === 'roas';
});
var targetAxis = roasSeries ? roasSeries.yAxis : chart.yAxis[0];
targetAxis.addPlotLine({
id: 'min-roas-line',
color: 'red',
width: 2,
value: limitVal,
dashStyle: 'Dash',
zIndex: 5,
label: {
text: 'Limit <?= htmlspecialchars((string)$this->min_roas, ENT_QUOTES) ?>',
align: 'right',
style: { color: 'red', fontSize: '14px' }
}
});
function adjustAxisToIncludeValue(axis, val) {
var e = axis.getExtremes();
if (e.dataMin == null || e.dataMax == null) return;
var min = Math.min(e.dataMin, val);
var max = Math.max(e.dataMax, val);
var span = (max - min) || 1;
var pad = span * 0.05;
var newMin = min - pad;
var newMax = max + pad;
if (newMin !== e.min || newMax !== e.max) {
axis.setExtremes(newMin, newMax);
}
}
if (chart.hasLoaded) {
adjustAxisToIncludeValue(targetAxis, limitVal);
} else {
Highcharts.addEvent(chart, 'load', function () {
adjustAxisToIncludeValue(targetAxis, limitVal);
});
}
chart.series.forEach(function (s) {
Highcharts.addEvent(s, 'show', function () { adjustAxisToIncludeValue(targetAxis, limitVal); });
Highcharts.addEvent(s, 'hide', function () { adjustAxisToIncludeValue(targetAxis, limitVal); });
});
})();
<?php endif; ?>
},
error: function (jqXHR, textStatus, errorThrown) {
console.error('Error AJAX:', textStatus, errorThrown);
}
});
// OBSŁUGA FORMULARZA zapis komentarza i odświeżenie strony
$('#product-comment-form').on('submit', function(e) {
e.preventDefault();
var $btn = $('#save_comment');
var dateStr = $.trim($('#comment_date').val());
var comment = $.trim($('#comment_text').val());
if (!dateStr) { alert('Wybierz datę.'); return; }
if (!comment) { alert('Wpisz komentarz.'); return; }
$btn.prop('disabled', true).text('Zapisywanie...');
$.ajax({
url: '/products/comment_add/',
method: 'POST',
data: {
client_id: client_id,
product_id: product_id,
date: dateStr, // oczekiwany format: YYYY-MM-DD
comment: comment
},
success: function(res) {
res = JSON.parse(res);
if (res.status === 'ok') {
location.reload();
} else {
alert('Nie udało się zapisać komentarza. Spróbuj ponownie.');
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Błąd zapisu komentarza:', textStatus, errorThrown);
alert('Nie udało się zapisać komentarza. Spróbuj ponownie.');
$btn.prop('disabled', false).text('Zapisz komentarz');
}
});
});
// OBSŁUGA USUWANIA KOMENTARZA Z TABELI
$(document).on('click', '.delete-comment', function(e) {
e.preventDefault();
var comment_id = $(this).data('comment-id');
if (!confirm('Czy na pewno chcesz usunąć ten komentarz?')) {
return;
}
$.ajax({
url: '/products/comment_delete/',
method: 'POST',
data: {
comment_id: comment_id
},
success: function(res) {
res = JSON.parse(res);
if (res.status === 'ok') {
location.reload();
} else {
alert('Nie udało się usunąć komentarza. Spróbuj ponownie.');
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Błąd usuwania komentarza:', textStatus, errorThrown);
alert('Nie udało się usunąć komentarza. Spróbuj ponownie.');
}
});
});
});
</script>