app/template/default/Product/detail.twig line 1

Open in your IDE?
  1. {#
  2. This file is part of EC-CUBE
  3. Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  4. http://www.ec-cube.co.jp/
  5. For the full copyright and license information, please view the LICENSE
  6. file that was distributed with this source code.
  7. #}
  8. {% extends 'default_frame.twig' %}
  9. {% set isHasLeftSide = false %}
  10. {% block stylesheet %}
  11.     <link rel="stylesheet" href="{{ asset('assets/css/product-detail.css') }}">
  12.     <link rel="stylesheet" href="{{ asset('assets/CustomComponent/modal.css') }}">
  13.     <style>
  14.         .custom-quantity {
  15.             border: none;
  16.             font-size: 18px;
  17.             text-align: center;
  18.         }
  19.         /* remove number input arrow */
  20.         .custom-quantity::-webkit-outer-spin-button,
  21.         .custom-quantity::-webkit-inner-spin-button {
  22.             -webkit-appearance: none;
  23.             margin: 0;
  24.         }
  25.         .custom-quantity[type=number] {
  26.             -moz-appearance: textfield;
  27.         }
  28.         .custom-quantity[type=number] {
  29.             -moz-appearance: textfield;
  30.         }
  31.         .slick-slider {
  32.             margin-bottom: 10px;
  33.         }
  34.         .slick-dots {
  35.             position: absolute;
  36.             bottom: -45px;
  37.             display: block;
  38.             width: 100%;
  39.             padding: 0;
  40.             list-style: none;
  41.             text-align: center;
  42.         }
  43.         .slick-dots li {
  44.             position: relative;
  45.             display: inline-block;
  46.             width: 20px;
  47.             height: 20px;
  48.             margin: 0 5px;
  49.             padding: 0;
  50.             cursor: pointer;
  51.         }
  52.         .slick-dots li button {
  53.             font-size: 0;
  54.             line-height: 0;
  55.             display: block;
  56.             width: 20px;
  57.             height: 20px;
  58.             padding: 5px;
  59.             cursor: pointer;
  60.             color: transparent;
  61.             border: 0;
  62.             outline: none;
  63.             background: transparent;
  64.         }
  65.         .slick-dots li button:hover,
  66.         .slick-dots li button:focus {
  67.             outline: none;
  68.         }
  69.         .slick-dots li button:hover:before,
  70.         .slick-dots li button:focus:before {
  71.             opacity: 1;
  72.         }
  73.         .slick-dots li button:before {
  74.             content: " ";
  75.             line-height: 20px;
  76.             position: absolute;
  77.             top: 0;
  78.             left: 0;
  79.             width: 12px;
  80.             height: 12px;
  81.             text-align: center;
  82.             opacity: .25;
  83.             background-color: black;
  84.             border-radius: 50%;
  85.         }
  86.         .slick-dots li.slick-active button:before {
  87.             opacity: .75;
  88.             background-color: black;
  89.         }
  90.         .slick-dots li button.thumbnail img {
  91.             width: 0;
  92.             height: 0;
  93.         }
  94.         .slide-item img {
  95.             border-radius: 10px;
  96.             object-fit: contain;
  97.             max-height: 550px;
  98.             height: 550px;
  99.             width: 100%;
  100.             background-color: #E9E9E9;
  101.         }
  102.         .ec-sliderItemRole .slideThumb img {
  103.             width: 100%;
  104.             border-radius: 6px;
  105.             height: 133px;
  106.             max-height: 133px;
  107.             object-fit: contain;
  108.         }
  109.         .ec-sliderItemRole .slideThumb {
  110.             margin-bottom: 0;
  111.             width: calc(25% - 7.5px);
  112.         }
  113.         .ec-sliderItemRole .item_nav {
  114.             gap: 10px;
  115.         }
  116.         .item_nav {
  117.             display: flex;
  118.             gap: 16px;
  119.             flex-wrap: wrap;
  120.         }
  121.         .slideThumb {
  122.             width: calc(25% - 12px);
  123.         }
  124.         @media screen and (max-width: 1200px) {
  125.             .item_nav {
  126.                 gap: 10px;
  127.             }
  128.             .slideThumb {
  129.                 width: calc(25% - 7.5px);
  130.                 height: 20vw;
  131.             }
  132.             .slide-item img {
  133.                 height: auto;
  134.                 max-height: 500px;
  135.             }
  136.         }
  137.         @media screen and (max-width: 768px) {
  138.             .slide-item img {
  139.                 height: 342px;
  140.             }
  141.         }
  142.         .ec-grid2 {
  143.             gap: 10px;
  144.         }
  145.         .main_button_add_cart, .main_button_add_cart:hover {
  146.             background-color: var(--cart-button-color);
  147.         }
  148.     </style>
  149. {% endblock %}
  150. {% set body_class = 'product_page' %}
  151. {# script #}
  152. {% block javascript %}
  153. <script>
  154. $(document).ready(function() {
  155.     $('.item-minus').click(function () {
  156.         // Check if button is disabled
  157.         if ($(this).hasClass('btn-disabled')) {
  158.             return;
  159.         }
  160.         const $input = $(this).siblings('.quantity-value').find('.custom-quantity');
  161.         let current = parseInt($input.val()) || 0;
  162.         if (current > 1) {
  163.             $input.val(current - 1);
  164.         }
  165.     });
  166.     $('.item-plus').click(function () {
  167.         // Check if button is disabled
  168.         if ($(this).hasClass('btn-disabled')) {
  169.             return;
  170.         }
  171.         const $input = $(this).siblings('.quantity-value').find('.custom-quantity');
  172.         let current = parseInt($input.val()) || 0;
  173.         $input.val(current + 1);
  174.     });
  175. });
  176. const params = new URLSearchParams(window.location.search);
  177. const imei = params.get('imei');
  178. if (imei) {
  179.     $('#user_imei').val(imei);
  180. }
  181. </script>
  182. <script>
  183.     eccube.classCategories = {{ class_categories_as_json(Product)|raw }};
  184.     // 規格2に選択肢を割り当てる。
  185.     function fnSetClassCategories(form, classcat_id2_selected) {
  186.         var $form = $(form);
  187.         var product_id = $form.find('input[name=product_id]').val();
  188.         var $sele1 = $form.find('select[name=classcategory_id1]');
  189.         var $sele2 = $form.find('select[name=classcategory_id2]');
  190.         eccube.setClassCategories($form, product_id, $sele1, $sele2, classcat_id2_selected);
  191.     }
  192.     {% if form.classcategory_id2 is defined %}
  193.     fnSetClassCategories(
  194.         $('#form1'), {{ form.classcategory_id2.vars.value|json_encode|raw }}
  195.     );
  196.     {% elseif form.classcategory_id1 is defined %}
  197.     eccube.checkStock($('#form1'), {{ Product.id }}, {{ form.classcategory_id1.vars.value|json_encode|raw }}, null);
  198.     {% endif %}
  199.     // Thêm thuộc tính is_country_category cho classcategory select
  200.     function addCountryCategoryAttributes() {
  201.         // Lấy country_id từ URL
  202.         const countryId = params.get('country_id');
  203.         console.log('Country ID from URL:', countryId);
  204.         console.log('eccube.classCategories:', eccube.classCategories);
  205.         if (!countryId) {
  206.             console.log('No country_id found in URL');
  207.             return; // Không có country_id thì không làm gì
  208.         }
  209.         let hasSelected = false;
  210.         // Thêm thuộc tính cho classcategory_id1
  211.         const $select1 = $('select[name=classcategory_id1]');
  212.         if ($select1.length) {
  213.             console.log('Found select1, checking options...');
  214.             $select1.find('option').each(function() {
  215.                 const $option = $(this);
  216.                 const optionValue = $option.val();
  217.                 let isCountryCategoryFlag = false;
  218.                 if (optionValue && optionValue !== '__unselected') {
  219.                     isCountryCategoryFlag = isCountryCategory(optionValue);
  220.                     if (isCountryCategoryFlag && optionValue === countryId) {
  221.                         console.log('Auto-selecting country category:', optionValue, 'in select1');
  222.                         $option.prop('selected', true);
  223.                         hasSelected = true;
  224.                     }
  225.                 }
  226.                 $option.attr('data-is-country-category', isCountryCategoryFlag);
  227.             });
  228.             // Trigger change event sau khi đã chọn tất cả options
  229.             if (hasSelected) {
  230.                 setTimeout(function() {
  231.                     $select1.trigger('change');
  232.                 }, 10);
  233.             }
  234.         }
  235.         // Thêm thuộc tính cho classcategory_id2
  236.         const $select2 = $('select[name=classcategory_id2]');
  237.         if ($select2.length) {
  238.             console.log('Found select2, checking options...');
  239.             $select2.find('option').each(function() {
  240.                 const $option = $(this);
  241.                 const optionValue = $option.val();
  242.                 // Kiểm tra xem option này có phải là country category không
  243.                 let isCountryCategoryFlag = false;
  244.                 if (optionValue && optionValue !== '__unselected') {
  245.                     // Kiểm tra xem option này có phải là country category không
  246.                     isCountryCategoryFlag = isCountryCategory(optionValue);
  247.                     // Nếu đây là country category và match với country_id, tự động chọn
  248.                     if (isCountryCategoryFlag && optionValue === countryId) {
  249.                         console.log('Auto-selecting country category:', optionValue, 'in select2');
  250.                         $option.prop('selected', true);
  251.                         hasSelected = true;
  252.                     }
  253.                 }
  254.                 // Thêm thuộc tính data-is-country-category
  255.                 $option.attr('data-is-country-category', isCountryCategoryFlag);
  256.             });
  257.             // Trigger change event sau khi đã chọn tất cả options
  258.             if (hasSelected) {
  259.                 setTimeout(function() {
  260.                     $select2.trigger('change');
  261.                 }, 10);
  262.             }
  263.         }
  264.         if (hasSelected) {
  265.             console.log('Country category auto-selection completed');
  266.         }
  267.     }
  268.     // Function để kiểm tra xem một classcategory có phải là country category không
  269.     function isCountryCategory(categoryId) {
  270.         if (eccube.classCategories && eccube.classCategories[categoryId]) {
  271.             console.log('Category', categoryId, 'found in eccube.classCategories');
  272.             return true;
  273.         }
  274.         return false;
  275.     }
  276.     // Chạy function khi document ready và sau khi các select đã được khởi tạo
  277.     $(document).ready(function() {
  278.         // Delay một chút để đảm bảo các select đã được khởi tạo
  279.         setTimeout(function() {
  280.             addCountryCategoryAttributes();
  281.         }, 100);
  282.     });
  283.     // Custom function to update price display
  284.     function updatePriceDisplay(classcat2) {
  285.         var $price02 = $('.price02-default');
  286.         try {
  287.             if (classcat2 && typeof classcat2.price02_inc_tax !== 'undefined' && String(classcat2.price02_inc_tax).length >= 1) {
  288.                 // Show specific price when category is selected
  289.                 $price02.text(classcat2.price02_inc_tax_with_currency).addClass('selected');
  290.             } else {
  291.                 // Show range price when no category is selected
  292.                 var originalPrice = $price02.attr('data-original-price');
  293.                 if (originalPrice) {
  294.                     $price02.text(originalPrice).removeClass('selected');
  295.                 }
  296.             }
  297.         } catch (e) {
  298.             console.log('Error updating price display:', e);
  299.             // Fallback to original price
  300.             var originalPrice = $price02.attr('data-original-price');
  301.             if (originalPrice) {
  302.                 $price02.text(originalPrice).removeClass('selected');
  303.             }
  304.         }
  305.     }
  306.     // Add event listeners for category changes
  307.     $(document).ready(function() {
  308.         // Listen for category 1 changes
  309.         $('select[name=classcategory_id1]').on('change', function() {
  310.             var $form = $(this).parents('form');
  311.             var product_id = $form.find('input[name=product_id]').val();
  312.             var $sele1 = $(this);
  313.             var $sele2 = $form.find('select[name=classcategory_id2]');
  314.             // Update price display after a short delay to let original function complete
  315.             setTimeout(function() {
  316.                 updatePriceFromCategories(product_id, $sele1.val(), $sele2.val());
  317.             }, 150);
  318.         });
  319.         // Listen for category 2 changes
  320.         $('select[name=classcategory_id2]').on('change', function() {
  321.             var $form = $(this).parents('form');
  322.             var product_id = $form.find('input[name=product_id]').val();
  323.             var $sele1 = $form.find('select[name=classcategory_id1]');
  324.             var $sele2 = $form.find('select[name=classcategory_id2]');
  325.             // Update price display after a short delay to let original function complete
  326.             setTimeout(function() {
  327.                 updatePriceFromCategories(product_id, $sele1.val(), $sele2.val());
  328.             }, 150);
  329.         });
  330.     });
  331.     // Function to update price based on selected categories
  332.     function updatePriceFromCategories(product_id, classcat_id1, classcat_id2) {
  333.         try {
  334.             // Don't update if categories are not properly selected
  335.             if (!classcat_id1 || classcat_id1 === '__unselected' || classcat_id1 === '') {
  336.                 var $price02 = $('.price02-default');
  337.                 var originalPrice = $price02.attr('data-original-price');
  338.                 if (originalPrice) {
  339.                     $price02.text(originalPrice).removeClass('selected');
  340.                 }
  341.                 return;
  342.             }
  343.             var classcat2;
  344.             if (eccube.hasOwnProperty('productsClassCategories')) {
  345.                 if (eccube.productsClassCategories[product_id] &&
  346.                     eccube.productsClassCategories[product_id][classcat_id1]) {
  347.                     classcat2 = eccube.productsClassCategories[product_id][classcat_id1]['#' + classcat_id2];
  348.                 }
  349.             } else {
  350.                 if (typeof eccube.classCategories[classcat_id1] !== 'undefined') {
  351.                     classcat2 = eccube.classCategories[classcat_id1]['#' + classcat_id2];
  352.                 }
  353.             }
  354.             updatePriceDisplay(classcat2);
  355.         } catch (e) {
  356.             console.log('Error updating price from categories:', e);
  357.             // Fallback to original price
  358.             var $price02 = $('.price02-default');
  359.             var originalPrice = $price02.attr('data-original-price');
  360.             if (originalPrice) {
  361.                 $price02.text(originalPrice).removeClass('selected');
  362.             }
  363.         }
  364.     }
  365.     // Initialize price display when page loads
  366.     $(document).ready(function() {
  367.         // Store original price if not already stored
  368.         var $price02 = $('.price02-default');
  369.         if (!$price02.attr('data-original-price')) {
  370.             $price02.attr('data-original-price', $price02.text());
  371.         }
  372.         // Check if any category is selected on page load
  373.         var $classcat1 = $('select[name=classcategory_id1]');
  374.         var $classcat2 = $('select[name=classcategory_id2]');
  375.         // Only update price if categories are properly selected
  376.         if ($classcat1.length && $classcat1.val() && $classcat1.val() !== '__unselected') {
  377.             // Category 1 is selected, trigger price update
  378.             if ($classcat2.length && $classcat2.val() && $classcat2.val() !== '__unselected') {
  379.                 // Both categories are selected
  380.                 setTimeout(function() {
  381.                     updatePriceFromCategories({{ Product.id }}, $classcat1.val(), $classcat2.val());
  382.                 }, 200);
  383.             } else {
  384.                 // Only category 1 is selected
  385.                 setTimeout(function() {
  386.                     updatePriceFromCategories({{ Product.id }}, $classcat1.val(), null);
  387.                 }, 200);
  388.             }
  389.         }
  390.     });
  391. </script>
  392.     <script>
  393.         $(function() {
  394.             // bfcache無効化
  395.             $(window).bind('pageshow', function(event) {
  396.                 if (event.originalEvent.persisted) {
  397.                     location.reload(true);
  398.                 }
  399.             });
  400.             // Core Web Vital の Cumulative Layout Shift(CLS)対策のため
  401.             // img タグに width, height が付与されている.
  402.             // 630px 未満の画面サイズでは縦横比が壊れるための対策
  403.             // see https://github.com/EC-CUBE/ec-cube/pull/5023
  404.             $('.ec-grid2__cell').hide();
  405.             var removeSize = function () {
  406.                 $('.slide-item').height('');
  407.                 $('.slide-item img')
  408.                     .removeAttr('width')
  409.                     .removeAttr('height')
  410.                     .removeAttr('style');
  411.             };
  412.             var slickInitial = function(slick) {
  413.                 $('.ec-grid2__cell').fadeIn(1500);
  414.                 var baseHeight = $(slick.target).height();
  415.                 var baseWidth = $(slick.target).width();
  416.                 var rate = baseWidth / baseHeight;
  417.                 $('.slide-item').height(baseHeight * rate); // 余白を削除する
  418.                 // transform を使用することでCLSの影響を受けないようにする
  419.                 $('.slide-item img')
  420.                     .css(
  421.                         {
  422.                             'transform-origin': 'top left',
  423.                             'transform': 'scaleY(' + rate + ')',
  424.                             'transition': 'transform .1s'
  425.                         }
  426.                     );
  427.                 // 正しいサイズに近くなったら属性を解除する
  428.                 setTimeout(removeSize, 500);
  429.             };
  430.             $('.item_visual').on('init', slickInitial);
  431.             // リサイズ時は CLS の影響を受けないため属性を解除する
  432.             $(window).resize(removeSize);
  433.             $('.item_visual').slick({
  434.                 dots: false,
  435.                 arrows: false,
  436.                 responsive: [{
  437.                     breakpoint: 768,
  438.                     settings: {
  439.                         dots: true
  440.                     }
  441.                 }]
  442.             });
  443.             $('.slideThumb').on('click', function() {
  444.                 var index = $(this).attr('data-index');
  445.                 $('.item_visual').slick('slickGoTo', index, false);
  446.             })
  447.         });
  448.     </script>
  449.     <script>
  450.         $(function() {
  451.             $('.add-cart').on('click', function(event) {
  452.                 {% if form.classcategory_id1 is defined %}
  453.                     // 規格1フォームの必須チェック
  454.                     if ($('#classcategory_id1').val() == '__unselected' || $('#classcategory_id1').val() == '') {
  455.                         $('#classcategory_id1')[0].setCustomValidity('{{ 'front.product.product_class_unselected'|trans }}');
  456.                         return true;
  457.                     } else {
  458.                         $('#classcategory_id1')[0].setCustomValidity('');
  459.                     }
  460.                 {% endif %}
  461.                 {% if form.classcategory_id2 is defined %}
  462.                     // 規格2フォームの必須チェック
  463.                     if ($('#classcategory_id2').val() == '__unselected' || $('#classcategory_id2').val() == '') {
  464.                         $('#classcategory_id2')[0].setCustomValidity('{{ 'front.product.product_class_unselected'|trans }}');
  465.                         return true;
  466.                     } else {
  467.                         $('#classcategory_id2')[0].setCustomValidity('');
  468.                     }
  469.                 {% endif %}
  470.                 // 個数フォームのチェック
  471.                 if ($('#quantity').val() < 1) {
  472.                     $('#quantity')[0].setCustomValidity('{{ 'front.product.invalid_quantity'|trans }}');
  473.                     return true;
  474.                 } else {
  475.                     $('#quantity')[0].setCustomValidity('');
  476.                 }
  477.                 // IMEIフォームのチェック
  478.                 {% if Product.hasUserImeiCategory %}
  479.                     if (!$('#user_imei').length || $('#user_imei').val() == '') {
  480.                         if ($('#user_imei').length) {
  481.                             $('#user_imei')[0].setCustomValidity('{{ 'front.cart.user_imei_required'|trans }}');
  482.                         } else {
  483.                             alert('{{ 'front.cart.user_imei_required'|trans }}');
  484.                         }
  485.                         return true;
  486.                     } else {
  487.                         if ($('#user_imei').length) {
  488.                             $('#user_imei')[0].setCustomValidity('');
  489.                         }
  490.                     }
  491.                 {% endif %}
  492.                 event.preventDefault();
  493.                 $form = $('#form1');
  494.                 $.ajax({
  495.                     url: $form.attr('action'),
  496.                     type: $form.attr('method'),
  497.                     data: $form.serialize(),
  498.                     dataType: 'json',
  499.                     beforeSend: function(xhr, settings) {
  500.                         // Buttonを無効にする
  501.                         $('.add-cart').prop('disabled', true);
  502.                     }
  503.                 }).done(function(data) {
  504.                     if (data.error_code == 'OUT_OF_STOCK_ZERO' || data.error_code == 'OUT_OF_STOCK') {
  505.                         alert('この商品の在庫は残り' + data.stock_left + 'です。');
  506.                     } else if (data.error_code == 'DUPLICATE_IMEI') {
  507.                         alert('{{ 'front.shopping.duplicate_imei'|trans }}');
  508.                     } else {
  509.                         // カートブロックを更新する
  510.                         $.ajax({
  511.                             url: "{{ url('block_cart') }}",
  512.                             type: 'GET',
  513.                             dataType: 'html'
  514.                         }).done(function(html) {
  515.                             $('.ec-headerRole__cart').html(html);
  516.                         });
  517.                         // Hiển thị popup thông báo thành công sử dụng ModalComponent
  518.                         if (window.ModalUtils) {
  519.                             window.ModalUtils.showSuccess('cart-success-modal',
  520.                                 '{{ 'front.modal.cart_success.title'|trans({}, 'messages') }}',
  521.                                 '{{ 'front.modal.cart_success.message'|trans({}, 'messages') }}'
  522.                             );
  523.                         } else {
  524.                             // Fallback nếu ModalComponent chưa load
  525.                             alert('{{ 'front.modal.cart_success.title'|trans({}, 'messages') }}');
  526.                         }
  527.                     }
  528.                 }).fail(function(data) {
  529.                     alert('{{ 'front.product.add_cart_error'|trans }}');
  530.                 }).always(function(data) {
  531.                     // Buttonを有効にする
  532.                     $('.add-cart').prop('disabled', false);
  533.                 });
  534.             });
  535.         });
  536.         // Modal events are now handled by ModalComponent
  537.     </script>
  538.     <script src="{{ asset('assets/CustomComponent/modal.js') }}"></script>
  539.     </script>
  540.     <script type="application/ld+json">
  541.     {
  542.         "@context": "https://schema.org/",
  543.         "@type": "Product",
  544.         "name": "{{ Product.name }}",
  545.         "image": [
  546.             {% for img in Product.ProductImage %}
  547.                 "{{ app.request.schemeAndHttpHost }}{{ asset(img, 'save_image') }}"{% if not loop.last %},{% endif %}
  548.             {% else %}
  549.                 "{{ app.request.schemeAndHttpHost }}{{ asset(''|no_image_product, 'save_image') }}"
  550.             {% endfor %}
  551.         ],
  552.         "description": "{{ Product.description_list | default(Product.description_detail) | replace({'\n': '', '\r': ''}) | slice(0,300) }}",
  553.         {% if Product.code_min %}
  554.         "sku": "{{ Product.code_min }}",
  555.         {% endif %}
  556.         "offers": {
  557.             "@type": "Offer",
  558.             "url": "{{ url('product_detail', {'id': Product.id}) }}",
  559.             "priceCurrency": "{{ eccube_config.currency }}",
  560.             "price": {{ Product.getPrice02IncTaxMin ? Product.getPrice02IncTaxMin : 0}},
  561.             "availability": "{{ Product.stock_find ? "InStock" : "OutOfStock" }}"
  562.         }
  563.     }
  564.     </script>
  565. {% endblock %}
  566. {% block main %}
  567.     <!-- breadcrumb section -->
  568.     <div class="breadcrumb-section">
  569.         <div class="container-1360 border-box px-40">
  570.             <nav class="breadcrumb">
  571.                 <a href="{{ url('homepage') }}" class="breadcrumb__item text-black">ホーム</a>
  572.                 <img src="{{ asset('assets/img/default/icons/icon-breadcrumb-arrow.svg') }}" alt=">" class="breadcrumb__separator">
  573.                 <a href="{{ url('product_list') }}" class="breadcrumb__item text-black">商品一覧</a>
  574.                 <img src="{{ asset('assets/img/default/icons/icon-breadcrumb-arrow.svg') }}" alt=">" class="breadcrumb__separator">
  575.                 <span class="breadcrumb__item breadcrumb__item--current text-black">{{ Product.name }}</span>
  576.             </nav>
  577.         </div>
  578.     </div>
  579.     <!-- product detail section -->
  580.     <div class="product-detail-section container-1200">
  581.         <div class="product-detail-wrapper">
  582.             <div class="product-detail-left">
  583.                 <!-- Main gallery (using existing slick) -->
  584.                 <div class="item_visual">
  585.                     {% for ProductImage in Product.ProductImage %}
  586.                         <div class="slide-item"><img src="{{ asset(ProductImage, 'save_image') }}" alt="{{ loop.first ? Product.name : '' }}" width="550" height="550"{% if loop.index > 1 %} loading="lazy"{% endif %}></div>
  587.                     {% else %}
  588.                         <div class="slide-item"><img src="{{ asset(''|no_image_product, 'save_image') }}" alt="{{ loop.first ? Product.name : '' }}" width="550" height="550"></div>
  589.                     {% endfor %}
  590.                 </div>
  591.                 <!-- Thumbs below -->
  592.                 <div class="product-detail-left-bottom">
  593.                     <div class="item_nav">
  594.                         {% for ProductImage in Product.ProductImage %}
  595.                             <div class="slideThumb" data-index="{{ loop.index0 }}">
  596.                                 <div class="product-thumbnail"><img src="{{ asset(ProductImage, 'save_image') }}" alt="" width="133" height="133" loading="lazy"></div>
  597.                             </div>
  598.                         {% endfor %}
  599.                     </div>
  600.                 </div>
  601.             </div>
  602.             <div class="product-detail-right">
  603.                 <!-- Product Title -->
  604.                 <h1 class="product-title">{{ Product.name }}</h1>
  605.                 <!-- Product Price -->
  606.                 <div class="product-price">
  607.                     {% if Product.hasProductClass -%}
  608.                         <span class="product-price-value price02-default text-color-primary" data-original-price="{{ Product.getPrice02IncTaxMin|price }}{% if Product.getPrice02IncTaxMin != Product.getPrice02IncTaxMax %} ~ {{ Product.getPrice02IncTaxMax|price }}{% endif %}">{{ Product.getPrice02IncTaxMin|price }}{% if Product.getPrice02IncTaxMin != Product.getPrice02IncTaxMax %} ~ {{ Product.getPrice02IncTaxMax|price }}{% endif %}</span>
  609.                     {% else %}
  610.                         <span class="product-price-value price02-default text-color-primary" data-original-price="{{ Product.getPrice02IncTaxMin|price }}">{{ Product.getPrice02IncTaxMin|price }}</span>
  611.                     {% endif %}
  612.                 </div>
  613.                 <!-- Quantity and Add to Cart -->
  614.                 <form method="post" id="form1" action="{{ url('product_add_cart', {'id': Product.id}) }}">
  615.                 <div class="product-quantity-wrapper">
  616.                     {% if Product.stock_find %}
  617.                         <div class="product-quantity-left">
  618.                             <div class="btn-group__header group__header-mobile mb-12">数量</div>
  619.                                     {{ form_widget(form._token) }}
  620.                                     {{ form_widget(form.product_id) }}
  621.                                     {{ form_widget(form.ProductClass) }}
  622.                                     {% if form.classcategory_id1 is defined %}
  623.                                         <div class="classcategory-wrapper" data-category-type="1">
  624.                                             {{ form_widget(form.classcategory_id1) }}
  625.                                             {{ form_errors(form.classcategory_id1) }}
  626.                                         </div>
  627.                                     {% endif %}
  628.                                     {% if form.classcategory_id2 is defined %}
  629.                                         <div class="classcategory-wrapper" data-category-type="2">
  630.                                             {{ form_widget(form.classcategory_id2) }}
  631.                                             {{ form_errors(form.classcategory_id2) }}
  632.                                         </div>
  633.                                     {% endif %}
  634.                                     <div class="product-quantity">
  635.                                         <button type="button" class="quantity-btn quantity-btn--minus item-minus{% if Product.getProductType() == constant('Eccube\\Entity\\Product::PRODUCT_TYPE_DATA_PACKAGE') %} btn-disabled{% endif %}">
  636.                                             <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect y="5" width="12" height="2" fill="#333333"/></svg>
  637.                                         </button>
  638.                                         <div class="quantity-value">
  639.                                             {{ form_widget(form.quantity, {
  640.                                                 'attr': {
  641.                                                     'class': 'custom-quantity hide-number-input h-100 bg-white',
  642.                                                     'readonly': 'readonly',
  643.                                                 }
  644.                                             }) }}
  645.                                         </div>
  646.                                         <button type="button" class="quantity-btn quantity-btn--plus item-plus{% if Product.getProductType() == constant('Eccube\\Entity\\Product::PRODUCT_TYPE_DATA_PACKAGE') %} btn-disabled{% endif %}">
  647.                                             <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 5H12V7H7V12H5V7H0V5H5V0H7V5Z" fill="#333333"/></svg>
  648.                                         </button>
  649.                                         {{ form_errors(form.quantity) }}
  650.                                     </div>
  651.                         </div>
  652.                         <!-- Add to Cart Button -->
  653.                         <button type="submit" class="btn-add-to-cart add-cart">
  654.                             <span>カートに入れる</span>
  655.                             <div class="btn-add-to-cart__icon">
  656.                                 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.252 6.00098L10.249 6.00391L10.252 6.00684L5.72656 10.5312L4.5957 9.40039L7.19434 6.80273H1.72852V5.20312H7.19238L4.5957 2.60742L5.72656 1.47656L10.252 6.00098Z" fill="#FBE800"/></svg>
  657.                             </div>
  658.                         </button>
  659.                         {% else %}
  660.                         <!-- button out of stock -->
  661.                         <button class="btn-add-to-cart btn-add-to-cart--out-of-stock" disabled="disabled">
  662.                             <span>{{ 'front.product.out_of_stock'|trans }}</span>
  663.                         </button>
  664.                     {% endif %}
  665.                 </div>
  666.                 {% if form.user_imei is defined %}
  667.                     <div class="user-imei-form mt-4">
  668.                         <div class="btn-group__header group__header-mobile mb-12">IMEI番号</div>
  669.                         {{ form_widget(form.user_imei) }}
  670.                         {{ form_errors(form.user_imei) }}
  671.                     </div>
  672.                 {% endif %}
  673.                 </form>
  674.                 <!-- Divider -->
  675.                 <div class="product-divider"></div>
  676.                 <!-- Product Description Section -->
  677.                 <div class="btn-group__header mb-16">商品説明</div>
  678.                 <div class="product-description">
  679.                     {{ Product.description_detail | nl2br }}
  680.                 </div>
  681.                 {% if Product.product_note is not empty %}
  682.                     <div class="btn-group__header mb-16">備考</div>
  683.                     <div class="product-remarks">{{ Product.product_note | nl2br }}</div>
  684.                 {% endif %}
  685.             </div>
  686.         </div>
  687.         <!-- border bottom -->
  688.         <div class="product-detail-border-bottom"></div>
  689.         <!-- btn bottom -->
  690.         <div class="btn-bottom">
  691.             <button class="btn-return" onclick="history.back()">
  692.                 <div class="btn-return__icon">
  693.                     <svg width="9" height="10" viewBox="0 0 9 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 4.52441L0.00292969 4.52734L0 4.53027L4.52539 9.05469L5.65625 7.92383L3.05762 5.32617H8.52344V3.72656H3.05957L5.65625 1.13086L4.52539 0L0 4.52441Z" fill="#FBE800"/></svg>
  694.                 </div>
  695.                 <span>前のページに戻る</span>
  696.             </button>
  697.         </div>
  698.     </div>
  699.     <!-- Modal -->
  700.     {% include 'CustomComponent/modal.twig' with {
  701.         id: 'cart-success-modal',
  702.         title: 'front.modal.cart_success.title'|trans({}, 'messages'),
  703.         message: 'front.modal.cart_success.message'|trans({}, 'messages'),
  704.         showCancelButton: true,
  705.         cancelText: 'front.modal.cart_success.cancel_text'|trans({}, 'messages'),
  706.         showActionButton: true,
  707.         actionText: 'front.modal.cart_success.action_text'|trans({}, 'messages'),
  708.         actionUrl: url('cart'),
  709.         autoRedirect: false,
  710.         redirectUrl: '',
  711.         redirectDelay: 0,
  712.         reloadOnCancel: true,
  713.         reloadOnError: false
  714.     } %}
  715.   <script>
  716.     // slick js
  717.     $(document).ready(function() {
  718.       $('.item_visual').slick({
  719.         dots: false,
  720.         arrows: false,
  721.         responsive: [{
  722.           breakpoint: 768,
  723.           settings: {
  724.             dots: false
  725.           }
  726.         }]
  727.       });
  728.       $('.slideThumb').on('click', function() {
  729.         var index = $(this).attr('data-index');
  730.         $('.item_visual').slick('slickGoTo', index, false);
  731.       })
  732.     });
  733.   </script>
  734. {% endblock %}