# Katalog ve Ürün Sayfaları

https://dev.wisecp.com/tr/katalog-ve-urun-sayfalari

Genel kataloğun tamamını üç view taşır; zor iş, JavaScript'in hemen değiştirmeyeceği bir fiyat yazmaktır.

## Genel Bakış

Katalog, bir temanın aynı sayıyı iki kez ürettiği tek bölümdür: sunucu ilk boyama için yazar, tarayıcı faturalama butonu her hareket ettiğinde yeniden yazar. Bir kuruş fark, sayfanın titremesidir.

Tutarlar çevrilmiş ve promosyonu çözülmüş gelir, yani view'a hiçbir fiyat mantığı ait değildir.

## Yapı

Üç view, tek fiyat mekanizması. Kategori sayfası iki düzen taşır ve aralarında çalışma anında seçer. Fiyatlı ürün yoksa aşağıdaki dalların hepsi atlanır.

| View | Cevap verdiği | Ana veri |
| --- | --- | --- |
| `views/products/category` | Her kategori adresi, üst ve yaprak | `$mode`, `$tabs` ya da `$cards` artı `$plans`, `$billing_cycles`, `$price_mode` |
| `views/products/software` | Mağaza kökü ve her mağaza kategorisi | `$products`, `$filters`, `$page_size`, `$total_count` |
| `views/products/detail` | Tek bir yazılım ürününün kendi sayfası | `$product`, `$prices`, `$gallery`, `$included`, `$versions`, `$faq` |

- **components/plan-grid.tpl**: Dikey kartlar. `$plans` bekler ve `$billing_cycles`'ı üst kapsamdan okur, ikisini birden geçin.
- **components/plan-rows.tpl**: Yatay satırlar. Her planda `features` yerine `specs` ve `chips` bekler.
- **Products::catalog_plans()**: İki şekli de kurar; üçüncü argüman birini seçer.

## Adım Adım

### İki Kategori Modunu da Karşılayın

İçerik bloğunun içindeki ilk iş `$mode`'a dallanmak olsun.

1. **tabs**: her çocuk bir yapraktır, sayfa alt aile sekmeleri ve pano başına bir ızgara gösterir. Her `$tabs` kaydı kendi başlığını, meta'sını, hero arka planını ve planlarını taşır.
2. **drilldown**: kategorinin hem alt kategorileri *hem* kendi planları vardır — `$cards` ve `$plans`.
3. Faturalama butonunu yalnız değiştirilecek bir şey varken gösterin: `{if $billing_cycles|@count > 1}`.

### Planı Basın, Fiyat Mantığını Değil

1. Kart öğesine `plan-card`, yatay yerleşimde `plan-row` sınıfını verin.
2. Aşağıdaki sözleşmedeki `data-*` özniteliklerini ekleyin.
3. Tükendi durumu için harekete geçirici mesajı `$plan.in_stock` ile değiştirin; kartı gizlemeyin.
4. `$plan.link`'i geldiği gibi kullanın.

### Fiyatı İki Kez, Aynı Şekilde Yazın

Sunucu sayfa boş kalmasın diye yazar, script üzerine yazar; ikisi aynı string'i üretmek zorundadır.

1. Sunucu değerini şu düğüme basın: `<span data-role="price-now">{$plan.price_now}</span>`.
2. Aylık son ekini yanına basın, dönem toplamında gizleyin: `{if $plan.is_period}d-none{/if}`.
3. Türetilmiş figürleri boş bırakın — onlar script'in işi.
4. Sayfa bağlamını sarmalayıcıya koyun.

### Kategorinin Yerleşim Tercihine Uyun

Bir kategori ızgara yerine yatay satır isteyebilir; tercih `$layout` olarak gelir.

1. Tek bir yerde, include'da dallanın: ya satır ya ızgara, asla yarı dönüşmüş bir kart değil.
2. Izgara planında `features` vardır; satır planında `specs` ve `chips` vardır, `features` yoktur.
3. `value|label` çifti taşımayan özellik metninde her satır çipe dönüşür ve özellik sütunları boş çıkar. Bu bir hata değil, veridir.

## Referans

### Tek Bir Planın Şekli

```php
public static function catalog_plans(int $categoryId, string $type, string $layout = 'grid'): array;

// $categoryId  ürünleri istenen kategori
// $type        ürün türü, yapılandırma adresinin kullandığı parçanın aynısı
// $layout      'grid' ya da 'rows'; dönen HER planın ŞEKLİNİ değiştirir
```

