Files
adsPRO/templates/products/product_history.php
Jacek Pyziak 7573312038 feat: Add Gemini AI integration for product title and description optimization
- 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.
2026-02-22 00:44:03 +01:00

369 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,
},
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>