ver. 0.277: ShopProduct factory, Dashboard, Update migration, legacy cleanup, admin\App

- ShopProduct factory: full migration (~40 ProductRepository methods, ~30 controller actions)
- Dashboard: Domain+DI migration (DashboardRepository + DashboardController)
- Update: Domain+DI migration (UpdateRepository + UpdateController, template rewrite)
- Renamed admin\Site to admin\App, removed dead fallback routing
- Removed all legacy folders: admin/controls, admin/factory, admin/view
- Newsletter: switched from admin\factory\Articles to ArticleRepository
- 414 tests, 1335 assertions passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 01:06:29 +01:00
parent 56ddd34e44
commit c8469f4371
51 changed files with 4960 additions and 5403 deletions

View File

@@ -160,7 +160,7 @@ $orderId = (int)($this -> order['id'] ?? 0);
<tbody>
<? if ( is_array( $this -> order[ 'products' ] ) ): foreach ( $this -> order[ 'products' ] as $product ):?>
<?
if ( $id = \admin\factory\ShopProduct::get_product_parent_id( $product['product_id'] ) )
if ( $id = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->getParentId( $product['product_id'] ) )
$product_id = $id;
else
$product_id = $product['product_id'];

View File

@@ -68,8 +68,8 @@
$.ajax({
type: 'POST',
cache: false,
url: '/admin/ajax.php',
data: { a: 'cookie_categories', category_id: category_id }
url: '/admin/shop_category/cookie_categories/',
data: { category_id: category_id }
});
}
});

View File

@@ -1,7 +1,7 @@
<div class="g-container" data="table:order-details">
<div class="panel panel-info panel-border top">
<div class="panel-heading">
<span class="panel-title">Kobinacje produktu: <?= $this -> product['languages'][ $this -> default_language ]['name'];?></span>
<span class="panel-title">Kombinacje produktu: <?= $this -> product['languages'][ $this -> default_language ]['name'];?></span>
</div>
<div class="panel-heading p10 pl15" id="g-menu" style="height: auto;">
<a class="btn btn btn-dark btn-sm mr5 btn-sm mr5" href="/admin/shop_product/view_list/"><i class="fa fa-reply mr5"></i>Wstecz</a>
@@ -13,19 +13,17 @@
<table class="table table-hover table-bordered table-condensed">
<thead>
<tr>
<th></th>
<th>Kombinacja</th>
<th>SKU</th>
<th>Stan magazynowy</th>
<th>Cena netto</th>
<th>Zam. SM 0</th>
<th>Zam. przy braku</th>
<th style="width: 100px;">Opcje</th>
</tr>
</thead>
<tbody>
<? if ( \S::is_array_fix( $this -> product_permutations ) ): foreach ( $this -> product_permutations as $product ):?>
<tr>
<td></td>
<tr data-combination-id="<?= $product['id'];?>">
<td>
<?
$attributes = explode( '|', $product['permutation_hash'] );
@@ -38,19 +36,19 @@
?>
</td>
<td>
<input type="text" name="sku" value="<?= $product['sku'];?>" class="form-control" style="max-width: 100px;" onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_sku_save/', data: { product_id: <?= $product['id'];?>, sku: $( this ).val() } } );">
<input type="text" value="<?= $product['sku'];?>" class="form-control combination-field" style="max-width: 100px;" data-product-id="<?= $product['id'];?>" data-field="sku">
</td>
<td>
<input type="text" name="quantity" value="<?= $product['quantity'];?>" class="form-control" style="max-width: 100px;" onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_quantity_save/', data: { product_id: <?= $product['id'];?>, quantity: $( this ).val() } } );">
<input type="text" value="<?= $product['quantity'];?>" class="form-control combination-field" style="max-width: 100px;" data-product-id="<?= $product['id'];?>" data-field="quantity">
</td>
<td>
<input type="text" name="price" value="<?= $product['price_netto'];?>" class="form-control" style="max-width: 100px;" onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_price_save/', data: { product_id: <?= $product['id'];?>, price: $( this ).val() } } );">
<input type="text" value="<?= $product['price_netto'];?>" class="form-control combination-field" style="max-width: 100px;" data-product-id="<?= $product['id'];?>" data-field="price">
</td>
<td>
<input type="checkbox" name="stock_0_buy" <? if ( $product['stock_0_buy'] ): echo 'checked="checked"'; endif;?> onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_stock_0_buy_save/', data: { product_id: <?= $product['id'];?>, stock_0_buy: $( this ).is( ':checked' ) } } );">
<input type="checkbox" class="g-checkbox combination-checkbox" data-product-id="<?= $product['id'];?>" <? if ( $product['stock_0_buy'] ): echo 'checked="checked"'; endif;?>>
</td>
<td class="text-center">
<a href="/admin/shop_product/delete_combination/combination_id=<?= $product['id'];?>&product_id=<?= $product['parent_id'];?>" class="btn btn-danger btn-delete-permutation"><i class="fa fa-trash"></i></a>
<button type="button" class="btn btn-danger btn-delete-permutation" data-combination-id="<?= $product['id'];?>"><i class="fa fa-trash"></i></button>
</td>
</tr>
<? endforeach; endif;?>
@@ -65,6 +63,7 @@
<div class="combination-attribute">
<div class="title">
<?= $attribute['languages'][ $this -> default_language ]['name'];?>
<label style="float: right; font-weight: normal; font-size: 12px; cursor: pointer;"><input type="checkbox" class="g-checkbox select-all-attr"> wszystkie</label>
</div>
<ul class="values">
<? foreach ( $attribute['values'] as $value ):?>
@@ -92,10 +91,90 @@
radioClass: 'iradio_minimal-blue'
});
// "Zaznacz wszystkie" per atrybut
$( '.select-all-attr' ).on( 'ifChanged', function()
{
var checked = $( this ).is( ':checked' );
$( this ).closest( '.combination-attribute' ).find( '.g-checkbox' ).each( function()
{
$( this ).iCheck( checked ? 'check' : 'uncheck' );
});
});
// Inline save — SKU, ilość, cena
var fieldUrlMap = {
'sku': '/admin/shop_product/product_combination_sku_save/',
'quantity': '/admin/shop_product/product_combination_quantity_save/',
'price': '/admin/shop_product/product_combination_price_save/'
};
$( 'body' ).on( 'change', '.combination-field', function()
{
var $input = $( this );
var field = $input.data( 'field' );
var data = { product_id: $input.data( 'product-id' ) };
data[ field ] = $input.val();
$.ajax({
type: 'POST',
cache: false,
url: fieldUrlMap[ field ],
data: data,
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( data ) {
$( '#overlay' ).hide();
var response = jQuery.parseJSON( data );
if ( response.status === 'ok' ) {
$input.css( 'border-color', '#1cbb8c' );
setTimeout( function() { $input.css( 'border-color', '' ); }, 1500 );
} else {
$input.css( 'border-color', '#ff3d60' );
setTimeout( function() { $input.css( 'border-color', '' ); }, 1500 );
if ( response.msg ) create_error( response.msg );
}
},
error: function() {
$( '#overlay' ).hide();
$input.css( 'border-color', '#ff3d60' );
setTimeout( function() { $input.css( 'border-color', '' ); }, 1500 );
}
});
});
// Inline save — checkbox stock_0_buy (iCheck event)
$( 'body' ).on( 'ifChanged', '.combination-checkbox', function()
{
var $cb = $( this );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_combination_stock_0_buy_save/',
data: { product_id: $cb.data( 'product-id' ), stock_0_buy: $cb.is( ':checked' ) },
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( data ) {
$( '#overlay' ).hide();
var response = jQuery.parseJSON( data );
if ( response.status !== 'ok' && response.msg ) {
create_error( response.msg );
}
},
error: function() {
$( '#overlay' ).hide();
}
});
});
// Usuwanie kombinacji — AJAX
$( 'body' ).on( 'click', '.btn-delete-permutation', function(e)
{
e.preventDefault();
var href = $( this ).attr( 'href' );
var combinationId = $( this ).data( 'combination-id' );
var $row = $( this ).closest( 'tr' );
$.alert(
{
@@ -119,14 +198,33 @@
text: 'Tak',
btnClass: 'btn-danger',
keys: ['enter'],
action: function() {
document.location.href = href;
action: function()
{
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/delete_combination_ajax/',
data: { combination_id: combinationId },
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( data ) {
$( '#overlay' ).hide();
var response = jQuery.parseJSON( data );
if ( response.status === 'ok' ) {
$row.fadeOut( 300, function() { $( this ).remove(); } );
} else {
if ( response.msg ) create_error( response.msg );
}
},
error: function() {
$( '#overlay' ).hide();
}
});
}
}
}
});
return false;
});
});

