Răsfoiți Sursa

Variant price table above dropzone + rename cart total — v1.5.11 -> v1.5.12

- Render a read-only variant price/discount table above the upload
  dropzone (woocommerce_single_product_summary priority 34). Shows unit
  price via wc_price() and the tiered quantity discount for each
  variant. Out-of-stock rows greyed out. Pure overview — no radio/qty/
  add-to-cart.
- Hide WC's native price-range span ("10,00 Kč – 40,00 Kč") on FPP
  products. The new table already communicates per-variant pricing.
- Reword WC cart "Estimated total" → "Total" ("Součet" in cs_CZ) via
  gettext_woocommerce filter routed through our own textdomain, so the
  translation lives in our .po.

New i18n: Price list, Variant, Unit price, Quantity discount, Total
(cs_CZ: Ceník, Varianta, Cena za kus, Množstevní sleva, Součet).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dalibor Votruba 2 luni în urmă
părinte
comite
224a12b555

+ 3 - 1
studiou-wc-free-photo-product/CLAUDE.md

@@ -2,7 +2,7 @@
 
 ## Project Overview
 
-WordPress/WooCommerce plugin called **studiou-wc-free-photo-product** (QDR - Studiou WC Free Photo Product) v1.5.11.
+WordPress/WooCommerce plugin called **studiou-wc-free-photo-product** (QDR - Studiou WC Free Photo Product) v1.5.12.
 Allows publishing special variable products where customers upload custom raw images to be printed for a price.
 
 ## Initial Requirements
@@ -50,6 +50,8 @@ Allows publishing special variable products where customers upload custom raw im
 **FPP product page (`is_product()` + `is_fpp_product()`):**
 
 1. `maybe_suppress_pvtfw()` detaches pvtfw's `print_table` and `available_options_btn` hooks on `wp` priority 5.
+1a. (1.5.12+) `maybe_suppress_pvtfw()` also detaches `woocommerce_template_single_price` at priority 10 so WC's price-range span (e.g. "10,00 Kč – 40,00 Kč") is hidden on FPP products — the per-variant table below replaces it.
+1b. (1.5.12+) `render_variant_price_table()` on `woocommerce_single_product_summary` priority 34 renders a read-only server-side table (`views/single-product-price-table.php`) above the dropzone: one row per variant with unit price (via `wc_price()`) and tiered quantity-discount badges. Pure overview — no radio/qty/add-to-cart.
 2. `render_upload_area()` on `woocommerce_single_product_summary` priority 35 passes variants + max uploads + existing batch files + hero preview URL to JS via `wp_localize_script('studiou-wcfpp-frontend', 'studiouFppCardData', ...)`.
 3. Browser renders:
    - **Dropzone** (multi-file `<input type="file" multiple>`) — accepts drag-drop + click-to-browse. Click-to-browse opens the native file picker via `$fileInput[0].click()` (NOT `$fileInput.trigger('click')`), because the `<input>` lives inside the dropzone element and a bubbling jQuery trigger re-entered the dropzone's own click handler, causing `Maximum call stack size exceeded` on mobile where the browser synthesizes an extra click when the picker closes (fixed in 1.5.11).

+ 98 - 0
studiou-wc-free-photo-product/assets/css/frontend.css

