feat: add search and custom label filters to products view

- Added a search input for filtering products by name or ID.
- Introduced a custom label input for filtering by CL4.
- Implemented debounce functionality for both filters to optimize performance.
- Updated local storage handling to persist filter values.
- Modified styles for new filter groups in the product layout.

chore: add .serena configuration files

- Created .serena/.gitignore to exclude cache files.
- Added .serena/project.yml for project configuration.

fix: add status column to campaign_ad_groups table

- Altered the campaign_ad_groups table to include a status column with ENUM values 'active' and 'paused'./c
This commit is contained in:
2026-02-22 11:59:20 +01:00
parent 6e6fd0110a
commit 95cfb7a495
12 changed files with 335 additions and 25 deletions

View File

@@ -31,6 +31,14 @@
</button>
</div>
</div>
<div class="filter-group filter-group-search">
<label for="products_search"><i class="fa-solid fa-magnifying-glass"></i> Szukaj</label>
<input type="text" id="products_search" class="form-control" placeholder="Nazwa, ID oferty..." />
</div>
<div class="filter-group filter-group-cl4">
<label for="products_cl4"><i class="fa-solid fa-tag"></i> CL4</label>
<input type="text" id="products_cl4" class="form-control" placeholder="np. bestseller, deleted..." />
</div>
<div class="filter-group filter-group-columns">
<label><i class="fa-solid fa-table-columns"></i> Kolumny</label>
<details class="products-columns-control">
@@ -318,6 +326,8 @@ $( function()
d.client_id = $( '#client_id' ).val() || '';
d.campaign_id = $( '#products_campaign_id' ).val() || '';
d.ad_group_id = $( '#products_ad_group_id' ).val() || '';
d.search_text = $( '#products_search' ).val() || '';
d.filter_cl4 = $( '#products_cl4' ).val() || '';
}
},
processing: true,
@@ -352,6 +362,12 @@ $( function()
{ width: '120px', orderable: false },
{ width: '190px', orderable: false, className: 'dt-center' }
],
createdRow: function( row, data ) {
var cl4Val = $( data[19] ).val();
if ( cl4Val && cl4Val.toLowerCase() === 'niedostępny' ) {
$( row ).addClass( 'product-row-unavailable' );
}
},
order: [ [ 9, 'desc' ] ],
language: {
processing: 'Ładowanie...',
@@ -375,6 +391,22 @@ $( function()
products_table.ajax.reload( null, false );
}
// Filtr: szukaj po nazwie/offer_id (debounce 400ms)
var _searchTimer = null;
$( '#products_search' ).on( 'keyup', function() {
localStorage.setItem( 'products_search', $( this ).val() || '' );
clearTimeout( _searchTimer );
_searchTimer = setTimeout( function() { reload_products_table(); }, 400 );
});
// Filtr: custom_label_4 (debounce 400ms)
var _cl4Timer = null;
$( '#products_cl4' ).on( 'keyup', function() {
localStorage.setItem( 'products_cl4', $( this ).val() || '' );
clearTimeout( _cl4Timer );
_cl4Timer = setTimeout( function() { reload_products_table(); }, 400 );
});
function submit_delete_campaign_ad_group( campaign_id, ad_group_id, delete_scope, on_success )
{
function parse_json_loose( raw )
@@ -1068,6 +1100,10 @@ $( function()
localStorage.setItem( 'products_client_id', client_id );
localStorage.removeItem( 'products_campaign_id' );
localStorage.removeItem( 'products_ad_group_id' );
localStorage.removeItem( 'products_search' );
localStorage.removeItem( 'products_cl4' );
$( '#products_search' ).val( '' );
$( '#products_cl4' ).val( '' );
update_delete_ad_group_button_state();
load_products_campaigns( client_id, '' ).done( function() {
@@ -1178,12 +1214,19 @@ $( function()
var savedClient = localStorage.getItem( 'products_client_id' ) || '';
var savedCampaign = localStorage.getItem( 'products_campaign_id' ) || '';
var savedAdGroup = localStorage.getItem( 'products_ad_group_id' ) || '';
var savedSearch = localStorage.getItem( 'products_search' ) || '';
var savedCl4 = localStorage.getItem( 'products_cl4' ) || '';
if ( savedClient && $( '#client_id option[value="' + savedClient + '"]' ).length )
{
$( '#client_id' ).val( savedClient );
}
$( '#products_search' ).val( savedSearch );
$( '#products_cl4' ).val( savedCl4 );
load_cl4_suggestions( $( '#client_id' ).val() || '' );
load_products_campaigns( $( '#client_id' ).val() || '', savedCampaign ).done( function() {
var selected_campaign_id = $( '#products_campaign_id' ).val() || '';
load_products_ad_groups( selected_campaign_id, savedAdGroup ).done( function() {
@@ -1350,6 +1393,73 @@ $( function()
});
});
// CL4 autocomplete — datalist z unikalnymi wartościami
var cl4_values_cache = [];
var cl4_datalist_id = 'cl4-suggestions';
function load_cl4_suggestions( client_id )
{
if ( !client_id )
{
cl4_values_cache = [];
return;
}
$.ajax({
url: '/products/get_distinct_cl4/client_id=' + client_id,
type: 'GET',
dataType: 'json'
}).done( function( res ) {
cl4_values_cache = ( res && res.values ) ? res.values : [];
render_cl4_datalist();
});
}
function render_cl4_datalist()
{
var $dl = $( '#' + cl4_datalist_id );
if ( !$dl.length )
{
$dl = $( '<datalist id="' + cl4_datalist_id + '"></datalist>' );
$( 'body' ).append( $dl );
}
var html = '';
for ( var i = 0; i < cl4_values_cache.length; i++ )
{
html += '<option value="' + escape_html( cl4_values_cache[i] ) + '">';
}
$dl.html( html );
}
function bind_cl4_datalist()
{
$( '.custom_label_4' ).attr( 'list', cl4_datalist_id );
}
// Podłącz datalist po każdym renderze tabeli
$( '#products' ).on( 'draw.dt', function() {
bind_cl4_datalist();
});
// Załaduj sugestie po zmianie klienta
$( 'body' ).on( 'change', '#client_id', function() {
load_cl4_suggestions( $( this ).val() || '' );
});
// Odśwież cache po zapisie CL4
function refresh_cl4_cache_after_save()
{
var client_id = $( '#client_id' ).val() || '';
if ( client_id )
{
load_cl4_suggestions( client_id );
}
}
// Zapis custom_label_4
$( 'body' ).on( 'change', '.custom_label_4', function()
{
@@ -1376,6 +1486,7 @@ $( function()
if ( data.status === 'ok' )
{
show_toast( 'Custom Label 4 zapisany.', 'success' );
refresh_cl4_cache_after_save();
}
else
{
@@ -1392,12 +1503,14 @@ $( function()
// Edycja produktu (tytuł, opis, kategoria Google)
$( 'body' ).on( 'click', '.edit-product-title', function( e )
{
var current_product_name = $.trim( $( this ).closest( '.table-product-title' ).find( 'a' ).text() );
$.confirm({
title: 'Edytuj produkt',
content: '' +
'<form action="" class="formName">' +
'<div class="form-group">' +
'<label>Tytuł produktu</label>' +
'<label>Tytuł produktu <small class="text-muted" style="font-weight:normal">— ' + escape_html( current_product_name ) + '</small></label>' +
'<div class="input-with-ai">' +
'<input type="text" value="" product_id="' + $( this ).attr( 'product_id' ) + '" placeholder="Tytuł produktu" class="name form-control" required />' +
( AI_OPENAI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest" data-field="title" data-provider="openai" title="Zaproponuj tytuł przez ChatGPT"><i class="fa-solid fa-wand-magic-sparkles"></i> GPT</button>' : '' ) +