View File

@@ -0,0 +1,631 @@
<?php
$product = is_array($this->product ?? null) ? $this->product : [];
$productId = (int)($product['id'] ?? 0);
$userId = (int)($this->user['id'] ?? 0);
$imagesCount = is_array($product['images'] ?? null) ? count($product['images']) : 0;
$filesCount = is_array($product['files'] ?? null) ? count($product['files']) : 0;
$imageMaxPx = 1920;
if (isset($GLOBALS['settings']['image_px']) && (int)$GLOBALS['settings']['image_px'] > 0) {
$imageMaxPx = (int)$GLOBALS['settings']['image_px'];
}
$uploadToken = bin2hex(random_bytes(24));
if (!isset($_SESSION['upload_tokens']) || !is_array($_SESSION['upload_tokens'])) {
$_SESSION['upload_tokens'] = [];
}
$_SESSION['upload_tokens'][$uploadToken] = [
'user_id' => $userId,
'expires' => time() + 60 * 20,
];
$cookieCategories = [];
if (!empty($_COOKIE['cookie_categories'])) {
$decoded = @unserialize($_COOKIE['cookie_categories']);
if (is_array($decoded)) {
$cookieCategories = $decoded;
}
}
?>
<link type="text/css" rel="stylesheet" href="/libraries/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css" />
<link type="text/css" rel="stylesheet" href="/libraries/selectize/css/selectize.css" />
<link type="text/css" rel="stylesheet" href="/libraries/selectize/css/selectize.default.css" />
<script type="text/javascript" src="/libraries/jquery/sortable/sortable.js"></script>
<script type="text/javascript" src="/libraries/plupload/plupload.js"></script>
<script type="text/javascript" src="/libraries/plupload/plupload.html5.js"></script>
<script type="text/javascript" src="/libraries/plupload/plupload.html4.js"></script>
<script type="text/javascript" src="/libraries/plupload/jquery.plupload.queue/jquery.plupload.queue.js"></script>
<script type="text/javascript" src="/libraries/plupload/i18n/pl.js"></script>
<script type="text/javascript" src="/libraries/jquery-nested-sortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/libraries/jquery/lozad.js"></script>
<script type="text/javascript" src="/libraries/selectize/js/standalone/selectize.js"></script>
<style type="text/css">
#fg-product-edit .layout-tree-toggle {
width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
background: transparent;
padding: 0;
margin-right: 4px;
color: #666;
cursor: pointer;
text-indent: 0;
background-image: none !important;
}
#fg-product-edit .layout-tree-toggle:focus,
#fg-product-edit .layout-tree-toggle:active,
#fg-product-edit .layout-tree-toggle:focus-visible {
outline: none;
box-shadow: none;
}
#fg-product-edit li.sort-expanded > div .layout-tree-toggle i {
transform: rotate(90deg);
}
#fg-product-edit .sortable li.sort-branch > div > .layout-tree-toggle {
display: inline-flex;
float: none;
margin-right: 4px;
}
#fg-product-edit .menu_sortable .icheckbox_minimal-blue {
margin-top: 0;
margin-right: 5px;
}
#fg-product-edit .menu_sortable .g-checkbox {
position: relative;
overflow: hidden;
}
#files-list {
list-style: none;
padding: 0;
margin: 0 0 15px 0;
}
#files-list li {
display: block;
width: 100%;
margin-bottom: 6px;
cursor: move;
}
#files-list li .input-group {
width: 100%;
}
</style>
<script type="text/javascript">
var images_count = <?= (int)$imagesCount ?>;
var files_count = <?= (int)$filesCount ?>;
var product_id = <?= (int)$productId ?>;
function remove_custom_filed(el) {
confirm_delete_element(function() {
el.parent().parent().parent().remove();
});
}
$(function() {
// --- Podgląd produktu ---
$('body').on('click', '#product-preview', function() {
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/ajax_product_url/',
data: { product_id: $('#id').val() },
success: function(response) {
var data = jQuery.parseJSON(response);
var win = window.open(data.url, '_blank');
if (win) win.focus();
}
});
});
// --- Selectize: produkty powiązane ---
$('#products_related').selectize({
maxItems: 999,
plugins: ['remove_button']
});
// --- Lozad: lazy load obrazków ---
var observer = lozad();
observer.observe();
// --- Śledzenie kolejności galerii i plików (hidden inputs) ---
function ensureGalleryOrderInput() {
var $form = $('#fg-product-edit');
if (!$form.length) return null;
var $input = $form.find('input[name="gallery_order"]');
if (!$input.length) {
$input = $('<input>', { type: 'hidden', name: 'gallery_order', id: 'gallery_order' });
$form.append($input);
}
return $input;
}
function buildGalleryOrder() {
var order = [];
$('#images-list li').each(function() {
var imageId = $(this).find('a.article_image_delete').attr('image-id');
if (imageId) order.push(imageId);
});
return order.join(';');
}
function refreshGalleryOrderInput() {
var $input = ensureGalleryOrderInput();
if ($input) $input.val(buildGalleryOrder());
}
function ensureFilesOrderInput() {
var $form = $('#fg-product-edit');
if (!$form.length) return null;
var $input = $form.find('input[name="files_order"]');
if (!$input.length) {
$input = $('<input>', { type: 'hidden', name: 'files_order', id: 'files_order' });
$form.append($input);
}
return $input;
}
function buildFilesOrder() {
var order = [];
$('#files-list li').each(function() {
var fileId = $(this).find('.product_file_edit').attr('file_id');
if (fileId) order.push(fileId);
});
return order.join(';');
}
function refreshFilesOrderInput() {
var $input = ensureFilesOrderInput();
if ($input) $input.val(buildFilesOrder());
}
ensureGalleryOrderInput();
refreshGalleryOrderInput();
ensureFilesOrderInput();
refreshFilesOrderInput();
// --- Sortable: galeria zdjęć ---
var imageList = document.getElementById('images-list');
if (imageList) {
Sortable.create(imageList, {
onEnd: function() {
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/images_order_save/',
data: { product_id: product_id, order: buildGalleryOrder() },
beforeSend: function() { $('#overlay').show(); },
success: function(data) {
$('#overlay').hide();
var response = jQuery.parseJSON(data);
if (response.status !== 'ok') create_error(response.msg);
}
});
refreshGalleryOrderInput();
}
});
}
// --- Sortable: pliki ---
var filesList = document.getElementById('files-list');
if (filesList) {
Sortable.create(filesList, {
onEnd: function() {
refreshFilesOrderInput();
}
});
}
// --- Plupload: upload zdjęć ---
$('#images-uploader').pluploadQueue({
multipart_params: {
upload_token: '<?= htmlspecialchars($uploadToken, ENT_QUOTES, 'UTF-8') ?>'
},
runtimes: 'html5,html4',
init: {
Refresh: function() {
$('.plupload_buttons').css('display', 'inline');
$('.plupload_upload_status').css('display', 'inline');
$('.plupload_start').addClass('plupload_disabled').removeClass('plupload_disabled');
},
UploadComplete: function() {
$('.plupload_buttons').css('display', 'inline');
$('.plupload_upload_status').css('display', 'inline');
$('.plupload_start').addClass('plupload_disabled').removeClass('plupload_disabled');
},
FileUploaded: function(up, file, response) {
var data = jQuery.parseJSON(response.response);
$('#images-list').append(
'<li id="image-' + data.image_id + '">' +
'<img class="article-image lozad" data-src="/libraries/thumb.php?img=' + data.data_link + '&w=300&h=300">' +
'<a href="#" class="input-group-addon btn btn-danger article_image_delete" image-id="' + data.image_id + '">' +
'<i class="fa fa-trash"></i>' +
'</a>' +
'<input type="text" class="form-control image-alt" value="" image-id="' + data.image_id + '" placeholder="atrybut alt...">' +
'</li>'
);
images_count++;
observer.observe();
refreshGalleryOrderInput();
$('html, body').animate({ scrollTop: $('#images-uploader').offset().top }, 1);
}
},
url: '/libraries/plupload/upload-product-images.php',
chunk_size: '1mb',
max_file_size: '20mb',
unique_names: false,
resize: {
width: <?= (int)$imageMaxPx ?>,
height: <?= (int)$imageMaxPx ?>,
quality: 95
},
filters: [{ title: 'Obrazki', extensions: 'jpg,gif,png,bmp,jpeg' }]
});
// --- Plupload: upload plików ---
$('#files-uploader').pluploadQueue({
multipart_params: {
upload_token: '<?= htmlspecialchars($uploadToken, ENT_QUOTES, 'UTF-8') ?>'
},
runtimes: 'html5,html4',
init: {
Refresh: function() {
$('.plupload_buttons').css('display', 'inline');
$('.plupload_upload_status').css('display', 'inline');
$('.plupload_start').addClass('plupload_disabled').removeClass('plupload_disabled');
},
FileUploaded: function(up, file, response) {
var data = jQuery.parseJSON(response.response);
$('#files-list').append(
'<li id="file-' + data.file_id + '">' +
'<div class="input-group">' +
'<input type="text" class="form-control product_file_edit" file_id="' + data.file_id + '" value="' + data.file_name + '" />' +
'<a href="#" class="input-group-addon btn btn-info product_file_delete" file_id="' + data.file_id + '">' +
'<i class="fa fa-trash"></i>' +
'</a>' +
'</div>' +
'</li>'
);
files_count++;
refreshFilesOrderInput();
}
},
url: '/libraries/plupload/upload-product-files.php',
chunk_size: '1mb',
max_file_size: '50mb',
unique_names: false,
filters: [{ title: 'Wszystkie pliki', extensions: '*' }]
});
// --- Drzewo kategorii: ukryj strzałki na liściach ---
function refreshTreeDisclosureState() {
$('ol.sortable li').each(function() {
var $li = $(this);
var hasChildren = $li.children('ol').children('li').length > 0;
var $toggle = $li.children('div').children('.disclose');
if (hasChildren) {
$li.removeClass('sort-leaf').addClass('sort-branch');
if (!$li.hasClass('sort-collapsed') && !$li.hasClass('sort-expanded')) {
$li.addClass('sort-collapsed');
}
$toggle.attr('aria-expanded', $li.hasClass('sort-expanded') ? 'true' : 'false').show();
} else {
$li.removeClass('sort-branch sort-collapsed sort-expanded').addClass('sort-leaf');
$toggle.attr('aria-expanded', 'false').hide();
}
});
}
// --- Drzewo kategorii: nestedSortable + iCheck ---
if ($.fn && typeof $.fn.iCheck === 'function') {
$('#fg-product-edit .menu_sortable .g-checkbox').iCheck({
checkboxClass: 'icheckbox_minimal-blue',
radioClass: 'iradio_minimal-blue'
});
}
$('ol.sortable').nestedSortable({
forcePlaceholderSize: true,
handle: 'div',
helper: 'clone',
items: 'li',
opacity: .9,
placeholder: 'placeholder',
revert: 250,
tabSize: 45,
tolerance: 'pointer',
toleranceElement: '> div',
maxLevels: 2,
isTree: true,
expandOnHover: 700,
protectRoot: false
});
$('.disclose').on('click', function() {
var $li = $(this).closest('li');
$li.toggleClass('sort-collapsed').toggleClass('sort-expanded');
$(this).attr('aria-expanded', $li.hasClass('sort-expanded') ? 'true' : 'false');
});
$('.disclose').mousedown(function(e) {
if (e.which === 1) {
var category_id = $(this).parent('div').parent('li').attr('id');
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_category/cookie_categories/',
data: { category_id: category_id }
});
}
});
refreshTreeDisclosureState();
<?php foreach ($cookieCategories as $key => $val): ?>
<?php if ($val): ?>$('#<?= $key ?>').children('div').children('.disclose').click();<?php endif; ?>
<?php endforeach; ?>
// --- AJAX: zmiana alt zdjęcia ---
$('body').on('change', '.image-alt', function() {
var $input = $(this);
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/image_alt_change/',
data: { image_id: $input.attr('image-id'), image_alt: $input.val() },
beforeSend: function() { $('#overlay').show(); },
success: function(data) {
$('#overlay').hide();
var response = jQuery.parseJSON(data);
if (response.status !== 'ok') create_error(response.msg);
}
});
});
// --- AJAX: zmiana nazwy pliku ---
$('body').on('change', '.product_file_edit', function() {
var $input = $(this);
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_file_name_change/',
data: { file_id: $input.attr('file_id'), file_name: $input.val() },
beforeSend: function() { $('#overlay').show(); },
success: function(data) {
$('#overlay').hide();
var response = jQuery.parseJSON(data);
if (response.status !== 'ok') create_error(response.msg);
}
});
});
// --- AJAX: usunięcie pliku ---
$('body').on('click', '.product_file_delete', function() {
$(this).blur();
var file_id = $(this).attr('file_id');
$.alert({
title: 'Pytanie',
content: 'Na pewno chcesz usunąć wybrany plik?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-question',
buttons: {
cancel: { text: 'Nie', btnClass: 'btn-dark', action: function() {} },
confirm: {
text: 'Tak', btnClass: 'btn-danger', keys: ['enter'],
action: function() {
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_file_delete/',
data: { file_id: file_id },
beforeSend: function() {
$('#file-' + file_id).find('input').addClass('disabled');
$('#file-' + file_id).find('a').addClass('disabled');
},
success: function(data) {
var response = jQuery.parseJSON(data);
if (response.status === 'ok') {
$('#file-' + file_id).remove();
refreshFilesOrderInput();
} else {
create_error(response.msg);
}
}
});
}
}
}
});
return false;
});
// --- AJAX: usunięcie zdjęcia ---
$('body').on('click', '.article_image_delete', function() {
$(this).blur();
var image_id = $(this).attr('image-id');
$.alert({
title: 'Pytanie',
content: 'Na pewno chcesz usunąć wybrane zdjęcie?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-question',
buttons: {
cancel: { text: 'Nie', btnClass: 'btn-dark', action: function() {} },
confirm: {
text: 'Tak', btnClass: 'btn-danger', keys: ['enter'],
action: function() {
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/image_delete/',
data: { image_id: image_id },
beforeSend: function() { $('#overlay').show(); },
success: function(data) {
$('#overlay').hide();
var response = jQuery.parseJSON(data);
if (response.status === 'ok') {
$('#image-' + image_id).remove();
refreshGalleryOrderInput();
} else {
create_error(response.msg);
}
}
});
}
}
}
});
return false;
});
// --- Odśwież kolejność galerii/plików przed zapisem ---
$('body').on('click', '#g-edit-save, #g-edit-save-close', function() {
refreshGalleryOrderInput();
refreshFilesOrderInput();
});
// --- Kalkulator cen netto/brutto ---
$('body').on('keyup', '#price_netto', function() { calculate_price_brutto(); });
$('body').on('keyup', '#price_brutto', function() { calculate_price_netto(); });
$('body').on('keyup', '#price_netto_promo', function() { calculate_price_brutto_promo(); });
$('body').on('keyup', '#price_brutto_promo', function() { calculate_price_netto_promo(); });
$('body').on('change', '#vat', function() { calculate_price_brutto(); });
// --- Dodawanie custom field ---
$('body').on('click', '#add_custom_field', function(e) {
e.preventDefault();
var html = '';
html += '<div class="form-group row custom-field-row bg-white p-4">';
html += '<div class="form-group row"><label class="col-sm-3 control-label">Nazwa pola:</label>';
html += '<div class="col-sm-9"><input type="text" class="form-control" name="custom_field_name[]" value=""></div></div>';
html += '<div class="form-group row"><label class="col-sm-3 control-label">Rodzaj pola:</label>';
html += '<div class="col-sm-9"><select class="form-control" name="custom_field_type[]">';
html += '<option value="text" selected>Tekst</option>';
html += '<option value="image">Obrazek</option>';
html += '</select></div></div>';
html += '<div class="form-group row"><label class="col-sm-3 control-label">Status pola:</label>';
html += '<div class="col-sm-9"><label style="margin:0; font-weight:normal;" class="d-flex align-items-center mt-3">';
html += '<input type="checkbox" class="custom-field-required" name="custom_field_required[]"> wymagane</label></div></div>';
html += '<div class="form-group row"><div class="col-sm-12 text-right">';
html += '<span class="input-group-addon btn btn-info" onclick="remove_custom_filed( $( this ) );">usuń</span>';
html += '</div></div>';
html += '</div>';
$('.additional_fields').append(html);
});
});
// --- Funkcje kalkulacji cen ---
function calculate_price_brutto() {
var price_netto = $('#price_netto').val().replace(',', '.');
if (!price_netto) return false;
var vat = $('#vat').val().replace(',', '.');
var price_brutto = price_netto * 1 + price_netto * vat / 100;
price_brutto = Math.floor(price_brutto * 100) / 100;
price_brutto = number_format(price_brutto, 2, '.', '');
return $('#price_brutto').val(price_brutto);
}
function calculate_price_netto() {
var price_brutto = $('#price_brutto').val().replace(',', '.');
var vat = $('#vat').val().replace(',', '.');
var price_netto = price_brutto / (vat / 100 + 1);
price_netto = number_format(price_netto, 2, '.', '');
return $('#price_netto').val(price_netto);
}
function calculate_price_brutto_promo() {
var price_netto = $('#price_netto_promo').val().replace(',', '.');
var vat = $('#vat').val().replace(',', '.');
var price_brutto = price_netto * 1 + price_netto * vat / 100;
price_brutto = Math.floor(price_brutto * 100) / 100;
price_brutto = number_format(price_brutto, 2, '.', '');
if (Math.floor(price_netto) <= 0) {
$('#price_netto_promo').val('');
$('#price_brutto_promo').val('');
return true;
}
return $('#price_brutto_promo').val(price_brutto);
}
function calculate_price_netto_promo() {
var price_brutto = $('#price_brutto_promo').val().replace(',', '.');
var vat = $('#vat').val().replace(',', '.');
var price_netto = price_brutto / (vat / 100 + 1);
price_netto = number_format(price_netto, 2, '.', '');
if (Math.floor(price_brutto) <= 0) {
$('#price_netto_promo').val('');
$('#price_brutto_promo').val('');
return true;
}
return $('#price_netto_promo').val(price_netto);
}
// --- Generowanie SKU ---
function generate_sku_code(product_id) {
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/generate_sku_code/',
data: { product_id: product_id },
beforeSend: function() { $('#sku').addClass('disabled'); },
success: function(data) {
$('#sku').removeClass('disabled');
var response = jQuery.parseJSON(data);
if (response.status === 'ok') {
$('#sku').val(response.sku);
} else {
create_error(response.msg);
}
}
});
}
// --- Generowanie linku SEO ---
function generate_seo_links(lang, title, article_id) {
if (title === '') return false;
$.ajax({
type: 'POST',
cache: false,
url: '/admin/pages/generateSeoLink/',
data: { title: title, article_id: article_id },
beforeSend: function() {
$('#seo_link_' + lang).parents('.g-form-data').find('input, a').each(function() {
$(this).prop('disabled', true).addClass('disabled');
});
},
success: function(data) {
$('#seo_link_' + lang).parents('.g-form-data').find('input, a').each(function() {
$(this).prop('disabled', false).removeClass('disabled');
});
var response = jQuery.parseJSON(data);
if (response.status === 'ok') {
$('#seo_link_' + lang).val(response.seo_link);
} else {
create_error(response.msg);
}
}
});
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,302 @@
<? if ( $this -> shoppro_enabled ):?>
<script type="text/javascript">
$(function() {
var $header = $( '.panel-heading .col-sm-8' );
if ( $header.length ) {
$header.append( ' <a href="#" class="btn btn-danger btn-sm btn-shoppro-product-import"><i class="fa fa-download mr5"></i>Pobierz produkt z shopPRO</a>' );
}
});
</script>
<? endif;?>
<style type="text/css">
.product-image {
display: inline-block;
vertical-align: top;
width: 50px;
margin-right: 8px;
}
.product-image img {
max-width: 50px;
max-height: 50px;
}
.product-name {
display: inline-block;
vertical-align: top;
}
.product-categories {
display: block;
}
</style>
<script type="text/javascript">
$(function() {
// --- Inline price save ---
$( 'body' ).on( 'change', '.product-price', function() {
var $el = $( this );
var price = $el.val().replace( ' ', '' );
price = parseFloat( price.replace( ',', '.' ) * 1 );
price = number_format( price, 2, '.', '' );
$el.val( price );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_change_price_brutto/',
data: { product_id: $el.attr( 'product-id' ), price: price },
beforeSend: function() { $( '#overlay' ).show(); },
success: function( data ) {
$( '#overlay' ).hide();
var response = jQuery.parseJSON( data );
if ( response.status !== 'ok' ) create_error( response.msg );
}
});
});
$( 'body' ).on( 'change', '.product-price-promo', function() {
var $el = $( this );
var price = $el.val().replace( ' ', '' );
price = parseFloat( price.replace( ',', '.' ) * 1 );
price = number_format( price, 2, '.', '' );
$el.val( price );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_change_price_brutto_promo/',
data: { product_id: $el.attr( 'product-id' ), price: price },
beforeSend: function() { $( '#overlay' ).show(); },
success: function( data ) {
$( '#overlay' ).hide();
var response = jQuery.parseJSON( data );
if ( response.status !== 'ok' ) create_error( response.msg );
}
});
});
// --- Duplicate product ---
$( 'body' ).on( 'click', '.duplicate-product', function(e) {
e.preventDefault();
var product_id = $( this ).attr( 'product-id' );
$.alert({
title: 'Pytanie',
content: 'Na pewno chcesz wykonać duplikat produktu?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-question',
buttons: {
confirm: {
text: 'Tak (produkt bez kombinacji)',
btnClass: 'btn-success',
keys: ['enter'],
action: function() {
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id;
}
},
confirm2: {
text: 'Tak (produkt z KOMBINACJAMI)',
btnClass: 'btn-primary',
action: function() {
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id + '&combination=1';
}
},
cancel: {
text: 'Nie',
btnClass: 'btn-dark',
action: function() {}
}
}
});
});
// --- shopPRO import ---
$( 'body' ).on( 'click', '.btn-shoppro-product-import', function(e) {
e.preventDefault();
$.alert({
title: 'Import produktu',
content: 'Wprowadź ID produktu z shopPRO',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-8',
theme: 'material',
icon: 'fa fa-exclamation-triangle',
buttons: {
confirm: {
text: 'Importuj',
btnClass: 'btn-success',
keys: ['enter'],
action: function() {
var product_id = $( '#shoppro-product-id' ).val();
if ( product_id ) {
document.location.href = '/admin/integrations/shoppro_product_import/product_id=' + product_id;
}
}
},
cancel: {
text: 'Anuluj',
btnClass: 'btn-danger',
action: function() {}
}
},
onOpenBefore: function() {
this.setContent( '<input type="text" class="form-control" id="shoppro-product-id" placeholder="ID produktu z shopPRO">' );
}
});
});
// --- Apilo ---
$( 'body' ).on( 'click', '.apilo-product-search', function() {
var product_id = $( this ).attr( 'product-id' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/integrations/apilo_product_search/',
data: { product_id: product_id },
beforeSend: function() { $( '#overlay' ).show(); },
success: function( response ) {
$( '#overlay' ).hide();
var data = jQuery.parseJSON( response );
if ( data.status == 'SUCCESS' ) {
if ( data.products.length == 0 ) {
var html = '<div class="apilo-found-products">';
html += '<p>Nie znaleziono produktów</p>';
html += '<a href="/admin/integrations/apilo_create_product/product-id=' + product_id + '" class="btn btn-success btn_apilo_create_product" product_id="' + product_id + '">Utwórz produkt</a> ';
html += '<button class="btn btn-default apilo-cancel">Anuluj</button>';
html += '</div>';
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
} else {
var html = '<div class="apilo-found-products">';
html += '<p>Znaleziono ' + data.products.length + ' produktów</p>';
html += '<select class="form-control apilo-product-select" product-id="' + product_id + '">';
$.each( data.products, function( index, value ) {
html += '<option value="' + value.id + '">' + value.name + ' SKU: ' + value.sku + '</option>';
});
html += '</select>';
html += '<button class="btn btn-success apilo-product-select-save" product-id="' + product_id + '">Zapisz</button> ';
html += '<button class="btn btn-default apilo-cancel">Anuluj</button>';
html += '</div>';
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
}
} else if ( data.status == 'error' ) {
$.alert({
title: 'Błąd',
content: data.msg,
type: 'red',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-exclamation-triangle',
buttons: {
confirm: {
text: 'OK',
btnClass: 'btn-danger',
keys: ['enter'],
action: function() {}
}
}
});
}
}
});
});
$( 'body' ).on( 'click', '.apilo-cancel', function() {
$( this ).closest( '.apilo-found-products' ).remove();
});
$( 'body' ).on( 'click', '.apilo-delete-linking', function() {
var product_id = $( this ).attr( 'product-id' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/integrations/apilo_product_select_delete/',
data: { product_id: product_id },
beforeSend: function() { $( '#overlay' ).show(); },
success: function( response ) {
$( '#overlay' ).hide();
var data = jQuery.parseJSON( response );
if ( data.status == 'ok' ) {
$( 'span.apilo-delete-linking[product-id="' + product_id + '"]' ).closest( 'td' ).html( '<span class="text-danger apilo-product-search" product-id="' + product_id + '">nie przypisano <i class="fa fa-search"></i></span>' );
}
}
});
});
$( 'body' ).on( 'click', '.btn_apilo_create_product', function(e) {
e.preventDefault();
var product_id = $( this ).attr( 'product_id' );
$.alert({
title: 'Pytanie',
content: 'Na pewno chcesz utworzyć produkt w bazie Apilo?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-question',
buttons: {
confirm: {
text: 'Tak',
btnClass: 'btn-success',
keys: ['enter'],
action: function() {
document.location.href = '/admin/integrations/apilo_create_product/product_id=' + product_id;
}
},
cancel: {
text: 'Nie',
btnClass: 'btn-danger',
action: function() {}
}
}
});
});
$( 'body' ).on( 'click', '.apilo-product-select-save', function() {
var product_id = $( this ).attr( 'product-id' );
var apilo_product_id = $( '.apilo-product-select[product-id="' + product_id + '"]' ).val();
var apilo_product_name = $( '.apilo-product-select[product-id="' + product_id + '"] option:selected' ).text();
$.ajax({
type: 'POST',
cache: false,
url: '/admin/integrations/apilo_product_select_save/',
data: {
product_id: product_id,
apilo_product_id: apilo_product_id,
apilo_product_name: apilo_product_name
},
beforeSend: function() { $( '#overlay' ).show(); },
success: function( response ) {
$( '#overlay' ).hide();
var data = jQuery.parseJSON( response );
if ( data.status == 'ok' ) {
$( '.apilo-product-select[product-id="' + product_id + '"]' ).closest( '.apilo-found-products' ).remove();
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).html( '<span title="' + apilo_product_name + '">' + apilo_product_name.substr( 0, 25 ) + '...</span>' ).removeClass( 'apilo-product-search' ).removeClass( 'text-danger' );
}
}
});
});
});
</script>

