# Müşteri Paneli

https://dev.wisecp.com/tr/musteri-paneli

Yirmi beş view, tek bir controller işaretinin açtığı aynı kabuğu paylaşır. Her biri, giriş yapan kişiye ait olmayan bir hesap için görünebilir.

## Genel Bakış

Aynı sayfanın iki soruya cevap verdiği tek aile burasıdır: üzerinde ne var ve kimin. Bir üye başka hesap adına işlem yapabilir, yani liste başkasına ait olabilir. Bir sekme, verisi boş olduğu için değil izin verilmediği için eksik olabilir.

View'lar genel layout'u genişletir; bir işaret onu müşteri kabuğuna çevirir.

## Yapı

Yirmi beş view altı işe ayrılır.

| Grup | View'lar | Notlar |
| --- | --- | --- |
| Pano | `dashboard-hero`, `dashboard-standard` | Tek adresin iki varyantı ya da tek bir `dashboard` view'ı |
| Varlıklar | `services`, `service-detail`, `domains`, `domain-detail`, `service-transfer-approve`, `license-transfer-verify` | Listeler preset kullanır; detaylar çok sekmeli |
| Para | `invoices`, `invoice-detail`, `subscriptions`, `bulk-pay`, `balance` | İkisi fatura layout'unu genişletir |
| Destek | `tickets`, `ticket-detail`, `ticket-create`, `ticket-msg` | `ticket-msg` bir parçadır: yoklayıcıya dönen tek yanıt |
| Hesap | `settings`, `sub-accounts`, `api-credentials`, `sms` | Avatar menüsü sayfaları; sekme işaretlemezler |
| Programlar | `affiliate`, `reseller`, `reseller-program`, `access-denied` | Tek bayi adresi, iki view |

## Adım Adım

### Müşteri Kabuğunu Açın

1. İşareti bir kez, en tepede hesaplayın: ziyaretçi giriş yapmış *ve* alt menü istenmiş olmalı.
2. Kabuğu onunla seçin: müşteri topbar'ı, kenar çubuğu, footer'ı; tanıtım bandı yok.
3. Aktif sekmeyi `$subnav_active`'den işaretleyin. Değerlerini controller'lar sabitler: `dashboard`, `services`, `domains`, `billing`, `support`, `sms`. Avatar menüsü sayfaları boş string taşır.
4. Sekme adı uydurmayın: tanınmayan değer sessizce hiçbir sekmeyi işaretlemez.

### Doğru Hesap İçin Kurun

1. Giriş kimliğini içeriğin sahibi olarak asla yazmayın: biri giriş yapandır, öteki verinin sahibidir.
2. Bir panelin boş değil yok olmasını bekleyin; verilmemiş izin, sorgunun hiç koşmaması demektir.
3. Hesap değiştiriciyi görünür tutun; başka hesap adına çalışan üyenin geri dönüş yolu olmalı.
4. Uygulamayı çekirdeğe bırakın. Bağlantı gizlemek nezakettir; sınır, erişim reddi kapısıdır.

### Bir Liste Ekranı Kurun

Dört büyük liste satırlarını iki kez yazar: ilk sayfada sunucuda, sonrasında tablo motorunda.

1. Satır işaretlemesini temanın tablo presetine, `tables/` altına koyun.
2. View'ın döngüsü aynı işaretlemeyi yazsın; ilk boyama ile AJAX sayfası bayt bayt aynı olur.
3. Sayfa değerlerini global'lerden değil tablo seçeneklerinden okuyun; preset controller verisi görmez.

### Detay Sekmelerini Bağlanabilir Yapın

1. Her sekme butonuna panel id'sinden ayrı, kalıcı bir anahtar verin ki sayfa zıplamasın.
2. Yüklemede sorgu dizesindeki sekmeyi açın; hatırlanan sekmeyi ezmelidir.
3. Sekme gösterildiğinde anahtarını geri yazın, terk edilen sekmenin parametrelerini düşürün.
4. Tembel yüklenen sekmede yüklemede ve sonraki turda tekrar denetleyin: hatırlanan sekme iki anda geri yüklenir.

## Referans

### Kabuk Değişkenleri