```php
$plan = [
    'id'          => 18,                 // ürün id'si
    'title'       => 'Starter',
    'tagline'     => 'İlk siteniz için',
    'popular'     => true,               // ürünün kendi seçeneklerinden
    'in_stock'    => true,               // '' stok sınırsız demek, 0 tükendi demek
    'prices'      => [                   // YALNIZ gerçekten fiyatı olan döngüler
        'monthly' => 4.90,
        'annual'  => 49.00,
    ],
    'currency'    => 'USD',              // görüntü kodu, data-currency özniteliği için
    'currency_id' => 2,                  // görüntü para birimi id'si, biçimlendirici kullanır
    'link'        => '/configure/hosting/18',
    'features'    => ['10 GB disk', '1 alan adı'],
];

// $layout = 'rows' iken son anahtar yerine iki anahtar gelir:
//   'specs' => [['value' => '10 GB', 'label' => 'Disk'], ...]   kıyaslanabilir sütunlar
//   'chips' => ['Ücretsiz taşıma', ...]                         düz satırlar
```

### İlk Boyama Fiyatı Nasıl Üretilir

```php
public static function seed_plan_prices(array &$plans, string $cycle, bool $periodMode): void;

// $cycle       bu sayfanın varsayılan faturalama döngüsü
// $periodMode  true tam dönem toplamını basar, false aylık karşılığını basar
//
// Her plana iki anahtar ekler:
//   price_now  şablonun bastığı biçimlenmiş string
//   is_period  o string bir dönem toplamı mı ("/ay" son ekini o belirler)
//
// $cycle için fiyatı olmayan plan sıfır değil, price_now = '' alır.
// Aylık fiyatı olmayan plan, $periodMode false olsa bile dönem moduna zorlanır.

public static function plan_cycles(array $tabs): array;
// [['key' => 'monthly', 'label' => 'Aylık'], ['key' => 'annual', 'label' => 'Yıllık']]
// Yalnız sayfadaki en az bir planın gerçekten fiyatladığı döngüler. Sabit gösterim sırası.

public static function price_monthly_equivalent(): bool;
// $periodMode'un arkasındaki GENEL ayar. Kurulumun tema yapılandırmasında yaşar,
// temanızın kendi ayar şemasında DEĞİL, yani bir tema onu zorlayamaz.
```

### data-* Sözleşmesi

Birini yeniden adlandırın, fiyat sessizce güncellenmeyi bırakır. İşleyici `data-billing` taşıyan her öğeyi grup, içindeki her `.plan-card` ve `.plan-row` öğesini kart sayar.

- **data-billing**: Sarmalayıcıda: `$default_cycle`'dan açılış döngüsü ve grup seçicisi; geçiş butonu onu değiştirir.
- **.plan-card ve .plan-row**: Kart seçicisi: ızgara birincisini, satır ikincisini kullanır. Sınıf ekleyin, bunları değiştirmeyin.
- **data-price-mode**: Sarmalayıcıda: `$price_mode`'dan `monthly` ya da `period`. Sayfa değeri, tema ayarı değil.
- **data-currency**: Sarmalayıcıda ve her kartta; kart kazanır, karışık para birimleri doğru biçimlenir.
- **data-monthly, data-annual ve diğerleri**: Kartta: döngü başına bir ham ondalık, `|default:''` ile — fiyatsız döngü sıfır değil boş kalsın.
- **data-role**: Script'in yazdığı düğümler: `price-now`, `price-suffix`, `price-was`, `savings`, `cycle-total`, `cycle-mo`, `currency-label`.
- **data-save-text**: Sarmalayıcıda: kazanç rozetinin etiketi, dil dosyasından — script'in çevirmeni yok.

### Faturalama Geçişi Sözleşmesi

Buton seçtiği döngüyü `data-cycle` içinde taşır, asla `data-billing` içinde değil: işleyici en yakın `data-billing` atasını arar, o özniteliği taşıyan buton kendi grubu olur.

- **data-billing-toggle**: Buton grubunda. Etkin sınıfı gruptaki butonlar arasında taşır, böylece birden çok geçiş bir arada durabilir.
- **data-action="set-billing"**: Her butonda. Temanın devredilmiş tıklama işleyicisi buna göre dallanır; kendi dinleyiciniz gerekmez.
- **data-cycle**: Her butonda: `monthly`, `quarterly`, `semiannually`, `annual`, `biennial`, `triennial`. `$c.key`'den, etiketi `$c.label`'dan yazın.

## Örnek