View File

@@ -1,101 +0,0 @@
<? $i = ( $this -> current_page - 1 ) * 10 + 1;?>
<? foreach ( $this -> products as $product ):?>
<tr>
<td>
<?= $i++;?>
</td>
<td>
<div class="product-image">
<? if ( $product['images'][0]['src'] ):?>
<img src="<?= $product['images'][0]['src'];?>" alt="<?= $product['images'][0]['alt'];?>" class="img-responsive">
<? else:?>
<img src="/admin/layout/images/no-image.png" alt="Brak zdjęcia" class="img-responsive">
<? endif;?>
</div>
<div class="product-name">
<a href="/admin/shop_product/product_edit/id=<?= $product['id'];?>">
<?= $product['languages']['pl']['name'];?>
</a>
<a href="#" class="text-muted duplicate-product" product-id="<?= $product['id'];?>">duplikuj</a>
</div>
<small class="text-muted product-categories"><?= \admin\factory\ShopProduct::product_categories( $product['id'] );?></small>
<small class="text-muted product-categories">SKU: <?= $product['sku'];?>, EAN: <?= $product['ean'];?></small>
</td>
<td class="text-center">
<input type="text" class="product-price form-control text-right" product-id="<?= $product['id'];?>" value="<?= $product['price_brutto'];?>" style="width: 75px;">
</td>
<td class="text-center">
<input type="text" class="product-price-promo form-control text-right" product-id="<?= $product['id'];?>" value="<?= $product['price_brutto_promo'];?>" style="width: 75px;">
</td>
<td class="text-center">
<?= $product['promoted'] ? '<span class="text-success text-bold">tak</span>' : 'nie';?>
</td>
<td class="text-center">
<?= $product['status'] ? 'tak' : '<span class="text-danger text-bold">nie</span>';?>
</td>
<td class="text-center">
<span class="text-muted"><?= (int)\admin\factory\shopProduct::get_product_quantity_list( $product['id'] );?></span>
</td>
<? if ( $this -> apilo_enabled ):?>
<td class="text-center">
<?
if ( $product['apilo_product_name'] != "" ) {
echo "<span title='" . htmlspecialchars( $product['apilo_product_name'] ) . "'>" . mb_substr( $product['apilo_product_name'], 0, 25, "UTF-8" ) . "...</span>";
echo "<br>";
echo "<span class='text-danger apilo-delete-linking' product-id='" . $product['id'] . "'>";
echo "<i class='fa fa-times'></i>usuń powiązanie";
echo "</span>";
} else {
echo "<span class='text-danger apilo-product-search' product-id='" . $product['id'] . "'>";
echo "nie przypisano <i class='fa fa-search'></i>";
echo "</span>";
}
?>
</td>
<? endif;?>
<td>
<a href='/admin/shop_product/product_combination/product_id=<?= $product['id'];?>'>kombinacje (<?= \admin\factory\shopProduct::count_product_combinations( $product['id'] );?>)</a>
</td>
<td class="text-center">
<a href='/admin/shop_product/product_edit/id=<?= $product['id'];?>'>edytuj</a>
</td>
<td class="text-center">
<a href='/admin/shop_product/product_archive/product_id=<?= $product['id'];?>' class="product-delete">usuń</a>
</td>
</tr>
<? if ( $this -> show_xml_data ):?>
<tr>
<td colspan="12">
<div class="product-xml-data">
<!-- input nazwa produktu XML -->
<input type="text" class="form-control product_xml_name" value="<?= $product['languages']['pl']['xml_name'];?>" placeholder="Nazwa produktu XML (PL)" product-id="<?= $product['id'];?>" lang-id="pl">
<!-- input custom_label_0 -->
<div class="custom_label_0_container">
<input type="text" class="form-control custom_label_0" value="<?= $product['custom_label_0'];?>" placeholder="custom_label_0" product-id="<?= $product['id'];?>" label-type="custom_label_0">
<div class="custom_label_0_suggestions" label-type="custom_label_0"></div>
</div>
<!-- input custom_label_1 -->
<div class="custom_label_1_container">
<input type="text" class="form-control custom_label_1" value="<?= $product['custom_label_1'];?>" placeholder="custom_label_1" product-id="<?= $product['id'];?>" label-type="custom_label_1">
<div class="custom_label_1_suggestions" label-type="custom_label_1"></div>
</div>
<!-- input custom_label_2 -->
<div class="custom_label_2_container">
<input type="text" class="form-control custom_label_2" value="<?= $product['custom_label_2'];?>" placeholder="custom_label_2" product-id="<?= $product['id'];?>" label-type="custom_label_2">
<div class="custom_label_2_suggestions" label-type="custom_label_2"></div>
</div>
<!-- input custom_label_3 -->
<div class="custom_label_3_container">
<input type="text" class="form-control custom_label_3" value="<?= $product['custom_label_3'];?>" placeholder="custom_label_3" product-id="<?= $product['id'];?>" label-type="custom_label_3">
<div class="custom_label_3_suggestions" label-type="custom_label_3"></div>
</div>
<!-- input custom_label_4 -->
<div class="custom_label_4_container">
<input type="text" class="form-control custom_label_4" value="<?= $product['custom_label_4'];?>" placeholder="custom_label_4" product-id="<?= $product['id'];?>" label-type="custom_label_4">
<div class="custom_label_4_suggestions" label-type="custom_label_4"></div>
</div>
</div>
</td>
</tr>
<? endif;?>
<? endforeach;?>