- **$show_client_subnav**: Varsayılan layout'u müşteri kabuğuna çeviren anahtar. Hazır müşteri verisinden **ÖNCE** true yapılır.
- **$subnav_active**: Güncel alt menü sekmesi; boş string bilinçlidir.
- **$notification_count**: Zil rozeti. Sıfırda bile kurulur, biçimlenmiş `$notification_count_text` ile. Çekirdek kabuğu.
- **$account_info**: Kimlik bloğu, hep **oturum açmış üye**. Anahtarlar: `name`, `surname`, `full_name`, `email`, `avatar`, `initials`, `balance` (biçimlenmiş), `support_pin`, `two_factor`, `is_reseller`, `dealership`, `last_login`, `last_login_date`, `last_login_ip`, `last_login_country`, `last_login_city`. Avatar → baş harfler → ikon.
- **$account_info.last_login_date**: Hesap hiç giriş yapmamışsa boştur. Olmayan tarihi biçimlemek şu anı yazar ve şimdiyi son giriş gibi gösterir — tire koyun.
- **$currency_formats_json ve kardeşleri**: Örnek string'ler, para birimi id'leri ve kurlar; para script'i sunucuyla aynı biçimlesin diye.

### Hesap Bağlamı API'si

```php
// Üye oturumu yoksa null. Giriş kimliğini asla değiştirmez.
public static function activeAccount(): ?array;

// Kendi hesabına her zaman izinlidir; devredilmiş hesapta yalnız izin verilmişse.
public static function accountCan(string $permission): bool;

// Yalnız hâlâ geçerli bir üyelik (ya da kendisi) kabul edilir; istemciden gelen id'ye asla güvenilmez.
public static function switchAccount(int $ownerId): bool;

// Giriş yapmış üyenin zil sayacı. 0 hem "üye yok" hem "okunmamış yok" demektir.
public static function notification_count(int $user_id = 0): int;
```

```php
$ctx = [
    'login_id'    => 41,      // kimlik doğrulayan; devredilmişken ASLA değişmez
    'owner_id'    => 88,      // bu sayfanın verisi kime ait
    'is_self'     => false,   // iki id aynıysa true
    'permissions' => ['view_services', 'view_invoices'],   // her istekte yeniden okunur
];

// Devredilen hesap HER istekte üyelik tablosuna karşı yeniden doğrulanır, yani iptal
// edilmiş bir üyelik erişimi bir sonraki girişte değil anında kaybeder.
```

### Pano Verisi

| Değişken | Taşıdığı | İzin yokken davranışı |
| --- | --- | --- |
| `$dash_services`, `$dash_domains`, `$dash_invoices`, `$dash_tickets` | Panel listeleri | Boş; sorgu hiç koşmaz |
| `$dash_stats` | Rakam şeridi | İzin verilen okumalardan kurulur |
| `$dash_alerts` | Dikkat kalemleri; modüllerin genişlettiği kancadan geçer | Daha az kayıt, asla eksik değil |
| `$dash_health`, `$dash_feed`, `$dash_balance` | Sağlık satırı, akış, bakiye | Var ve boş — "sıfır" değil "size ait değil" |
| `$dash_greeting`, `$dash_first_name`, `$dash_today` | Karşılama satırı; oturum açmış üyeyi anar | Her zaman var |
| `$dash_l10n` | Pano script'lerinin etiketlendiği metinler | Her zaman var |
| `$show_activity` | Akış görünebilir mi | Geçiş yapılmışsa false: etkinlik kişiseldir |

### Müşteri Paneli Tema İşaretleri

- **dashboard_layout**: Bir ayar alanı, `hero` ya da `standard`; `views/account/dashboard-{değer}`'i seçer. Tek bir `account/dashboard` varsa yok sayılır.
- **meta.dashboard_due_soon_alert**: Ayar değil, manifest işareti. False yalnız vadesi geçmiş faturayı uyarır; true yaklaşanı da.
- **meta.disabled_routes**: Temanızın servis etmediği rota anahtarları; yarım miras view yerine 404 döner. Liste şablonlara da açılır.

## Örnek

