# Arayüz Bileşenleri

https://dev.wisecp.com/tr/arayuz-bilesenleri

Panelin kurulduğu dört yapı taşı. Eklediğiniz ekran, ürünle gelen ekranlar gibi görünür ve davranır.

## Genel Bakış

Bileşen PHP'de kurulur ve şablonun içinde görünür: controller ne istediğini tarif eder, bileşen işaretlemeyi üretir. Sıralama, filtreleme, sayfalama ve sekme hafızası yeniden yazılmadan gelir.

Dördü de `WISECP\Components` altındadır ve `new` ile kurulur. Hepsi içeriğini konumsal argümanlarla değil bir **seçenek dizisiyle** alır.

## Referans

### Table

```php
public function __construct(string $name, array $options = []);   // $name = preset dosya adı

public function setColumns(array $columns): self;
public function setColumn(string $key, array $data): self;
public function deleteColumn(string $key): self;
public function setRows(array $rows): self;
public function setRowRender(callable $renderRow): self;
public function setFilters(array $filters): self;
public function setOptions(array $options): self;
public function getOptions(): array;

public function isRequestAjax(): bool;
public function ajaxControl(array $options = []): string;
public function allDataAjaxResponse(): string;
public function ajaxResponse(int $totalEntries = 0, int $totalSearchEntries = 0): string;

public function build(string $format = ''): string;        // '' = tüm tablo, 'justBody' = yalnız satırlar
public function buildList(string $format = ''): string;    // ızgara yerine kart satırları
public function container(string $content, array $attributes = []): string;
public function exportButton(array $options = []): string; // ['label', 'formats', 'class']

// Genel property'ler. İki model, bileşenin kendisinin çağırdığı closure'lardır.
public int $ajax_transition_limit = 500;
public mixed $totalModel;   // fn (string $search = '', array $filters = []): int
public mixed $dataModel;    // fn (string $search = '', string $order = '', string $direction = '', int $start = 0, int $end = -1): array
```

Kurucu seçenekleri, hepsi isteğe bağlı. Kurucu **preset dosyasını kendisi include eder**, controller başka hiçbir şeyi atamadan önce; bu yüzden preset seçenekleri `getOptions()` ile okur, satırları asla görmez.

- **preset**: Tablonun kendi adı yerine başka bir preset dosyası yükler; örn. ana listenin kolonlarını kullanan pano widget'ı için `'ticketList'`.
- **lazy**: `true` ilk sorguyu sekme açılana kadar erteler. Detay sekmesi için değil; pahalı kaynaklar için (dosya sistemi taraması, uzak katalog).
- **perPage · perPageOptions · search · info · pagination**: Araç çubuğu anahtarları. `false` kontrolü kaldırır; `[10, 25, 50, 100, -1]` sayfa boyutlarını verir, `-1` tümü demektir.
- **renderer**: `'list'` müşteri panelinin kullandığı kart düzenine geçirir. Başka her değer ızgara verir.
- **export · exportPrivilege · exportName**: İndirme varsayılan açıktır. `false` kaldırır, bir yetki anahtarı daraltır, ad da dosya adı olur. Bunları controller'da verin: dışa aktarma isteği şablon çalışmadan sonlanır.
- **containerExtraAttributes**: Sarmalayıcıya yazılan attribute haritası; örn. filtreleri tarayıcı adresine yansıtmak için `['data-url-sync' => 'true']`.

`ajaxControl()` kendi dizisini alır; isteği zaten yanıtladıysa boş olmayan, aksi hâlde boş bir dize döner. Çağıran taraf onu olduğu gibi döndürür.

- **baseLink**: Veri isteğinin kurulduğu adres. Alt sayfada bu, **alt sayfanın tam adresi** olmalıdır; controller kökü sayfa metoduna hiç ulaşmaz ve liste boş döner.
- **mode**: `'partial'` (varsayılan) sunucuda sayfalar; `'allData'` tüm kümeyi tek yanıtta verir.
- **ajaxExtraParams**: Her veri isteğinde taşınan ek sorgu değerleri; örn. `['type' => 'hosting']`.
- **force**: Satır sayısı eşiğini ezer. Yalnız filtreler sorgunun kendisini şekillendiriyorsa meşrudur; gerekçesini yanına yazın.