View File

@@ -1,603 +1,9 @@
<div class="panel">
<div class="panel-body">
<a href="/admin/shop_product/product_edit/" class="btn btn-success">Dodaj produkt</a>
<!-- przycisk pokaż dane z XML -->
<? if ( !$this -> show_xml_data ):?>
<a href="/admin/shop_product/view_list/show_xml_data=true" class="btn btn-dark">Pokaż dane z XML</a>
<? else:?>
<a href="/admin/shop_product/view_list/show_xml_data=false" class="btn btn-danger">Ukryj dane z XML</a>
<? endif;?>
<? if ( $this -> shoppro_enabled ):?>
<a href="#" class="btn btn-danger btn-shoppro-product-import">Pobierz produkt z shopPRO</a>
<? endif;?>
</div>
<div class="panel-body pn">
<div class="table-responsive">
<table class="table table-bordered table-hover table-striped mbn" id="table-products">
<thead>
<tr>
<th style="width: 10px;">#</th>
<th>Nazwa</th>
<th class="text-center" style="width: 100px;">Cena</th>
<th class="text-center" style="width: 100px;">Cena promocyjna</th>
<th class="text-center" style="width: 25px;">Promowany</th>
<th class="text-center" style="width: 25px;">Aktywny</th>
<th class="text-center" style="width: 75px;">Stan MG</th>
<? if ( $this -> apilo_enabled ):?>
<th class="text-center" style="width: 100px;">Apilo</th>
<? endif;?>
<th class="text-center" style="width: 100px;">Kombinacje</th>
<th class="text-center" style="width: 75px;">Edytuj</th>
<th class="text-center" style="width: 75px;">Archiwizuj</th>
</tr>
<tr>
<th></th>
<th>
<input type="text" class="form-control table-search" field_name="name|ean|sku" placeholder="szukaj..." value="<?= htmlspecialchars( $this -> query_array['name|ean|sku'], ENT_QUOTES, 'UTF-8');?>">
</th>
<th colspan="10"></th>
</tr>
</thead>
<tbody>
<?= \Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
</tbody>
<tfoot>
<tr>
<td colspan="12">
<ul class="pagination" pagination_max="<?= $this -> pagination_max;?>">
<li>
<a href="#" page="1" title="pierwsza strona">
<i class="fa fa-angle-double-left"></i>
</a>
</li>
<li>
<a href="#" class="previous" page="<?= ( $this -> current_page - 1 > 1 ) ? ( $this -> current_page - 1 ) : 1;?>" title="poprzednia strona">
<i class="fa fa-angle-left" title="poprzednia strona"></i>
</a>
</li>
<li>
<div>
Strona <input type="number" id="current-page" value="<?= $this -> current_page;?>"> z <span id="max_page"><?= $this -> pagination_max;?></span>
</div>
</li>
<li>
<a href="#" class="next" page="<?= ( $this -> current_page + 1 < $this -> pagination_max ) ? ( $this -> current_page + 1 ) : $this -> pagination_max;?>" title="następna strona">
<i class="fa fa-angle-right"></i>
</a>
</li>
<li>
<a href="#" class="last" page="<?= $this -> pagination_max;?>" title="ostatnia strona">
<i class="fa fa-angle-double-right"></i>
</a>
</li>
</ul>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<script type="text/javascript">
$( function() {
ajax_load_products( <?= $this -> current_page;?>, null );
$( 'body' ).on( 'change', '.table-search', function() {
ajax_load_products( 1 );
});
$( 'body' ).on( 'change', '.pagination input[type="number"]', function() {
var current_page = $( this ).val();
var pagination_max = parseInt( $( '.pagination' ).attr( 'pagination_max' ) );
if ( current_page > pagination_max ) {
current_page = pagination_max;
$( this ).val( current_page );
}
if ( current_page < 1 ) {
current_page = 1;
$( this ).val( current_page );
}
ajax_load_products( current_page );
});
$( 'body' ).on( 'click', '.pagination a', function() {
var current_page = $( this ).attr( 'page' );
ajax_load_products( current_page );
});
$( 'body' ).on( 'click', '.btn-shoppro-product-import', function(e)
{
e.preventDefault();
// show alert with form with product_id field
$.alert({
title: 'Import produktu',
content: 'Wprowadź ID produktu z shopPRO',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-8',
theme: 'material',
icon: 'fa fa-exclamation-triangle',
buttons: {
confirm: {
text: 'Importuj',
btnClass: 'btn-success',
keys: ['enter'],
action: function() {
var product_id = $( '#shoppro-product-id' ).val();
if ( product_id )
{
document.location.href = '/admin/integrations/shoppro_product_import/product_id=' + product_id;
}
}
},
cancel: {
text: 'Anuluj',
btnClass: 'btn-danger',
action: function() {}
}
},
onOpenBefore: function() {
this.setContent( '<input type="text" class="form-control" id="shoppro-product-id" placeholder="ID produktu z shopPRO">' );
}
});
});
$( 'body' ).on( 'click', '.product-delete', function(e) {
e.preventDefault();
var href = $( this ).attr( 'href' );
$.alert({
title: 'Pytanie',
content: 'Na pewno chcesz przenieść wybrany produkt do archiwum?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'supervan',
icon: 'fa fa-question',
buttons: {
cancel: {
text: 'Nie',
btnClass: 'btn-success',
action: function() {}
},
confirm: {
text: 'Tak',
btnClass: 'btn-danger',
keys: ['enter'],
action: function() {
document.location.href = href;
}
}
}
});
return false;
});
$( 'body' ).on( 'change', '.product-price-promo', function(e)
{
var price = $( this ).val();
price = price.replace( ' ', '' );
price = parseFloat( price.replace( ',', '.' ) * 1 );
price = number_format( price, 2, '.', '' );
$( this ).val( price );
var product_id = $( this ).attr( 'product-id' );
$.ajax(
{
type: 'POST',
cache: false,
url: '/admin/shop_product/product_change_price_brutto_promo/',
data:
{
product_id: product_id,
price: price
},
beforeSend: function()
{
$( '#overlay' ).show();
},
success: function( data )
{
$( '#overlay' ).hide();
response = jQuery.parseJSON( data );
if ( response.status !== 'ok' )
create_error( response.msg );
}
});
});
$( 'body' ).on( 'change', '.product-price', function(e)
{
var price = $( this ).val();
price = price.replace( ' ', '' );
price = parseFloat( price.replace( ',', '.' ) * 1 );
price = number_format( price, 2, '.', '' );
$( this ).val( price );
var product_id = $( this ).attr( 'product-id' );
$.ajax(
{
type: 'POST',
cache: false,
url: '/admin/shop_product/product_change_price_brutto/',
data:
{
product_id: product_id,
price: price
},
beforeSend: function()
{
$( '#overlay' ).show();
},
success: function( data )
{
$( '#overlay' ).hide();
response = jQuery.parseJSON( data );
if ( response.status !== 'ok' )
create_error( response.msg );
}
});
});
$( 'body' ).on( 'click', '.duplicate-product', function(e)
{
e.preventDefault();
var product_id = $( this ).attr( 'product-id' );
$.alert({
title: 'Pytanie',
content: 'Na pewno chcesz wykonać duplikat produktu?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-question',
buttons: {
confirm: {
text: 'Tak (produkt bez kombinacji)',
btnClass: 'btn-success',
keys: ['enter'],
action: function() {
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id;
}
},
confirm2: {
text: 'Tak (produkt z KOMBINACJAMI)',
btnClass: 'btn-primary',
keys: ['enter'],
action: function() {
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id + '&combination=1';
}
},
cancel: {
text: 'Nie',
btnClass: 'btn-dark',
action: function() {}
}
}
});
})
// apilo product search
$( 'body' ).on( 'click', '.apilo-product-search', function() {
var product_id = $( this ).attr( 'product-id' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/integrations/apilo_product_search/',
data: {
product_id: product_id
},
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( response ) {
data = jQuery.parseJSON( response );
if ( data.status == 'SUCCESS' )
{
if ( data.products.length == 0 )
{
var html = '<div class="apilo-found-products">';
html += '<p>Nie znaleziono produktów</p>';
html += '<a href="/admin/integrations/apilo_create_product/product-id=' + product_id + '" class="btn btn-success btn_apilo_create_product" product_id="' + product_id + '">Utwórz produkt</a>';
html += '</div>';
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
}
else
{
var html = '<div class="apilo-found-products">';
html += '<p>Znaleziono ' + data.products.length + ' produktów</p>';
html += '<select class="form-control apilo-product-select" product-id="' + product_id + '">';
$.each( data.products, function( index, value ) {
html += '<option value="' + value.id + '">' + value.name + ' SKU: ' + value.sku + '</option>';
});
html += '</select>';
html += '<button class="btn btn-success apilo-product-select-save" product-id="' + product_id + '">Zapisz</button>';
html += '</div>';
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
}
}
else if ( data.status == 'error' )
{
$.alert({
title: 'Błąd',
content: data.msg,
type: 'red',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-exclamation-triangle',
buttons: {
confirm: {
text: 'OK',
btnClass: 'btn-danger',
keys: ['enter'],
action: function() {}
}
}
});
}
}
});
});
// delete apilo product linking
$( 'body' ).on( 'click', '.apilo-delete-linking', function() {
var product_id = $( this ).attr( 'product-id' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/integrations/apilo_product_select_delete/',
data: {
product_id: product_id
},
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( response ) {
data = jQuery.parseJSON( response );
if ( data.status == 'ok' ) {
$( 'span.apilo-delete-linking[product-id="' + product_id + '"]' ).closest('td').html( '<span class="text-danger apilo-product-search" product-id="' + product_id + '">nie przypisano <i class="fa fa-search"></i></span>' );
}
}
});
});
// apilo product create button click with alert confirmation
$( 'body' ).on( 'click', '.btn_apilo_create_product', function(e)
{
e.preventDefault();
var product_id = $( this ).attr( 'product_id' );
$.alert(
{
title: 'Pytanie',
content: 'Na pewno chcesz utworzyć produkt w bazie Apilo?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-times',
typeAnimated: true,
animation: 'opacity',
columnClass: 'col-12 col-lg-10',
theme: 'modern',
icon: 'fa fa-question',
buttons:
{
confirm:
{
text: 'Tak',
btnClass: 'btn-success',
keys: ['enter'],
action: function()
{
document.location.href = '/admin/integrations/apilo_create_product/product_id=' + product_id;
}
},
cancel:
{
text: 'Nie',
btnClass: 'btn-danger',
action: function() {}
}
}
});
});
// apilo product select save
$( 'body' ).on( 'click', '.apilo-product-select-save', function(){
var product_id = $( this ).attr( 'product-id' );
var apilo_product_id = $( '.apilo-product-select[product-id="' + product_id + '"]' ).val();
var apilo_product_name = $( '.apilo-product-select[product-id="' + product_id + '"] option:selected' ).text();
$.ajax({
type: 'POST',
cache: false,
url: '/admin/integrations/apilo_product_select_save/',
data: {
product_id: product_id,
apilo_product_id: apilo_product_id,
apilo_product_name: apilo_product_name
},
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( response ) {
data = jQuery.parseJSON( response );
if ( data.status == 'ok' ) {
$( '.apilo-product-select[product-id="' + product_id + '"]' ).closest( '.apilo-found-products' ).remove();
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).html( '<span title="' + apilo_product_name + '">' + apilo_product_name.substr( 0, 25 ) + '...</span>' ).removeClass( 'apilo-product-search' ).removeClass( 'text-danger' );
}
}
});
});
$( 'body' ).on( 'change', '.product_xml_name', function() {
var product_id = $( this ).attr( 'product-id' );
var product_xml_name = $( this ).val();
var lang_id = $( this ).attr( 'lang-id' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_xml_name_save/',
data: {
product_id: product_id,
product_xml_name: product_xml_name,
lang_id: lang_id
},
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( response ) {
$( '#overlay' ).hide();
}
});
});
// save custom_label on change
$( 'body' ).on( 'change', '.custom_label_0, .custom_label_1, .custom_label_2, .custom_label_3, .custom_label_4', function() {
var element = $( this );
var product_id = element.attr( 'product-id' );
var custom_label = element.val();
var label_type = element.attr( 'label-type' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_custom_label_save/',
data: {
product_id: product_id,
custom_label: custom_label,
label_type: label_type
},
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( response ) {
$( '#overlay' ).hide();
// hide after 1 seconds
setTimeout(function(){
element.parent( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).hide();
}, 1000);
}
});
});
// on keyup
$( 'body' ).on( 'keyup', '.custom_label_0, .custom_label_1, .custom_label_2, .custom_label_3, .custom_label_4', function() {
var element = $( this );
var product_id = element.attr( 'product-id' );
var custom_label = element.val();
var label_type = element.attr( 'label-type' );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_custom_label_suggestions/',
data: {
custom_label: custom_label,
label_type: label_type
},
beforeSend: function() {
},
success: function( response ) {
var data = jQuery.parseJSON( response );
var suggestions = "";
if ( data.suggestions.length > 0 ) {
for ( var i = 0; i < data.suggestions.length; i++ ) {
suggestions += "<div>" + data.suggestions[i].label + "</div>";
}
element.parent( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).html( suggestions ).show();
} else {
element.parent( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).hide();
}
}
});
});
});
$( 'body' ).on( 'click', '.custom_label_0_suggestions div, .custom_label_1_suggestions div, .custom_label_2_suggestions div, .custom_label_3_suggestions div, .custom_label_4_suggestions div', function(){
var element = $( this );
var label_type = element.parent().attr( 'label-type' );
var custom_label = element.html();
element.parents( '.' + label_type + '_container' ).find( 'input' ).val( custom_label );
// trigger change
element.parents( '.' + label_type + '_container' ).find( 'input' ).trigger( 'change' );
element.parents( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).hide();
});
function ajax_load_products( current_page ) {
var pagination_max = parseInt( $( '.pagination' ).attr( 'pagination_max' ) );
var query = '';
$( '.table-search' ).each(function(){
query += $( this ).attr( 'field_name' ) + '=' + $( this ).val() + '&';
});
current_page = parseInt( current_page );
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/ajax_load_products/',
data: {
current_page: current_page,
query: query
},
beforeSend: function() {
$( '#overlay' ).show();
},
success: function( response ) {
$( '#overlay' ).hide();
data = jQuery.parseJSON( response );
if ( data.status == 'ok' )
{
$( '#table-products tbody' ).html( data.html );
$( '.pagination .previous' ).attr( 'page', ( current_page - 1 > 1 ) ? ( current_page - 1 ) : 1 );
$( '.pagination .next' ).attr( 'page', ( current_page + 1 < pagination_max ) ? ( current_page + 1 ) : pagination_max );
$( '.pagination span' ).html( current_page );
if ( data.pagination_max )
{
$( '.pagination' ).attr( 'pagination_max', data.pagination_max );
$( '.pagination #max_page' ).html( data.pagination_max );
$( '.pagination .last' ).attr( 'page', data.pagination_max );
}
$( '#current-page' ).val( current_page );
}
}
});
}
</script>
<?php if (!empty($this->viewModel->customScriptView)): ?>
<?= \Tpl::view($this->viewModel->customScriptView, [
'list' => $this->viewModel,
'apilo_enabled' => $this->apilo_enabled,
'shoppro_enabled' => $this->shoppro_enabled,
]); ?>
<?php endif; ?>

