- Implemented Gemini API service for generating optimized product titles and descriptions based on Google Merchant Center guidelines. - Added settings for Gemini API key and model selection in user settings. - Enhanced product management views to support AI-generated suggestions for titles and descriptions. - Enabled state saving for various data tables across campaign, terms, logs, and products views. - Introduced AI prompt templates for generating product descriptions and categories.
369 lines
13 KiB
PHP
369 lines
13 KiB
PHP
<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,
|
||
},
|
||
stateSave: true,
|
||
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>
|