`setRowRender()` geri çağrınızın aldığı ve geri verdiği satırın üç parçası vardır:

```php
$row = [
    // Kaydın modelden geldiği hali. Salt-okunur girdi.
    'model' => ['id' => 5, 'name' => 'example.com', 'status' => 'active'],

    // Kolon anahtarı başına bir giriş. 'value' hücre işaretlemesi, 'attributes' <td>'ye iner.
    // 'data-value' sıralamanın ve istemci tarafı filtrelemenin karşılaştırdığı HAM değerdir.
    'data' => [
        'id'     => ['value' => '<a href="...">#5</a>', 'attributes' => ['data-value' => 5]],
        'status' => ['value' => '<span class="badge">Aktif</span>', 'attributes' => ['data-value' => 'active']],
    ],

    // <tr> attribute'ları. Satır seviyesi filtre değerleri buradan okunur.
    'attributes' => ['class' => 'table-tr-bg-info', 'data-filter-status' => 'active'],
];
```

### Tab ve Accordion

```php
// Tab. $type 'horizontal' ya da 'vertical'. add() İKİ argüman alır:
// panel anahtarı, sonra bir seçenek dizisi. Panel gövdesi $options['content']'tir.
public function __construct($name = '', $type = 'horizontal');
public function add($name, $options = []): self;
public function set($name, $options = []): self;                 // add() ile aynı
public function get($name = '');
public function remove($name = ''): self;
public function noUrl(bool $noUrl = true): self;
public function render(array $options = [], string $header = '', string $contents = ''): string;
public function header($options = []): string;
public function contents(array $options = []): string;

// Accordion. Aynı fikir, tipli, ve dikey/yatay seçimi yok.
public function __construct(string $name = '');
public function add(string $name, array $options = []): self;
public function set(string $name, array $options = []): self;
public function get(string $name = '');
public function remove(string $name): self;
public function noUrl(): self;
public function render(array $options = []): string;
```

- **content**: Panelin işaretlemesi. İki bileşenin de içeriği aldığı tek yer burasıdır; boş panel boş kalır, boş durumu kendiniz verirsiniz.
- **title**: Sekmenin ya da akordiyon başlığının etiketi. Yoksa anahtara düşer; çevrilmemiş bir panelin anahtarını göstermesinin sebebi budur.
- **icon**: İkon sınıfı, örn. `'bi bi-gear'`. Yatay sekmede boşluk sınıfı `iconClass`'tan gelir ve varsayılanı `'me-2'`'dir.
- **badge · badgeClass**: Yalnız Tab: sekmenin içinde, etiketinden sonra görünen sayaç. Boş olmayan her değer görünür, `'0'` dahil.
- **subtitle · hideContentTitle**: Yalnız Tab, dikey düzen: rail'deki ikinci satır ve panelin tekrar ettiği başlığı bastıran anahtar.
- **show**: Yalnız Accordion: `true` bu bölümü açık başlatır. Bölümler ortak bir ebeveyni paylaşır, biri açılınca diğerleri kapanır.
- **headerTag · titleClass · buttonClass · bodyClass**: Yalnız Accordion: başlık elemanı (varsayılan `'h2'`) ve başlığın, tetikleyicinin (varsayılan `'bg-light'`, temizlemek için `''`) ve gövdenin sınıfları.

İkisi de açık paneli adreste, bileşenin kendi adı altında hatırlar: `settings` adlı bir sekme kümesi `?settings=general` okur. Tek ekrandaki iki küme farklı ad ister.

### Modal