```php
// controllers/website/products.php, drilldown dalı

$plans = Products::catalog_plans($categoryId, $kind, $layout);

// Genel ayar, tersine çevrilmiş: aylık karşılık AÇIK ise dönem modu KAPALI.
$period_mode = !Theme::price_monthly_equivalent();

// Sayfanın varsayılan döngüsünde her plana price_now + is_period yazar.
Products::seed_plan_prices($plans, $default_cycle, $period_mode);

$this->addData("mode",           "drilldown");
$this->addData("plans",          $plans);
$this->addData("cards",          $cards);
$this->addData("layout",         $layout);
$this->addData("billing_cycles", Products::plan_cycles([['plans' => $plans]]));
$this->addData("default_cycle",  $default_cycle);
$this->addData("price_mode",     $period_mode ? "period" : "monthly");
```

```smarty
{* Bölüm sarmalayıcısı, fiyat script'inin okuduğu sayfa düzeyi bağlamı taşır. *}
<section data-billing="{$default_cycle}" data-currency="{$selected_currency_code}" data-price-mode="{$price_mode}">

    {if $layout == 'rows'}
        {include file='components/plan-rows.tpl' plans=$plans billing_cycles=$billing_cycles}
    {else}
        {include file='components/plan-grid.tpl' plans=$plans billing_cycles=$billing_cycles}
    {/if}

</section>

{* components/plan-grid.tpl, tek bir kartın fiyat bandı *}
<div class="card plan-card{if !$plan.in_stock} plan-soldout{/if}"
     data-currency="{$plan.currency}"
     data-monthly="{$plan.prices.monthly|default:''}"
     data-annual="{$plan.prices.annual|default:''}"
     data-biennial="{$plan.prices.biennial|default:''}">

  <div class="plan-price price-band">
    {* Script doldurur. Sunucuda bilerek boş. *}
    <del class="price-was num-tabular d-none" data-role="price-was"></del>
    <span class="badge d-none" data-role="savings"></span>

    {* Sunucu basar ki ilk boyama hiç boş kalmasın, sonra script yüklemede
       AYNI string ile üzerine yazar. *}
    <span class="price-now num-tabular" data-role="price-now">{$plan.price_now|default:''}</span>
    <span class="{if $plan.is_period}d-none{/if}" data-role="price-suffix">{lang key='website/products/category-per-month'}</span>

    {* Döngü başına bir satır; toplamlar script'in işi. *}
    {foreach $billing_cycles as $c}
      <tr data-cycle="{$c.key}">
        <td>{$c.label}</td>
        <td data-role="cycle-total"></td>
        <td data-role="cycle-mo"></td>
      </tr>
    {/foreach}
  </div>
</div>
```

## Tuzaklar

> **Bir kuruşluk uyuşmazlık gözle görülür bir titremedir**
> 
> `toFixed(2)` saklanan double'ı düzeltmesiz yuvarlar, sunucu biçimlendiricisi yarımı çifte yuvarlar, PHP'nin yuvarlaması önce kayan nokta düzeltmesi yapar. Üçü beşle biten değerlerde ayrışır: 237.905 iki farklı string olur ve sayfa zıplar. Tohumlama yardımcısı `sprintf('%.2f')` ile ön yuvarlar; siz de aynısını yapın.

> **Yatay yerleşimin arkasında iki anahtar var**
> 
> Panel seçimi `list_template` seçeneğine yazar (2 = düz); sayfa ise `layout` okur ve `rows` bekler. Aralarında eşleme yok, düz seçilmiş kategori yine ızgara görünür; ana sayfa rafı ikisini de okur.

> **Aylık karşılık sizin temanızın ayarı değildir**
> 
> Sistem geneli bir seçenektir, tema ayarları şemasından bilerek çıkarılmıştır. Onu `$price_mode` üzerinden okuyun ve kendi manifestinize benzeri bir anahtar eklemeyin.

> **Yapıştırılan gömülü kod türetilmiş figürleri geciktirir**
> 
> Kategorinin zengin metin gövdesi ham çıkar; içindeki senkron bir üçüncü parti etiketi ayrıştırmayı durdurur. Ana fiyat kurtulur çünkü onu sunucu yazmıştır, türetilmiş figürler bekler.

> **Stok anlıktır, planla cache'lenmez**
> 
> Plan listeleri cache'lenir; stok işareti cache okunduktan sonra tazelenir. `$plan.in_stock`'u güncel, gerisini bir saate kadar eski kabul edin; ikisini karıştıran bir parçayı cache'lemeyin.

## İlgili Makaleler

- [Sayfa Ekranları](https://dev.wisecp.com/tr/sayfa-yuzeyleri)
- [Sepet ve Ödeme](https://dev.wisecp.com/tr/sepet-ve-odeme)
- [Şablon Değişkenleri](https://dev.wisecp.com/tr/sablon-degiskenleri)
- [Tema Performansı ve Önbellekleme](https://dev.wisecp.com/tr/tema-performansi-ve-onbellek)
- [Tema Ayarları](https://dev.wisecp.com/tr/tema-ayarlari)