```smarty
{* BİR KEZ, doctype'tan bile önce, en tepede hesaplanır. İki yarım da gerekli:
   tanıtım sayfasındaki giriş yapmış ziyaretçi yine genel kabuğu almalıdır. *}
{$is_client_area = $is_logged_in && !empty($show_client_subnav)}<!DOCTYPE html>
<html lang="{$ui_lang|default:'en'}" dir="{$ui_dir|default:'ltr'}">
<head>
    {* Yalnız kabuğa ait stil: genel bir sayfa bunun bedelini hiç ödemez. *}
    {if $is_client_area}<link rel="stylesheet" href="{asset path='css/client-nav.css'}">{/if}
    {block name=head}{/block}
    {hook name='ui:client.head.css'}
</head>

<body class="{block name=body_class}{/block}"{if $is_client_area} data-client-nav="sidebar"{/if}>
{hook name='ui:client.body.begin'}

{if $is_client_area}
    {include file='partials/client-topbar.tpl'}
    {include file='partials/client-sidebar.tpl'}
{else}
    {include file='partials/header.tpl'}
{/if}

<main class="client-main">
{block name=content}{/block}
</main>

{* Tanıtım bantları yalnız genel siteye aittir. *}
{if !$is_client_area}{block name=bands}{/block}{/if}

{if $is_client_area}
    {include file='partials/client-footer.tpl'}
{else}
    {include file='partials/footer.tpl'}
{/if}

{block name=body_end}{/block}
{hook name='ui:client.body.end'}
```

```php
// Kimlik asla sahip değildir. İkisini de çözün, sonra izinleri sorun.
$ctx  = UserManager::activeAccount();
$uid  = (int) $ctx["owner_id"];
$self = (bool) $ctx["is_self"];

// Kendi hesabı kapıyı tümüyle atlar; devredilen hesap alan başına denetlenir.
$can = fn (string $p): bool => $self || in_array($p, $ctx["permissions"], true);

if (!Theme::active()->viewExists("account/services")) return $this->page_404("website");

// set_predefined_data("client") ÖNCESİNDE olmalı: bildirim sayacı bu bayrağın
// arkasında okunur ve sonradan kuran bir controller sıfır rozet alır.
$this->addData("show_client_subnav", true);
$this->addData("subnav_active", "services");

// İzin verilmemiş alan, boş görünen bir panel yerine hiçbir şey katmaz.
$this->addData("service_list", $can("view_services") ? $this->model->services($uid) : []);

$this->set_predefined_data("client");

return $this->view->chose("website")->render("account/services", $this->data, true);
```

## Tuzaklar

> **Kabuk işaretini müşteri verisinden önce kurun**
> 
> Bildirim sayacı `show_client_subnav`'ın arkasında hesaplanır. Sonradan kurarsanız rozet her zaman sıfır görünür.

> **Bağlantı gizlemek erişim denetimi değildir**
> 
> İzin verilmemiş bir alan için sekmeyi atlamak sunumdur. Gerçek sınır, erişim reddi view'ının arkasındaki kapıdır ve şablonunuzdan bağımsız çalışır.

> **Bir tablo preseti iki kez dahil edilebilir**
> 
> Yardımcılarını closure tutan değişkenler olarak bildirin: adlandırılmış fonksiyon ikinci dahil etmede ölümcül hata verir ve o ikinci dahil etme, özet döndüren bir AJAX isteğinde olur.

> **Global değişkenleri koşulsuz kurun, koşullu gizleyin**
> 
> Kabuğun okuduğu bir sayaç, değeri sıfır olsa bile her zaman atanır. Bir `if` içinde kurulan değişken, dalın atlandığı sayfalarda tanımsızdır.

> **İptal butonunu da yerelleştirin**
> 
> Onay diyaloğu iptal etiketini İngilizceye düşürür; yalnız eylem etiketini geçmek diğer dillerde başıboş bir İngilizce kelime bırakır.

## İlgili Makaleler

- [Sayfa Ekranları](https://dev.wisecp.com/tr/sayfa-yuzeyleri)
- [Giriş ve Kayıt](https://dev.wisecp.com/tr/giris-ve-kayit)
- [Şablon Değişkenleri](https://dev.wisecp.com/tr/sablon-degiskenleri)
- [Tema Kancaları ve Çıktı Filtreleri](https://dev.wisecp.com/tr/tema-kancalari-ve-cikti-filtreleri)
- [Arayüz Bileşenleri](https://dev.wisecp.com/tr/arayuz-bilesenleri)