```php
// Her şey kurucuda verilir; setter'lar sonradan değiştirmek içindir
// ve void döndükleri için ZİNCİRLENMEZLER.
public function __construct(array $params);
public function setTitle(string $title): void;
public function setBody(string $body): void;
public function setFooter(string $footer): void;
public function setForm(?array $formAttributes): void;   // attribute haritası, sarmayı kaldırmak için null
public function setHeaderClasses(array $classes): void;
public function render(): string;
```

- **id**: DOM id'si ve tetikleyicinin işaret ettiği hedef. Varsayılanı `'SampleModal'`, yani id'siz iki pencere çakışır.
- **title · body · bodyClass · footer**: Başlık, içerik işaretlemesi, gövdeye ek sınıflar ve alt bölüm. **Boş bir footer hiç oluşturulmaz**.
- **form**: Başlığı, gövdeyi ve alt bölümü bir `<form>` içine saran attribute haritası: `['action' => $url, 'method' => 'POST']`. Her çift attribute olarak yazılır, footer'daki gönder butonu pencereyi gönderir. Salt-okunur pencerede vermeyin.
- **headerClasses**: Yalnız **tonu** seçer: içinde `danger` geçen bir değer kırmızı başlık, başkası birincil başlık verir. Kabuk sabittir, renkli bir şerit boyayamaz.
- **modalDialogExtraClass · scrollable · centered**: `'modal-lg'` gibi genişlik sınıfı ve ikisi de `true` varsayılanlı iki düzen anahtarı.
- **attributes**: Dış elemandaki varsayılanların üzerine birleştirilen ham attribute'lar. Perdeyi kilitlemenin desteklenen tek yolu budur.

## Örnek

Bir liste üç dosya alır: controller veriyi bağlar, preset kolonları tanımlar ve her satırı modelden kurar, sayfa şablonu sonucu gösterir.

```php
$table = new \WISECP\Components\Table('widgetList', ['exportName' => 'widgets']);

// Operatörün seçtiği değerler. setFilters bunları veri isteğinde taşır;
// model ise yalnız bir isteği yanıtlarken alır.
$dynamic = [];
if ($status = Filter::init("REQUEST/filter/status", "route")) $dynamic['status'] = $status;

$table->setFilters($dynamic);
$filter = $table->isRequestAjax() ? $dynamic : [];

$table->totalModel = fn ($search = '', $filters = []) =>
    $this->model->list(true, array_merge($filter, $filters, ['word' => $search]));

$table->dataModel = fn ($search = '', $order = '', $direction = '', $start = 0, $end = -1) =>
    $this->model->list(false, array_merge($filter, ['word' => $search]), [$order => $direction], $start, $end);

// Boş olmayan dönüş, isteğin burada zaten yanıtlandığı anlamına gelir.
if ($response = $table->ajaxControl(['baseLink' => $links["controller"]])) return $response;

$this->addData('table', $table);
```

```php
/** @var \WISECP\Components\Table $table */
if (!isset($table)) return;

$table->setColumns([
    'id'     => ['title' => 'ID', 'attributes' => ['class' => 'text-center'], 'sortable' => true],
    'name'   => ['title' => Language::gc("admin/widgets/th-name"), 'sortable' => true],
    'status' => ['title' => Language::gc("admin/widgets/th-status"), 'sortable' => "asc"],
    'email'  => ['title' => Language::gc("admin/widgets/th-email"), 'exportOnly' => true],
]);

$table->setRowRender(function ($row) {
    $id = (int) $row["model"]["id"];

    $row["data"]["id"]["value"]                     = '<a href="?id=' . $id . '">#' . $id . '</a>';
    $row["data"]["id"]["attributes"]["data-value"]  = $id;

    $row["data"]["name"]["value"] = htmlspecialchars($row["model"]["name"] ?? '');

    // Filtrenin karşılaştırdığı ham değerdir; hücre rozeti gösterir.
    $row["data"]["status"]["value"]                    = \WISECP\AdminComponents\Statuses::getInstance()->service($row["model"]["status"]);
    $row["data"]["status"]["attributes"]["data-value"] = $row["model"]["status"];

    // Ekranda çizilmez, indirmede vardır.
    $row["data"]["email"]["value"] = $row["model"]["email"] ?? '';

    $row["attributes"]["data-filter-status"] = $row["model"]["status"];

    return $row;
});
```