@@ -925,3 +925,101 @@ body.studiou-fpp-lightbox-open {
 .studiou-fpp-lightbox-close:hover {
     background: rgba(255, 255, 255, 0.25);
 }
+
+/* ==========================================================================
+   Read-only variant price table (rendered above the upload dropzone, 1.5.12+)
+   ========================================================================== */
+
+.studiou-fpp-price-table-wrap {
+    margin: 18px 0 24px;
+}
+
+.studiou-fpp-price-table-title {
+    margin: 0 0 10px;
+    font-size: 1.15em;
+}
+
+.studiou-fpp-price-table {
+    width: 100%;
+    border-collapse: collapse;
+    border: 1px solid #e3e3e3;
+    background: #fff;
+    font-size: 0.95em;
+}
+
+.studiou-fpp-price-table thead th {
+    background: #f7f7f7;
+    text-align: left;
+    padding: 8px 12px;
+    font-weight: 600;
+    border-bottom: 2px solid #ddd;
+    color: #444;
+}
+
+.studiou-fpp-price-table tbody td {
+    padding: 10px 12px;
+    border-bottom: 1px solid #eee;
+    vertical-align: top;
+}
+
+.studiou-fpp-price-table tbody tr:last-child td {
+    border-bottom: none;
+}
+
+.studiou-fpp-price-table-variant {
+    font-weight: 600;
+}
+
+.studiou-fpp-price-table-price {
+    white-space: nowrap;
+    font-weight: 600;
+}
+
+.studiou-fpp-price-table-row-oos {
+    opacity: 0.5;
+}
+
+.studiou-fpp-price-table-tiers {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+}
+
+.studiou-fpp-price-table-tier {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    background: #fff7e6;
+    border: 1px solid #f0c36d;
+    border-radius: 3px;
+    padding: 1px 7px;
+    font-size: 0.9em;
+    white-space: nowrap;
+}
+
+.studiou-fpp-price-table-tier-qty {
+    color: #6a4b00;
+    font-weight: 600;
+}
+
+.studiou-fpp-price-table-tier-pct {
+    color: #1e7e34;
+    font-weight: 700;
+}
+
+.studiou-fpp-price-table-no-discount {
+    color: #999;
+}
+
+@media (max-width: 640px) {
+    .studiou-fpp-price-table thead th,
+    .studiou-fpp-price-table tbody td {
+        padding: 7px 8px;
+    }
+    .studiou-fpp-price-table-discount {
+        font-size: 0.9em;
+    }
+}

+ 24 - 0
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-single-product.php

@@ -14,6 +14,8 @@ class Studiou_WC_FPP_Single_Product {
         // Render upload cards UI at priority 35 on single product page.
         // Also suppress pvtfw's variant table + available-options button on FPP products.
         add_action('wp', array($this, 'maybe_suppress_pvtfw'), 5);
+        // Read-only price overview rendered right above the dropzone (1.5.12+).
+        add_action('woocommerce_single_product_summary', array($this, 'render_variant_price_table'), 34);
         add_action('woocommerce_single_product_summary', array($this, 'render_upload_area'), 35);
 
         // Add a body class on FPP product pages for CSS scoping
@@ -67,6 +69,11 @@ class Studiou_WC_FPP_Single_Product {
             $available_btn = PVTFW_AVAILABE_BTN::instance();
             remove_action('woocommerce_single_product_summary', array($available_btn, 'available_options_btn'), 11);
         }
+
+        // Hide WC's native price-range element (e.g. "10,00 Kč – 40,00 Kč") on FPP products.
+        // The variant price table we render at priority 34 already communicates per-variant
+        // pricing, and the range is visually noisy above it.
+        remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10);
     }
 
     public function body_class($classes) {
@@ -136,6 +143,23 @@ class Studiou_WC_FPP_Single_Product {
         }
     }
 