View File

@@ -1,70 +0,0 @@
<script type="text/javascript" src="/libraries/framework/vendor/plugins/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/libraries/framework/vendor/plugins/ckeditor/adapters/jquery.js"></script>
<?php
global $db;
ob_start();
if ( \S::is_array_fix( $this -> product -> permutations ) )
{
foreach ( $this -> product -> permutations as $permutation )
{
?>
<div class="form-group row">
<label class="col-lg-4 control-label default-value">
<?
$data = explode( '_', $permutation );
foreach ( $data as $row )
{
echo \shop\ProductAttribute::getAttributeNameByValue( $row, \front\factory\Languages::default_language()) . ': <span class="text-muted">' . \shop\ProductAttribute::getValueName( $row, \front\factory\Languages::default_language() ) . '</span>';
if ( $row !== end( $data ) )
echo ' | ';
}
echo '</label>';
echo '<div class="col-lg-8">';
echo '<input type="text" class="form-control" name="permutation_' . $permutation . '" value="' . \shop\Product::getPermutationQuantity( $this -> product -> id, $permutation ) . '">';
echo '</div>';
echo '</div>';
}
}
else
{
echo '<div class="form-group row">';
echo ' <label class="col-lg-4 control-label default-value">Produkt bez atrybutów (lub z 1 atrybutem i jedną wartością):</label>';
echo '<div class="col-lg-8">';
echo '<input type="text" class="form-control" name="permutation_0" value="' . \admin\factory\ShopProduct::permutation_quantity( $this -> product -> id, 0 ) . '">';
echo '</div>';
echo '</div>';
}
?>
<?php
$out = ob_get_clean();
$grid = new \gridEdit();
$grid -> id = 'stock';
$grid -> gdb_opt = $gdb;
$grid -> include_plugins = true;
$grid -> title = 'Stany magazynowe: <u>'.$this -> product -> language['name'].'</u>';
$grid -> fields = [
[
'db' => 'id',
'type' => 'hidden',
'value' => $this -> product -> id,
],
];
$grid -> actions = [
'save' => ['url' => '/admin/shop_product/stock_save/', 'back_url' => '/admin/shop_product/view_list/'],
'cancel' => ['url' => '/admin/shop_product/view_list/'],
];
$grid -> external_code = $out;
$grid -> persist_edit = true;
$grid -> id_param = 'id';
echo $grid -> draw();
?>
<script type="text/javascript">
$( function()
{
disable_menu();
});
</script>

