This commit is contained in:
2025-08-19 20:32:58 +02:00
parent b1c5000a43
commit a25de121ec
20 changed files with 172 additions and 57 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2228,4 +2228,20 @@ textarea.form-control {
width: 50px;
text-align: center;
}
}
.input-group-addon {
width: auto;
label {
display: flex;
align-items: center;
gap: 5px;
}
}
.additional_fields {
input[type="text"] {
height: 40px;
}
}

View File

@@ -12,11 +12,11 @@ $grid -> search = [
[ 'name' => 'Wysłany', 'db' => 'send', 'type' => 'select', 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] ]
];
$grid -> columns_view = [
[
[
'name' => 'Lp.',
'th' => [ 'class' => 'g-lp' ],
'td' => [ 'class' => 'g-center' ],
'autoincrement' => true
'autoincrement' => true
], [
'name' => 'Aktywny',
'db' => 'status',
@@ -24,6 +24,11 @@ $grid -> columns_view = [
'td' => [ 'class' => 'g-center' ],
'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ],
'sort' => true
], [
'name' => 'Użyto X razy',
'db' => 'used_count',
'td' => [ 'class' => 'g-center' ],
'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ],
], [
'name' => 'Nazwa',
'db' => 'name',
@@ -60,22 +65,22 @@ $grid -> columns_view = [
'db' => 'date_used',
'td' => [ 'class' => 'g-center' ],
'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ]
], [
'name' => 'Edytuj',
'action' => [ 'type' => 'edit', 'url' => '/admin/shop_coupon/coupon_edit/id=[id]' ],
], [
'name' => 'Edytuj',
'action' => [ 'type' => 'edit', 'url' => '/admin/shop_coupon/coupon_edit/id=[id]' ],
'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ],
'td' => [ 'class' => 'g-center' ]
], [
'name' => 'Usuń',
'action' => [ 'type' => 'delete', 'url' => '/admin/shop_coupon/coupon_delete/id=[id]' ],
], [
'name' => 'Usuń',
'action' => [ 'type' => 'delete', 'url' => '/admin/shop_coupon/coupon_delete/id=[id]' ],
'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ],
'td' => [ 'class' => 'g-center' ]
]
];
$grid -> buttons = [
[
'label' => 'Dodaj kupon',
'url' => '/admin/shop_coupon/coupon_edit/',
[
'label' => 'Dodaj kupon',
'url' => '/admin/shop_coupon/coupon_edit/',
'icon' => 'fa-plus-circle',
'class' => 'btn-success'
]

View File

@@ -44,6 +44,9 @@ ob_start();
<div class="panel">
<div class="panel-body">
<div>Kwota zamówienia <b><?= $this -> order[ 'summary' ];?> zł</b></div>
<? if ( $this -> coupon ):?>
<div>Kod rabatowy: <span style="color: #cc0000;"><?= $this -> coupon -> name;?> - <?= $this -> coupon -> amount;?> <?= $this -> coupon -> type == 1 ? '%' : 'zł';?></span></div>
<? endif;?>
<br>
<div><?= strip_tags( $this -> order[ 'transport' ] );?>: <b><?= $this -> order[ 'transport_cost' ];?> zł</b></div>
<? if ( $this -> order['transport_id'] == 2 and $this -> order[ 'inpost_paczkomat' ] ):?>

View File

@@ -663,12 +663,19 @@ ob_start();
<div>
<a href="#" class="btn btn-success" id="add_custom_field"><i class="fa fa-plus"></i> dodaj niestandardowe pole</a>
<div class="additional_fields">
<? if ( is_array( $this -> product['custom_fields'] ) ) : foreach ( $this -> product['custom_fields'] as $field ):?>
<div class="form-group row">
<? if ( is_array( $this->product['custom_fields'] ) ) : foreach ( $this->product['custom_fields'] as $field ):?>
<? $isRequired = !empty($field['is_required']); ?>
<div class="form-group row custom-field-row">
<label class="col-lg-4 control-label">Nazwa pola:</label>
<div class="col-lg-8">
<div class="input-group">
<input type="text" class="form-control" name="custom_field_name[]" value="<?= $field['name']; ?>">
<input type="text" class="form-control" name="custom_field_name[]" value="<?= htmlspecialchars($field['name']); ?>">
<span class="input-group-addon">
<label style="margin:0; font-weight:normal;">
<input type="checkbox" class="custom-field-required" <?= $isRequired ? 'checked' : '' ?> name="custom_field_required[]" />
wymagane
</label>
</span>
<span class="input-group-addon btn btn-info" onclick="remove_custom_filed( $( this ) );">usuń</span>
</div>
</div>
@@ -740,17 +747,23 @@ echo $grid->draw();
$(function() {
$( 'body' ).on( 'click', '#add_custom_field', function() {
$('body').on('click', '#add_custom_field', function() {
var html = '';
html += '<div class="form-group row">';
html += '<div class="form-group row custom-field-row">';
html += '<label class="col-lg-4 control-label">Nazwa pola:</label>';
html += '<div class="col-lg-8">';
html +='<div class="input-group">';
html += '<div class="input-group">';
html += '<input type="text" class="form-control" name="custom_field_name[]" value="">';
html += '<span class="input-group-addon">';
html += '<label style="margin:0; font-weight:normal;">';
html += '<input type="checkbox" class="custom-field-required" name="custom_field_required[]"> wymagane';
html += '</label>';
html += '</span>';
html += '<span class="input-group-addon btn btn-info" onclick="remove_custom_filed( $( this ) );">usuń</span>';
html += '</div>';
html += '</div>';
html += '</div>';
$( '.additional_fields' ).append( html );
$('.additional_fields').append(html);
});
$('body').on('click', '#product-preview', function() {

View File

@@ -64,7 +64,7 @@ echo $grid -> draw();
?>
<?
ob_start();
echo $versions = file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php' );
echo $versions = file_get_contents( 'http://www.shoppro.project-dc.pl/updates/changelog.php' );
$out = ob_get_clean();
$grid = new \gridEdit;

BIN
autoload/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -186,7 +186,7 @@ class ShopProduct
$values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'],
$values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'],
$values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'],
$values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name']
$values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required']
) ) {
$response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ];
}

View File

@@ -761,7 +761,7 @@ class ShopProduct
}
public static function save(
$product_id, $name, $short_description, $description, $status, $meta_description, $meta_keywords, $seo_link, $copy_from, $categories, $price_netto, $price_brutto, $vat, $promoted, $warehouse_message_zero, $warehouse_message_nonzero, $tab_name_1, $tab_description_1, $tab_name_2, $tab_description_2, $layout_id, $products_related, int $set_id, $price_netto_promo, $price_brutto_promo, $new_to_date, $stock_0_buy, $wp, $custom_label_0, $custom_label_1, $custom_label_2, $custom_label_3, $custom_label_4, $additional_message, int $quantity, $additional_message_text, int $additional_message_required, $canonical, $meta_title, $producer_id, $sku, $ean, $product_unit, $weight, $xml_name, $custom_field_name
$product_id, $name, $short_description, $description, $status, $meta_description, $meta_keywords, $seo_link, $copy_from, $categories, $price_netto, $price_brutto, $vat, $promoted, $warehouse_message_zero, $warehouse_message_nonzero, $tab_name_1, $tab_description_1, $tab_name_2, $tab_description_2, $layout_id, $products_related, int $set_id, $price_netto_promo, $price_brutto_promo, $new_to_date, $stock_0_buy, $wp, $custom_label_0, $custom_label_1, $custom_label_2, $custom_label_3, $custom_label_4, $additional_message, int $quantity, $additional_message_text, int $additional_message_required, $canonical, $meta_title, $producer_id, $sku, $ean, $product_unit, $weight, $xml_name, $custom_field_name, $custom_field_required
)
{
global $mdb, $user;
@@ -941,13 +941,17 @@ class ShopProduct
}
// dodatkowe pola
foreach ( $custom_field_name as $custom_field )
for ( $i = 0; $i < count( $custom_field_name ); ++$i )
{
if ( !empty( $custom_field ) )
if ( !empty( $custom_field_name[$i] ) )
{
$custom_field = $custom_field_name[$i];
$custom_field_required = isset( $custom_field_required[$i] ) ? 1 : 0;
$mdb -> insert( 'pp_shop_products_custom_fields', [
'id_product' => (int) $id,
'name' => $custom_field,
'is_required' => $custom_field_required,
] );
}
}
@@ -1268,13 +1272,20 @@ class ShopProduct
$mdb -> delete( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'id_additional_field[!]' => $exits_custom_ids ] ] );
foreach ( $custom_field_name as $custom_field )
// $custom_field_name i $custom_field_required
foreach ( $custom_field_name as $i => $custom_field )
{
if ( !empty( $custom_field ) )
{
$is_required = !empty( $custom_field_required[$i] ) ? 1 : 0;
if ( !$mdb -> count( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] ) )
{
$mdb -> insert( 'pp_shop_products_custom_fields', [ 'id_product' => $product_id, 'name' => $custom_field ] );
$mdb -> insert( 'pp_shop_products_custom_fields', [ 'id_product' => $product_id, 'name' => $custom_field, 'is_required' => $is_required ] );
}
else
{
$mdb -> update( 'pp_shop_products_custom_fields', [ 'is_required' => $is_required ], [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] );
}
}
}

View File

@@ -322,17 +322,42 @@ class S
return $result;
}
public static function normalize_decimal( $val, $precision = 2 )
static public function normalize_decimal($val, int $precision = 2)
{
$input = str_replace( ' ', '', $val );
$number = str_replace( ',', '.', $input );
if ( strpos( $number, '.' ) )
{
$groups = explode( '.', str_replace( ',', '.', $number ) );
$lastGroup = array_pop( $groups );
$number = implode( '', $groups ) . '.' . $lastGroup;
if ($val === null || $val === '') {
return number_format(0, $precision, '.', '');
}
return bcadd( round( $number, $precision ), 0, $precision );
// 1) wstępne czyszczenie
$s = (string)$val;
$s = str_replace(["\xC2\xA0", ' ', '', "'"], '', $s); // spacje (w tym NBSP) i apostrofy
$s = preg_replace('/[^0-9.,\-]/', '', $s); // zostaw tylko cyfry, . , i -
// 2) ustalenie separatora dziesiętnego
$lastDot = strrpos($s, '.');
$lastComma = strrpos($s, ',');
if ($lastDot !== false && $lastComma !== false) {
// oba występują prawy znak traktujemy jako dziesiętny
if ($lastDot > $lastComma) {
$s = str_replace(',', '', $s); // , = tysiące
} else {
$s = str_replace('.', '', $s); // . = tysiące
$s = str_replace(',', '.', $s); // , = dziesiętny
}
} elseif ($lastComma !== false) {
// tylko przecinek traktuj jako dziesiętny
$s = str_replace(',', '.', $s);
} elseif (substr_count($s, '.') > 1) {
// wiele kropek ostatnia dziesiętna, pozostałe tysiące
$pos = strrpos($s, '.');
$s = str_replace('.', '', substr($s, 0, $pos)) . '.' . substr($s, $pos + 1);
}
// na tym etapie mamy opcjonalny '-' i co najwyżej jedną kropkę dziesiętną
// 3) zaokrąglenie i normalizacja
$rounded = round((float)$s, $precision);
return number_format($rounded, $precision, '.', '');
}
public static function decimal( $val, $precision = 2, $dec_point = ',', $thousands_sep = ' ' )

BIN
autoload/front/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -485,6 +485,8 @@ jQuery( 'body' ).on( 'click', '#g-cancel', function() {
});
});
jQuery( 'body' ).on( 'click', '#g-save, #g-edit-save', function()
{
var back_url = jQuery( this ).attr( 'back_url' );
@@ -534,6 +536,44 @@ jQuery( 'body' ).on( 'click', '#g-save, #g-edit-save', function()
}
});
/* === UNIWERSALNA NORMALIZACJA CHECKBOXÓW TABLICOWYCH ===
Dla wszystkich input[type=checkbox] z name="coś[]":
- jeśli wartości są typu boolean ('', '1', 'on', 'true', 'yes') → zbuduj pełną tablicę 1/0 w kolejności DOM,
tak aby indeksy były ciągłe [0..N-1] nawet gdy część jest odznaczona.
- jeśli to lista wyboru (np. categories[] z różnymi ID) → pozostaw jak z serializeArray (tylko zaznaczone).
*/
(function normalizeCheckboxArrays() {
var $form = jQuery('#fg-' + gtable);
if (!$form.length) return;
// zgrupuj checkboxy po pełnej nazwie (z [] na końcu)
var groups = {};
$form.find('input[type="checkbox"][name$="[]"]').each(function () {
var n = this.name; // np. "required[]", "visible[]", "categories[]"
(groups[n] = groups[n] || []).push(this);
});
Object.keys(groups).forEach(function (nameWithBrackets) {
var inputs = groups[nameWithBrackets];
if (!inputs.length) return;
// sprawdź, czy wszystkie wartości wyglądają „booleanowo”
var uniqVals = Array.from(new Set(inputs.map(function (el) {
return (el.getAttribute('value') || '').toLowerCase();
})));
var boolSet = new Set(['', '1', 'on', 'true', 'yes']);
var isBooleanLike = uniqVals.every(function (v) { return boolSet.has(v); });
if (isBooleanLike) {
var baseKey = nameWithBrackets.replace(/\[\]$/, ''); // usuń [] → "required", "visible"
// pełna tablica 1/0 w kolejności w formularzu
formattedValues[baseKey] = inputs.map(function (el) { return el.checked ? '1' : '0'; });
}
// else: zostawiamy formattedValues tak jak już zbudowane z serializeArray()
});
})();
var url = jQuery( this ).attr( 'url' );
if ( url !== '' )

BIN
templates/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -4,7 +4,7 @@
<?= $custom_field['name'];?>:
</div>
<div class="_input">
<input type="text" class="form-control" name="custom_field[<?= $custom_field['id_additional_field'];?>]" field_name="<?= $custom_field['name'];?>" value="">
<input type="text" class="form-control" name="custom_field[<?= $custom_field['id_additional_field'];?>]" field_name="<?= $custom_field['name'];?>" value="" <? if ( !empty( $custom_field['is_required'] ) ): ?>required<? endif; ?>>
</div>
</div>
<? endforeach; endif;?>

View File

@@ -255,22 +255,24 @@
<script class="footer" type="text/javascript" src="/libraries/fancybox3/js/wheel.js"></script>
<script class="footer" type="text/javascript" src="/plugins/OwlCarousel/owl.carousel.js"></script>
<script type="text/javascript">
<? if ( $this -> settings['google_tag_manager_id'] ):?>
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: "view_item",
ecommerce: {
items: [
{
item_id: "<?= $this -> product -> id;?>",
item_name: "<?= str_replace( '"', '', $this -> product -> language['name'] );?>",
price: '<? if ( $this -> product -> price_brutto_promo ): echo \S::normalize_decimal( $this -> product -> price_brutto_promo ); else: echo \S::normalize_decimal( $this -> product -> price_brutto ); endif;?>',
quantity: 1
}
]
}
});
<? endif;?>
$(function(){
<? if ( $this -> settings['google_tag_manager_id'] ):?>
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: "view_item",
ecommerce: {
items: [
{
item_id: "<?= $this -> product -> id;?>",
item_name: "<?= str_replace( '"', '', $this -> product -> language['name'] );?>",
price: '<? if ( $this -> product -> price_brutto_promo ): echo \S::normalize_decimal( $this -> product -> price_brutto_promo ); else: echo \S::normalize_decimal( $this -> product -> price_brutto ); endif;?>',
quantity: 1
}
]
}
});
<? endif;?>
});
</script>
<script class="footer" type="text/javascript">
$( function ()
@@ -524,7 +526,7 @@
}
// dodatkowe pola muszą być uzupełnione
$( '.custom-field input' ).each( function( index, element )
$( '.custom-field input[required]' ).each( function( index, element )
{
if ( $.trim( $( element ).val() ) == '' )
{

BIN
templates_user/.DS_Store vendored Normal file

Binary file not shown.

BIN
templates_user/components/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,11 +1,11 @@
<? if ( is_array( $this -> custom_fields ) ): foreach ( $this -> custom_fields as $custom_field ):?>
<div class="custom-field">
<div class="_name">
<?= $custom_field['name'];?>:
<?= $custom_field['name'];?><? if ( !empty( $custom_field['is_required'] ) ): ?>*<? endif; ?>:
</div>
<div class="_input">
<div class="grow-wrap">
<textarea name="custom_field[<?= $custom_field['id_additional_field'];?>]" field_name="<?= $custom_field['name'];?>" onInput="this.parentNode.dataset.replicatedValue = this.value"></textarea>
<textarea name="custom_field[<?= $custom_field['id_additional_field'];?>]" field_name="<?= $custom_field['name'];?>" onInput="this.parentNode.dataset.replicatedValue = this.value" <? if ( !empty( $custom_field['is_required'] ) ): ?>required<? endif; ?>></textarea>
</div>
</div>
</div>

View File

@@ -502,7 +502,7 @@
}
// dodatkowe pola muszą być uzupełnione
$( '.custom-field textarea' ).each( function( index, element )
$( '.custom-field textarea[required]' ).each( function( index, element )
{
if ( $.trim( $( element ).val() ) == '' )
{