```php
// Sayfa şablonu basmadan hiçbir şey görünmez. Preset zaten kurucunun içinde çalıştı,
// bu çağrı yalnız controller ile preset'in kurduğunu render eder.
if (isset($table)) {
    $table->setOptions(['containerExtraClass' => 'mt-3']);
    echo $table->build();
}
```

Bir sekme kümesi ve bir pencere, ikisi de seçenek dizisiyle beslenir:

```php
// footer.php $modals içinde ne varsa basar; şablon onu bir kez burada tanımlar
// ve sayfadaki her pencere üzerine ekler.
$modals = '';

$tab = new \WISECP\Components\Tab('widget-detail');

$tab->add('overview', [
    'title'   => Language::g('widgets-overview'),
    'icon'    => 'bi bi-grid-1x2',
    'content' => $overviewHtml,
]);

// Koşul BURADA, bir kez. Paneli okuyan biri sekmenin bir şey düştüğü için değil
// bu kural yüzünden görünmediğini böylece bilir.
if ((int) Config::get('options/reselling/status') === 1)
    $tab->add('reselling', [
        'title'   => Language::g('widgets-reselling'),
        'badge'   => $pendingCount,
        'content' => $resellingHtml,
    ]);

echo $tab->render();

$modal = new \WISECP\Components\Modal([
    'id'                    => 'widgetDeleteModal',
    'title'                 => Language::g('needs/delete'),
    'body'                  => '<p>' . Language::gc('admin/widgets/delete-confirm') . '</p>'
                             . '<input type="hidden" name="operation" value="delete_widget">',
    'footer'                => '<button type="submit" class="btn btn-danger">' . Language::g('needs/delete') . '</button>',
    'headerClasses'         => ['bg-danger'],
    'modalDialogExtraClass' => 'modal-lg',
    'form'                  => ['action' => $links["controller"], 'method' => 'POST'],
]);

$modals .= $modal->render();
```

## Tuzaklar

> **Tab ve Accordion anahtar + seçenek dizisi alır, etiket + gövde değil**
> 
> `add('overview', 'Genel Bakış', $html)` yazmak, seçenek dizisinin beklendiği yere bir dize ve var olmayan bir üçüncü argüman geçirir. Panel, başlığı olarak anahtarını gösterir ve **hiç içerik taşımaz**, hata da vermez. Gövde `$options['content']`'tir.

> **Pencerenin setter'ları hiçbir şey döndürmez, zincirlenemez**
> 
> Her setter `void` tiplidir. Birini diğerine zincirlemek null üzerinde ölümcül bir çağrıdır. Her şeyi kurucu dizisinde verin; setter'lar zaten verdiğiniz bir değeri değiştirmek içindir.

> **Sayfalamanın nerede olacağına satır sayısı karar versin**
> 
> Bileşen, toplam eşiği aştığı anda sunucu tarafı sayfalamaya kendisi geçer. Bu kararı çağrı yerinde zorlarsanız ya elli bin satır tek belgeye girer ya da yirmi satır için fazladan bir tur ödersiniz.

> **İç içe sekme kümesi adresin dışında kalmalı**
> 
> Adrese yazan iki küme onun için çekişir, operatör geri döndüğünde yanlış çiftte açılır. İçteki küme `noUrl()` çağırır.

## İlgili Makaleler

- [View ve Şablonlar](https://dev.wisecp.com/tr/view-ve-sablonlar)
- [Admin Form Oluşturucu](https://dev.wisecp.com/tr/admin-form-olusturucu)
- [Admin JavaScript Kütüphanesi](https://dev.wisecp.com/tr/admin-javascript-kutuphanesi)
- [Admin Sayfası Ekleme](https://dev.wisecp.com/tr/admin-sayfasi-ekleme)