View File

@@ -1,176 +1,129 @@
<?
global $db;
ob_start();
?>
<?= \Html::form_text(
array(
'label' => 'Twoja wersja systemu',
'id' => 'ver',
'text' => $this -> ver
)
);?>
<?= \Html::form_text(
array(
'label' => 'Aktualna wersja systemu',
'text' => $this -> new_ver,
'id' => 'new_ver'
)
);?>
<?
$valuemax = ( $this -> new_ver - $this -> ver ) * 1000;
?>
<div class="progress-box hidden">
<div class="version">
<h3> Aktualizacja <p class="version_curent">0</p> / <p class="version_diff">0</p></h3>
</div>
<div class="progress ">
<div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar"
accesskey=""aria-valuenow="" aria-valuemin="0" aria-valuemax="<?=$valuemax?>" style="width:">
<div class="panel">
<div class="panel-heading">
<span class="panel-title">Aktualizacja systemu</span>
</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-lg-4">Twoja wersja systemu</label>
<div class="col-lg-8">
<p class="form-control-static"><?= $this->ver; ?></p>
</div>
</div>
</div>
<div class="row">
<? if ( $this -> ver < $this -> new_ver ):?>
<div class="form-group col-lg-6 text-right">
<div class="">
<a href="#" class="btn btn-system btn-sm mb5" id="confirm">Aktualizuj do wyższej wersji</a>
<div class="form-group">
<label class="control-label col-lg-4">Aktualna wersja systemu</label>
<div class="col-lg-8">
<p class="form-control-static"><?= $this->new_ver; ?></p>
</div>
</div>
</div>
<? endif;?>
<? if ( $this -> ver < $this -> new_ver ):?>
<div class="form-group col-lg-6">
<div class="">
<a href="#" class="btn btn-system btn-sm mb5" id="confirmUpdateAll">Aktualizuj do najwyższej wersji</a>
<div class="progress-box hidden">
<div class="version">
<h3>Aktualizacja <span class="version_curent">0</span> / <span class="version_diff">0</span></h3>
</div>
<div class="progress">
<div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
</div>
</div>
</div>
<? if ( $this->ver < $this->new_ver ): ?>
<div class="row mt15">
<div class="form-group col-lg-6 text-right">
<a href="#" class="btn btn-system btn-sm mb5" id="confirm">Aktualizuj do wyższej wersji</a>
</div>
<div class="form-group col-lg-6">
<a href="#" class="btn btn-system btn-sm mb5" id="confirmUpdateAll">Aktualizuj do najwyższej wersji</a>
</div>
</div>
<div class="text-danger text-center">* Przed aktualizacją systemu zalecane jest wykonanie pełnej kopii zapasowej.</div>
<? endif; ?>
</div>
<? endif;?>
</div>
<? if ( $this -> ver < $this -> new_ver ):?>
<div class="text-danger text-center">* Przed aktualizacją systemu zalecane jest wykonanie pełnej kopii zapasowej.</div>
<div class="clear"></div>
<?endif;?>
<?
$out = ob_get_clean();
$grid = new \gridEdit;
$grid -> id = 'update-view';
$grid -> gdb_opt = $gdb;
$grid -> include_plugins = true;
$grid -> title = 'Aktualizacja systemu';
$grid -> default_buttons = false;
$grid -> external_code = $out;
echo $grid -> draw();
?>
<?
ob_start();
echo $versions = file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php' );
$out = ob_get_clean();
<div class="panel">
<div class="panel-heading">
<span class="panel-title">Changelog</span>
</div>
<div class="panel-body">
<?= @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php' ); ?>
</div>
</div>
$grid = new \gridEdit;
$grid -> id = 'changelog';
$grid -> gdb_opt = $gdb;
$grid -> include_plugins = true;
$grid -> title = 'Changelog';
$grid -> default_buttons = false;
$grid -> external_code = $out;
echo $grid -> draw();
?>
<script type="text/javascript">
var version_current = <?= $this -> ver;?>;
var version_new = <?= $this -> new_ver;?>;
var version_diff = Math.round((version_new - version_current )* 1000);
$(function() {
var version_current = <?= $this->ver; ?>;
var version_new = <?= $this->new_ver; ?>;
var version_diff = Math.round( ( version_new - version_current ) * 1000 );
var width = 0;
var ac_lp = 0;
$( document ).ready( function()
{
$( 'body' ).on( 'click', '#confirm', function()
{
$.prompt( 'Na pewno chcesz dokonać aktualizacji systemu?',
{
title: 'Potwierdź?',
submit: function(e,v,m,f)
{
if ( v == true )
document.location.href = '/admin/update/update/';
},
buttons: {
'tak': true, 'nie': false
},
focus: 1
});
});
$( 'body' ).on( 'click', '#confirmUpdateAll', function()
{
$.prompt( 'Na pewno chcesz dokonać aktualizacji systemu?',
{
title: 'Potwierdź?',
submit: function(e,v,m,f)
{
if ( v == true )
{
$('.progress-box').removeClass('hidden');
$('#confirm').css('pointer-events','none');
$('#confirmUpdateAll').css('pointer-events','none');
$('.version_diff').html(version_diff);
updateAll( version_current, version_new, version_diff, width, ac_lp);
}
$( '#confirm' ).on( 'click', function( e ) {
e.preventDefault();
$.confirm({
title: 'Potwierdź',
content: 'Na pewno chcesz dokonać aktualizacji systemu?',
buttons: {
tak: function() {
document.location.href = '/admin/update/update/';
},
buttons: {
'tak': true, 'nie': false
}
});
nie: function() {}
}
});
});
function updateAll( version_current, version_new, version_diff, width, ac_lp)
{
$.ajax(
{
$( '#confirmUpdateAll' ).on( 'click', function( e ) {
e.preventDefault();
$.confirm({
title: 'Potwierdź',
content: 'Na pewno chcesz dokonać aktualizacji systemu do najwyższej wersji?',
buttons: {
tak: function() {
$( '.progress-box' ).removeClass( 'hidden' );
$( '#confirm' ).css( 'pointer-events', 'none' );
$( '#confirmUpdateAll' ).css( 'pointer-events', 'none' );
$( '.version_diff' ).html( version_diff );
updateAll( version_current, version_new, version_diff, width, ac_lp );
},
nie: function() {}
}
});
});
function updateAll( version_current, version_new, version_diff, width, ac_lp ) {
$.ajax({
url: '/admin/update/updateAll/',
type: 'POST',
data:
{
version_current: version_current
},
success: function( data )
{
response = jQuery.parseJSON( data );
if ( response.status == true )
{
ac_lp = ac_lp + 1;
$('.version_curent').html(ac_lp);
width = width + ((1/version_diff)*100);
$('.progress-bar').attr("style", "width:"+ width +"%");
if( response.version < version_new )
{
updateAll( response.version, version_new, version_diff, width, ac_lp );
}
else
{
$.prompt( "Aktualizacja przebiegła pomyślnie.",
{
title: 'Informacja',
close: function(e,v,m,f){
window.location.href ="/admin/update/main_view/";
}
});
}
}
else
{
$.prompt( "W trakcie aktualizacji systemu wystąpił błąd. Proszę spróbować ponownie." ,
{
title: 'Błąd',
close: function(e,v,m,f){
window.location.href ="/admin/update/main_view/";
}
data: { version_current: version_current },
success: function( data ) {
var response = jQuery.parseJSON( data );
if ( response.status == true ) {
ac_lp = ac_lp + 1;
$( '.version_curent' ).html( ac_lp );
width = width + ( ( 1 / version_diff ) * 100 );
$( '.progress-bar' ).css( 'width', width + '%' );
if ( response.version < version_new ) {
updateAll( response.version, version_new, version_diff, width, ac_lp );
} else {
$.alert({
title: 'Informacja',
content: 'Aktualizacja przebiegła pomyślnie.',
onClose: function() {
window.location.href = '/admin/update/main_view/';
}
});
}
} else {
$.alert({
title: 'Błąd',
content: 'W trakcie aktualizacji systemu wystąpił błąd. Proszę spróbować ponownie.',
onClose: function() {
window.location.href = '/admin/update/main_view/';
}
});
}
}
}
);
};
</script>
});
}
});
</script>