+    /**
+     * Read-only variant price/discount table rendered above the dropzone (1.5.12+).
+     * Customers see all variant prices and the tiered quantity discounts before uploading.
+     * Purely informational — no radio/select/qty/add-to-cart.
+     */
+    public function render_variant_price_table() {
+        global $product;
+        if (!$product || !Studiou_WC_FPP_Product::is_fpp_product($product->get_id())) {
+            return;
+        }
+        $variants = $this->build_variant_list($product);
+        if (empty($variants)) {
+            return;
+        }
+        include STUDIOU_WCFPP_PLUGIN_DIR . 'views/single-product-price-table.php';
+    }
+
     /**
      * Produce the array of variation descriptors consumed by the upload-card JS.
      * One entry per in-stock, purchasable variation: id, label, base_price, tiers.

+ 16 - 1
studiou-wc-free-photo-product/languages/studiou-wc-free-photo-product-cs_CZ.po

@@ -3,7 +3,7 @@
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.5.11\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.5.12\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: 2026-04-02 00:00+0000\n"
@@ -91,6 +91,21 @@ msgstr "Zavřít"
 msgid "Enlarge"
 msgstr "Zvětšit"
 
+msgid "Price list"
+msgstr "Ceník"
+
+msgid "Variant"
+msgstr "Varianta"
+
+msgid "Unit price"
+msgstr "Cena za kus"
+
+msgid "Quantity discount"
+msgstr "Množstevní sleva"
+
+msgid "Total"
+msgstr "Součet"
+
 msgid "Media Categories"
 msgstr "Kategorie médií"
 

+ 21 - 1
studiou-wc-free-photo-product/languages/studiou-wc-free-photo-product.pot

@@ -2,7 +2,7 @@
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.5.11\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.5.12\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@@ -114,6 +114,26 @@ msgstr ""
 msgid "Enlarge"
 msgstr ""
 
+#: views/single-product-price-table.php
+msgid "Price list"
+msgstr ""
+
+#: views/single-product-price-table.php
+msgid "Variant"
+msgstr ""
+
+#: views/single-product-price-table.php
+msgid "Unit price"
+msgstr ""
+
+#: views/single-product-price-table.php
+msgid "Quantity discount"
+msgstr ""
+
+#: studiou-wc-free-photo-product.php
+msgid "Total"
+msgstr ""
+
 #: includes/class-studiou-wc-fpp-db.php
 msgid "Media Categories"
 msgstr ""

+ 6 - 0
studiou-wc-free-photo-product/readme.md

@@ -2,6 +2,12 @@
 
 WordPress (6.9.4+) / WooCommerce (10.6.2+) plugin that allows publishing special variable products with custom raw image upload for photo printing. Customers upload one or more photos on the product page and compose an order where each uploaded photo can be printed in a different variant at its own quantity. Uploaded files are stored in the WordPress media library under a configurable media category.
 
+## What's new in 1.5.12
+
+- **Read-only variant price table above the dropzone.** Customers see all variant prices plus the tiered quantity-discount schedule before they upload. Server-side table uses `wc_price()` so formatting follows the WC pipeline. Out-of-stock rows greyed out. Rendered on `woocommerce_single_product_summary` priority 34 (right before the upload area at priority 35).
+- **WC price-range hidden on FPP products.** The "10,00 Kč – 40,00 Kč" span that WC outputs by default is suppressed via `remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10)` — the new variant table already communicates per-variant pricing.
+- **Cart "Estimated total" → "Total" (cs_CZ: "Součet").** `gettext_woocommerce` filter reroutes WC's core string through our own textdomain so the translation sits in our .po and stays locale-aware.
+
 ## What's new in 1.5.11
 
 - **Mobile upload fix.** Tapping the dropzone on mobile raised `Maximum call stack size exceeded` and prevented the file picker from opening. The `<input type="file">` lives inside the dropzone, so `$fileInput.trigger('click')` dispatched a bubbling jQuery event that re-entered the dropzone's own click handler in an infinite loop (mobile surfaces it because the browser synthesizes an extra click when the picker closes). Fix: open the picker via native `$fileInput[0].click()` (no bubbling jQuery event) and bail out of the dropzone handler when the click originates on the input itself.

+ 16 - 2
studiou-wc-free-photo-product/studiou-wc-free-photo-product.php

@@ -3,7 +3,7 @@
  * Plugin Name: QDR - Studiou WC Free Photo Product
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-free-photo-product
  * Description: Allows publishing special WooCommerce products with variants and custom raw image upload for photo printing
- * Version: 1.5.11
+ * Version: 1.5.12
  * Requires at least: 6.9.4
  * Requires PHP: 8.2
  * Requires Plugins: woocommerce, product-variant-table-for-woocommerce
@@ -22,7 +22,7 @@ if (!defined('WPINC')) {
     die;
 }
 
-if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.5.11');
+if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.5.12');
 if (!defined('STUDIOU_WCFPP_PLUGIN_DIR')) define('STUDIOU_WCFPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_URL')) define('STUDIOU_WCFPP_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));
@@ -62,6 +62,20 @@ class Studiou_WC_Free_Photo_Product {
         add_action('wp_enqueue_scripts', array($this, 'register_frontend_assets'));
         $this->init_components();
         add_action('before_woocommerce_init', array($this, 'declare_hpos_compatibility'));
+        // Override WC core strings with FPP-specific wording (e.g. cart "Estimated total"
+        // → "Součet" in cs_CZ). Translation lives in our .po so it stays locale-aware.
+        add_filter('gettext_woocommerce', array($this, 'override_wc_strings'), 10, 2);
+    }
+
+    /**
+     * Reword selected WooCommerce core strings via our own textdomain so translations
+     * stay centralized in languages/studiou-wc-free-photo-product-*.po.
+     */
+    public function override_wc_strings($translation, $text) {
+        if ($text === 'Estimated total') {
+            return __('Total', 'studiou-wc-free-photo-product');
+        }
+        return $translation;
     }
 
     private function load_dependencies() {

+ 51 - 0
studiou-wc-free-photo-product/views/single-product-price-table.php

@@ -0,0 +1,51 @@
+<?php
+if (!defined('WPINC')) {
+    die;
+}
+
+/** @var array<int,array{id:int,label:string,base_price:float,tiers:array,in_stock:bool}> $variants */
+?>
+<div class="studiou-fpp-price-table-wrap">
+    <h3 class="studiou-fpp-price-table-title"><?php esc_html_e('Price list', 'studiou-wc-free-photo-product'); ?></h3>
+    <table class="studiou-fpp-price-table">
+        <thead>
+            <tr>
+                <th class="studiou-fpp-price-table-variant"><?php esc_html_e('Variant', 'studiou-wc-free-photo-product'); ?></th>
+                <th class="studiou-fpp-price-table-price"><?php esc_html_e('Unit price', 'studiou-wc-free-photo-product'); ?></th>
+                <th class="studiou-fpp-price-table-discount"><?php esc_html_e('Quantity discount', 'studiou-wc-free-photo-product'); ?></th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php foreach ($variants as $v) :
+                $row_classes = array('studiou-fpp-price-table-row');
+                if (empty($v['in_stock'])) {
+                    $row_classes[] = 'studiou-fpp-price-table-row-oos';
+                }
+                ?>
+                <tr class="<?php echo esc_attr(implode(' ', $row_classes)); ?>">
+                    <td class="studiou-fpp-price-table-variant"><?php echo esc_html($v['label']); ?></td>
+                    <td class="studiou-fpp-price-table-price"><?php echo wp_kses_post(wc_price($v['base_price'])); ?></td>
+                    <td class="studiou-fpp-price-table-discount">
+                        <?php if (!empty($v['tiers'])) : ?>
+                            <ul class="studiou-fpp-price-table-tiers">
+                                <?php foreach ($v['tiers'] as $tier) :
+                                    $from = isset($tier['from_qty']) ? (int) $tier['from_qty'] : 0;
+                                    $pct  = isset($tier['percent'])  ? (float) $tier['percent'] : 0.0;
+                                    if ($from <= 0 || $pct <= 0) continue;
+                                    $pct_str = rtrim(rtrim(number_format($pct, 2, '.', ''), '0'), '.');
+                                    ?>
+                                    <li class="studiou-fpp-price-table-tier">
+                                        <span class="studiou-fpp-price-table-tier-qty"><?php echo esc_html($from); ?>+</span>
+                                        <span class="studiou-fpp-price-table-tier-pct">&minus;<?php echo esc_html($pct_str); ?>&nbsp;%</span>
+                                    </li>
+                                <?php endforeach; ?>
+                            </ul>
+                        <?php else : ?>
+                            <span class="studiou-fpp-price-table-no-discount">&mdash;</span>
+                        <?php endif; ?>
+                    </td>
+                </tr>
+            <?php endforeach; ?>
+        </tbody>
+    </table>
+</div>