feat: Add campaign comments functionality with API support and database migration
This commit is contained in:
34
.vscode/ftp-kr.sync.cache.json
vendored
34
.vscode/ftp-kr.sync.cache.json
vendored
@@ -9,8 +9,8 @@
|
||||
},
|
||||
"api.php": {
|
||||
"type": "-",
|
||||
"size": 2446,
|
||||
"lmtime": 0,
|
||||
"size": 4040,
|
||||
"lmtime": 1772671706766,
|
||||
"modified": false
|
||||
},
|
||||
"autoload": {
|
||||
@@ -71,9 +71,9 @@
|
||||
},
|
||||
"class.Api.php": {
|
||||
"type": "-",
|
||||
"size": 20317,
|
||||
"lmtime": 1744498273470,
|
||||
"modified": true
|
||||
"size": 25694,
|
||||
"lmtime": 1772667215646,
|
||||
"modified": false
|
||||
},
|
||||
"class.CampaignAlerts.php": {
|
||||
"type": "-",
|
||||
@@ -83,8 +83,8 @@
|
||||
},
|
||||
"class.Campaigns.php": {
|
||||
"type": "-",
|
||||
"size": 6153,
|
||||
"lmtime": 1771626580811,
|
||||
"size": 7262,
|
||||
"lmtime": 1772671343928,
|
||||
"modified": false
|
||||
},
|
||||
"class.CampaignTerms.php": {
|
||||
@@ -157,8 +157,8 @@
|
||||
},
|
||||
"class.Campaigns.php": {
|
||||
"type": "-",
|
||||
"size": 13229,
|
||||
"lmtime": 1771966790175,
|
||||
"size": 14333,
|
||||
"lmtime": 1772671326827,
|
||||
"modified": false
|
||||
},
|
||||
"class.Clients.php": {
|
||||
@@ -707,6 +707,12 @@
|
||||
"lmtime": 1771966738564,
|
||||
"modified": false
|
||||
},
|
||||
"026_clients_bestseller_settings.sql": {
|
||||
"type": "-",
|
||||
"size": 1613,
|
||||
"lmtime": 1772611513545,
|
||||
"modified": false
|
||||
},
|
||||
"026_cron_queue.sql": {
|
||||
"type": "-",
|
||||
"size": 1851,
|
||||
@@ -719,10 +725,10 @@
|
||||
"lmtime": 0,
|
||||
"modified": true
|
||||
},
|
||||
"026_clients_bestseller_settings.sql": {
|
||||
"027_campaigns_comments.sql": {
|
||||
"type": "-",
|
||||
"size": 1613,
|
||||
"lmtime": 1772611513545,
|
||||
"size": 482,
|
||||
"lmtime": 1772671313573,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -738,8 +744,8 @@
|
||||
"campaigns": {
|
||||
"main_view.php": {
|
||||
"type": "-",
|
||||
"size": 21607,
|
||||
"lmtime": 1771717394441,
|
||||
"size": 24191,
|
||||
"lmtime": 1772672347271,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
|
||||
98
api.php
98
api.php
@@ -71,6 +71,104 @@ if ( \S::get( 'action' ) == 'domain_opr_check' )
|
||||
exit;
|
||||
}
|
||||
|
||||
// Dodawanie komentarza do kampanii przez API (z Claude Code)
|
||||
if ( \S::get( 'action' ) == 'campaign_comment_add' )
|
||||
{
|
||||
$api_key = trim( \S::get( 'api_key' ) );
|
||||
$stored_key = $mdb -> get( 'settings', 'setting_value', [ 'setting_key' => 'api_key' ] );
|
||||
|
||||
if ( !$api_key || !$stored_key || $api_key !== $stored_key )
|
||||
{
|
||||
echo json_encode( [ 'result' => 'error', 'message' => 'Invalid api_key' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$external_campaign_id = trim( \S::get( 'campaign_id' ) );
|
||||
$client_id_param = trim( \S::get( 'client_id' ) );
|
||||
$comment = trim( \S::get( 'comment' ) );
|
||||
$date = \S::get( 'date' ) ?: date( 'Y-m-d' );
|
||||
|
||||
if ( !$external_campaign_id || !$client_id_param || !$comment )
|
||||
{
|
||||
echo json_encode( [ 'result' => 'error', 'message' => 'Missing required params: campaign_id, client_id, comment' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$client_id_clean = str_replace( '-', '', $client_id_param );
|
||||
|
||||
$local_campaign = $mdb -> query(
|
||||
'SELECT c.id
|
||||
FROM campaigns c
|
||||
JOIN clients cl ON c.client_id = cl.id
|
||||
WHERE c.campaign_id = :campaign_id
|
||||
AND REPLACE( cl.google_ads_customer_id, \'-\', \'\' ) = :client_id
|
||||
LIMIT 1',
|
||||
[
|
||||
':campaign_id' => $external_campaign_id,
|
||||
':client_id' => $client_id_clean
|
||||
]
|
||||
) -> fetch( \PDO::FETCH_ASSOC );
|
||||
|
||||
if ( !$local_campaign )
|
||||
{
|
||||
echo json_encode( [ 'result' => 'error', 'message' => 'Campaign not found' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
\factory\Campaigns::add_campaign_comment( $local_campaign['id'], $comment, $date );
|
||||
|
||||
echo json_encode( [ 'result' => 'ok' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Zmiana custom_label_4 dla produktu przez API
|
||||
if ( \S::get( 'action' ) == 'product_custom_label_4_set' )
|
||||
{
|
||||
$api_key = trim( \S::get( 'api_key' ) );
|
||||
$stored_key = $mdb -> get( 'settings', 'setting_value', [ 'setting_key' => 'api_key' ] );
|
||||
|
||||
if ( !$api_key || !$stored_key || $api_key !== $stored_key )
|
||||
{
|
||||
echo json_encode( [ 'result' => 'error', 'message' => 'Invalid api_key' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$offer_id = trim( \S::get( 'offer_id' ) );
|
||||
$client_id_param = trim( \S::get( 'client_id' ) );
|
||||
$custom_label_4 = trim( \S::get( 'custom_label_4' ) );
|
||||
|
||||
if ( !$offer_id || !$client_id_param )
|
||||
{
|
||||
echo json_encode( [ 'result' => 'error', 'message' => 'Missing required params: offer_id, client_id' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$product = $mdb -> query(
|
||||
'SELECT p.id
|
||||
FROM products p
|
||||
JOIN clients cl ON p.client_id = cl.id
|
||||
WHERE p.offer_id = :offer_id
|
||||
AND cl.id = :client_id
|
||||
LIMIT 1',
|
||||
[
|
||||
':offer_id' => $offer_id,
|
||||
':client_id' => (int) $client_id_param
|
||||
]
|
||||
) -> fetch( \PDO::FETCH_ASSOC );
|
||||
|
||||
if ( !$product )
|
||||
{
|
||||
echo json_encode( [ 'result' => 'error', 'message' => 'Product not found' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
\factory\Products::set_product_data( $product['id'], 'custom_label_4', $custom_label_4 );
|
||||
\factory\Products::add_product_comment( $product['id'], 'Zmiana etykiety 4 na: ' . $custom_label_4 . ' (API)' );
|
||||
|
||||
echo json_encode( [ 'result' => 'ok' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Open Page Rank - zapis
|
||||
if ( \S::get( 'action' ) == 'domain_opr_save' )
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ class Campaigns
|
||||
echo json_encode( [
|
||||
'chart_data' => $chart_data,
|
||||
'dates' => $dates,
|
||||
'comments' => []
|
||||
'comments' => \factory\Campaigns::get_campaign_comments( $campaign_id )
|
||||
] );
|
||||
exit;
|
||||
}
|
||||
@@ -221,4 +221,38 @@ class Campaigns
|
||||
echo json_encode( [ 'success' => true, 'deleted' => $deleted ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function comment_add()
|
||||
{
|
||||
$campaign_id = (int) \S::get( 'campaign_id' );
|
||||
$comment = trim( \S::get( 'comment' ) );
|
||||
$date = \S::get( 'date' );
|
||||
|
||||
if ( !$campaign_id || !$comment )
|
||||
{
|
||||
echo json_encode( [ 'success' => false, 'message' => 'Brak wymaganych parametrów' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = \factory\Campaigns::add_campaign_comment( $campaign_id, $comment, $date ?: null );
|
||||
|
||||
echo json_encode( [ 'success' => $result ? true : false ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function comment_delete()
|
||||
{
|
||||
$comment_id = (int) \S::get( 'comment_id' );
|
||||
|
||||
if ( !$comment_id )
|
||||
{
|
||||
echo json_encode( [ 'success' => false, 'message' => 'Nie podano komentarza' ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = \factory\Campaigns::delete_campaign_comment( $comment_id );
|
||||
|
||||
echo json_encode( [ 'success' => $result ? true : false ] );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,4 +415,31 @@ class Campaigns
|
||||
$mdb -> delete( 'campaigns_history', [ 'id' => $ids ] );
|
||||
return count( $ids );
|
||||
}
|
||||
|
||||
static public function get_campaign_comments( $campaign_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> query( 'SELECT id, comment, date_add FROM campaigns_comments WHERE campaign_id = \'' . (int) $campaign_id . '\' ORDER BY date_add DESC' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
}
|
||||
|
||||
static public function add_campaign_comment( $campaign_id, $comment, $date = null )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$date )
|
||||
$date = date( 'Y-m-d' );
|
||||
else
|
||||
$date = date( 'Y-m-d', strtotime( $date ) );
|
||||
|
||||
if ( $mdb -> count( 'campaigns_comments', [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $date ] ] ) )
|
||||
return $mdb -> update( 'campaigns_comments', [ 'comment' => $comment ], [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $date ] ] );
|
||||
else
|
||||
return $mdb -> insert( 'campaigns_comments', [ 'campaign_id' => $campaign_id, 'comment' => $comment, 'date_add' => $date ] );
|
||||
}
|
||||
|
||||
static public function delete_campaign_comment( $comment_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> delete( 'campaigns_comments', [ 'id' => $comment_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
10
migrations/027_campaigns_comments.sql
Normal file
10
migrations/027_campaigns_comments.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS `campaigns_comments` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`campaign_id` INT(11) NOT NULL,
|
||||
`comment` TEXT NOT NULL,
|
||||
`date_add` DATE NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_campaign_comment_date` (`campaign_id`, `date_add`),
|
||||
KEY `idx_campaign_id` (`campaign_id`),
|
||||
CONSTRAINT `FK_campaigns_comments_campaigns` FOREIGN KEY (`campaign_id`) REFERENCES `campaigns` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -235,23 +235,25 @@ function reloadChart()
|
||||
{
|
||||
var parsedData = JSON.parse( response );
|
||||
var plotLines = [];
|
||||
var chartComments = parsedData.comments || [];
|
||||
var commentData = [];
|
||||
|
||||
chartComments.forEach( function( comment ) {
|
||||
var idx = parsedData.dates.indexOf( comment.date_add.split(' ')[0] );
|
||||
if ( idx < 0 ) return;
|
||||
|
||||
commentData.push({ idx: idx, text: comment.comment, date: comment.date_add });
|
||||
|
||||
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' }
|
||||
},
|
||||
color: '#FF6B00',
|
||||
width: 2,
|
||||
value: idx,
|
||||
dashStyle: 'Dash',
|
||||
zIndex: 5
|
||||
});
|
||||
});
|
||||
|
||||
Highcharts.chart( 'container', {
|
||||
var chart = Highcharts.chart( 'container', {
|
||||
chart: {
|
||||
style: { fontFamily: '"Roboto", sans-serif' },
|
||||
backgroundColor: 'transparent'
|
||||
@@ -296,6 +298,47 @@ function reloadChart()
|
||||
tooltip: { style: { fontSize: '13px' } },
|
||||
credits: { enabled: false }
|
||||
});
|
||||
|
||||
// Nakładamy klikalne ikonki komentarzy jako elementy HTML nad wykresem
|
||||
$( '#container .chart-comment-icons' ).remove();
|
||||
if ( chart && commentData.length ) {
|
||||
var $wrap = $( '<div class="chart-comment-icons"></div>' ).css({ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, 'pointer-events': 'none', 'z-index': 10, overflow: 'visible' });
|
||||
$( '#container' ).css({ position: 'relative', overflow: 'visible' }).append( $wrap );
|
||||
|
||||
commentData.forEach( function( c, i ) {
|
||||
var px = chart.xAxis[0].toPixels( c.idx, false );
|
||||
var $icon = $( '<i class="fa-solid fa-comment"></i>' )
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: px - 8,
|
||||
top: chart.plotTop + 5,
|
||||
color: '#FF6B00',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
'pointer-events': 'auto'
|
||||
})
|
||||
.attr( 'title', 'Komentarz \u2014 ' + c.date )
|
||||
.data( 'comment-index', i );
|
||||
$wrap.append( $icon );
|
||||
});
|
||||
|
||||
$wrap.on( 'click', 'i', function() {
|
||||
var c = commentData[ $( this ).data( 'comment-index' ) ];
|
||||
if ( !c ) return;
|
||||
|
||||
var html = $( '<span>' ).text( c.text ).html();
|
||||
html = html.replace( /\n/g, '<br>' );
|
||||
html = html.replace( /\. /g, '.<br>' );
|
||||
|
||||
$.alert({
|
||||
title: '<i class="fa-solid fa-comment" style="color:#FF6B00;"></i> Komentarz \u2014 ' + c.date,
|
||||
content: '<div style="font-size:14px;line-height:1.7;">' + html + '</div>',
|
||||
closeIcon: true,
|
||||
columnClass: 'col-md-6 col-md-offset-3',
|
||||
buttons: { ok: { text: 'Zamknij', btnClass: 'btn-blue' } }
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function( jqXHR, textStatus, errorThrown ) {
|
||||
console.error( 'Error AJAX:', textStatus, errorThrown );
|
||||
|
||||
Reference in New Issue
Block a user