# Pano Widget'ı Ekleme

https://dev.wisecp.com/tr/pano-widgeti-ekleme

Admin panosuna bir kart eklemek için tek bir kayıt kancasından tanım dizisi döndürürsünüz.

## Genel Bakış

**Pano widget'ları bir modül tipi değildir:** widget modülü ve genişletilecek bir taban sınıf yoktur. `get_widgets()` onları yetki kontrollerinden kurar, tek bir şablon gösterir, kancalar genişletir.

`register:admin.dashboard_widgets` kancasından bir dizi döndürürsünüz. Çekirdek ona bir sıra atar, operatörün kayıtlı düzenini uygular ve paylaşılan kart kabuğuna verir. Panonun geri kalanına dört kanca daha ulaşır.

## Ön Koşullar

- Çalışan bir `hooks.php` dosyası olan bir modül; bkz. [Modülden Kanca Kaydetme](https://dev.wisecp.com/tr/modulden-kanca-kaydetme).
- İçeriğiniz bir HTML dizesi olarak; gövde olduğu gibi görünür.
- Kart kısıtlanacaksa bir yetki anahtarı.

## Yapı

- **controllers/admin/index.php**: `get_widgets()` listeyi kurar ve kancanızın dönüşünü birleştirir; `get_statistics()` rakam şeridini kurar.
- **templates/admin/index.php**: Pano kabuğu: enjeksiyon kancalarını ve widget filtresini tetikler, sonra listeyi kartlara döker.
- **inc/template-widget-item.php**: Kart kabuğu. Bilinmeyen bir ad sizin içeriğinize düşer.
- **operations/AdminIndex.php**: `get_widget_content`, yenile butonunun arkasındaki operation.
- **js/home.js**: Izgarayı Packery ile yerleştirir: rank HTML'deki sıradır, kartın yeri burada belirlenir.

## Adım Adım

### Kartı Kaydedin

1. `hooks.php` dosyanızdan `register:admin.dashboard_widgets` kancasını dinleyin ve tek bir tanım dizisi döndürün.
2. En azından `name`, `title` ve `content` verin; kalan her şeyin varsayılanı vardır.
3. Yetki kontrolünü dinleyicinin içinde yapın: düşerse `false` döndürün ya da `allowed` anahtarına sonucu yazın.

### Gövdeyi Kurun

1. HTML'i heredoc ile üretin ve metinlerinizi modülün dil dosyasından okuyun.
2. Dosya gövdesinde değil dinleyicinin içinde kurun; kartınızın gösterilmediği bir pano maliyet doğurmaz.
3. Sonradan dolan her şeyin yüksekliğini rezerve edin.

### Konum ve Boyutu Belirleyin

1. İşaretlemedeki sıra için `rank` verin; yazmazsanız çekirdek sizinkini sona ekler.
2. Tam genişlikte bir kart için `size` değerini `wide` yapın; başka her değer standart yarım genişliği verir.
3. Şerit, çevresindeki işaretleme ya da başkasının kartları için aşağıdaki diğer dört kancayı kullanın.

## Referans

### Beş Pano Kancası

| Kanca | Çağrı yeri | Dönüşünüz |
| --- | --- | --- |
| `register:admin.dashboard_widgets` | `Hook::run`, argümansız | bir tanım bir kart olur; `false` hiçbir şey kaydetmez |
| `filter:admin.dashboard.widgets` | `Hook::runRefs`, `$widgets` referansla | yerinde düzenler: sıralama, çıkarma, başlık değiştirme |
| `filter:admin.dashboard.statistics` | `Hook::run`, `$result` | boş olmayan dönüş **dizinin tamamını değiştirir** |
| `ui:admin.dashboard.top` | `Hook::run`, argümansız | rakam şeridinin üstünde bir dize |
| `ui:admin.dashboard.statistics.after` ve `ui:admin.dashboard.bottom` | aynısı, argümansız | şeritten ve ızgaradan sonra bir dize |

> **İstatistik filtresi değiştirir, birleştirmez**
> 
> Çağrı yeri, boş olmayan son dinleyici dönüşünü tutar ve dizinin tamamına atar. İlgilendiğiniz anahtarı değiştirip dizinin tamamını döndürün; kendi anahtarınızı tek başına döndürmek şeridi siler ve boş şerit yine makul görünür.

### Tanım Dizisi

- **name**: Kimlik: kartta `data-id`, kayıtlı düzende anahtar ve yenileme argümanı. Varsayılan `wt{rank}`, ona güvenmeyin.
- **title**: Başlık, ham HTML olarak görünür. Varsayılan `Untitled Widget`; `link` verirseniz bağlantıya sarılır.
- **content**: Kart gövdesi, olduğu gibi görünür. Boşsa widget adı görünür; unutmanın belirtisi budur.
- **icon**: Başlığın yanındaki disk için Bootstrap ikon sınıfı. Varsayılan `bi bi-box`.
- **allowed**: Boolean kapı, varsayılan true. False, kart görünmeden onu listeden çıkarır.
- **status**: `open` ya da `close`, varsayılan open. Kapalı kart yalnız başlığını gösterir.
- **rank**: İşaretlemedeki tam sayı sıra. Varsayılan: kaydedilen son widget'ın bir fazlası.
- **size**: `wide` tam genişlik sınıfını ekler; başka her değer standart genişliği bırakır.
- **hidden**: Boolean. Kart üretilir ama display none ile başlar; kapat butonu kendini böyle hatırlar.
- **link**: Başlığı bağlantıya çevirir. Bağlantı üreticisiyle kurun, düz yol yazmayın.
- **buttons**: Başlık butonları: `['create' => ['name' => …, 'link' => …, 'icon' => …]]`. `create` artı ikonu alır; diğer anahtarlar `icon` kullanır, o da yoksa metin olarak ad.
- **header_buttons**: Yenile, daralt ve kapat butonlarından önce gelen ham HTML.

### Çekirdeğin Doldurdukları

```php
$hook = Hook::run("register:admin.dashboard_widgets");
if ($hook)
{
    $last = end($widgets);
    $rank = $last["rank"] ?? 10;

    foreach ($hook as $h)
    {
        $rank++;
        $wn = "wt" . $rank;

        if (isset($h["name"]) && $h["name"]) $wn = $h["name"];
        if (!isset($h["allowed"])) $h["allowed"] = true;
        if (!isset($h["status"])) $h["status"] = "open";
        if (!isset($h["rank"]))   $h["rank"]   = $rank;

        $widgets[$wn] = $h;                 // anahtar sizin adınızdır: yerleşik bir kartı DEĞİŞTİREBİLİR
    }
}
```

### Yenile Butonu

```php
public function get_widget_content(Operation $operation): bool;
```

```php
// GET {dashboard}?operation=get_widget_content&name={adınız}
$widgets = $this->get_widgets();                 // kanca dahil listenin TAMAMI yeniden kurulur
if (!isset($widgets[$wn])) throw new Exception("Invalid widget");

// Aynı kabuktan yeniden basılan tek bir kart, HTML dizesi olarak döner.
return $operation->output($this->view->chose("admin")->render("inc" . DS . "template-widget-item", [
    'widget' => $widgets[$wn],
], true));
```

### Karta Tablo Koymak

```php
$t = new \WISECP\Components\Table("myAddonWidget", [
    'preset'      => 'invoiceList',      // satır basımı ANA listeden gelir
    'hideActions' => true,
    'perPage'     => false,
    'search'      => false,
    'info'        => false,
    'pagination'  => false,
]);

foreach ($t->getColumns() as $k => $v) $t->setColumn($k, ['sortable' => false]);
$t->deleteColumn("selection");
$t->setRows($rows);

$html = $t->build();
```

> **Satırlar preset'in okuduğu her anahtarı taşımalı**
> 
> Widget tablosu ana listenin satır basıcısını ödünç alır ve o basıcı, bıraktığınız kolonlardan çok daha fazla anahtar okur. Eksik anahtar hata vermez, boş hücre gösterir. Listeyi preset dosyasından çıkarın; oradaki bir değişiklik kartınıza da ulaşır.

## Örnek

Bir modülün kendi kuyruğu: kaydedilmiş, kapılanmış, sıralanmış ve yenilenebilir.

```php
<?php

Modules::Load('Addons', 'MyAddon', true);
$my_config = Modules::Config('Addons', 'MyAddon') ?: [];

if (!($my_config['status'] ?? false)) return;

/*
 * Panoda tek bir kart. Dinleyici argüman almaz ve TEK bir tanım döndürür; her pano
 * basımında ve bu kartın her yenilenmesinde koşar, bu yüzden ucuz kalır ve modül
 * örneğini ancak yetki kontrolü geçtikten sonra kurar.
 */
Hook::add('register:admin.dashboard_widgets', 10, function () {
    if (!Admin::isPrivilege('TOOLS_ADDONS')) return false;

    $module = Modules::getInstance('Addons', 'MyAddon');

    return [
        'name'    => 'myaddon_queue',
        'title'   => $module->lang['widget-title'] ?? 'My Addon Queue',
        'icon'    => 'bi bi-list-check',
        'rank'    => 6,
        'status'  => 'open',
        'link'    => LinkGenerator::admin('tools-2', ['addons', 'MyAddon']),
        'buttons' => [
            'create' => [
                'name' => Language::gc('admin/index/button-create-a-new'),
                'link' => LinkGenerator::wQS(LinkGenerator::admin('tools-2', ['addons', 'MyAddon']), ['trigger' => 'create']),
            ],
        ],
        'content' => $module->render_dashboard_widget(),
    ];
});

/* Şeritte bir rakam. Size verileni okuyun, bir anahtarı değiştirin, HEPSİNİ döndürün. */
Hook::add('filter:admin.dashboard.statistics', 10, function ($result) {
    if (!is_array($result)) return false;

    $result['static_blocks']['myaddon_pending'] = [
        'title' => 'Pending syncs',
        'value' => (int) WDB::select('COUNT(id) AS total')->from('MyAddon_queue')
            ->where('status', '=', 'pending')->build() ? (int) (WDB::getAssoc()['total'] ?? 0) : 0,
    ];

    return $result;
});
```

```php
public function render_dashboard_widget(): string
{
    $rows = '';

    foreach ($this->queue_preview(5) as $row) {
        $label = htmlspecialchars((string) ($row['label'] ?? ''), ENT_QUOTES);
        $state = htmlspecialchars((string) ($row['status'] ?? ''), ENT_QUOTES);

        $rows .= <<<HTML
        <li class="list-group-item d-flex justify-content-between align-items-center px-0">
            <span class="text-truncate">{$label}</span>
            <span class="badge text-bg-light">{$state}</span>
        </li>
        HTML;
    }

    if ($rows === '')
        $rows = '<li class="list-group-item px-0 text-body-secondary">' . ($this->lang['widget-empty'] ?? 'Nothing queued.') . '</li>';

    // Rezerve edilen yüksekliği sınıf taşır (aşağıdaki stil dosyasına bakın); liste
    // dolduğunda tüm ızgaranın yeniden paketlenmesini engelleyen şey budur.
    return <<<HTML
    <ul class="list-group list-group-flush myaddon-queue">{$rows}</ul>
    HTML;
}
```

```css
/* Nihai yükseklik, içerik var olmadan önce bildirilir. Kartı kaydeden aynı hooks.php
   dosyasından ui:admin.head.css üzerinden gönderin. */
.myaddon-queue { min-block-size: 220px; }
```

Okuma tarafı kart kabuğudur: `content` eksikken görünen yedek ve gövdenin neden kaçırılmadığı.

```php
$w_name    = $widget["name"]  ?? "widget" . $w_rank;
$w_title   = ($widget["title"] ?? '') ?: 'Untitled Widget';
$w_icon    = ($widget["icon"]  ?? '') ?: 'bi bi-box';
$w_content = $widget["content"] ?? '';

if ($w_name == "orders_chart") {
    // ... yerleşik adlardan oluşan uzun bir zincir, her birine bir dal
}
else
    echo $w_content ?: $w_name;      // kaçırılmamış HTML'iniz, ya da unuttuysanız ad
```

## Tuzaklar

> **İlk boyamadan sonra büyüyen kart ızgarayı yeniden paketler**
> 
> Yerleşim masonry'dir ve her geçişte her kartı yeniden ölçer. Sonradan dolan bir gövde kartının yüksekliğini değiştirir, ilgisiz kartlar ekranda kayar. Ölçüldü: 54 piksel büyüyen bir grafik kutusu, başka iki kartı 733 piksel taşıdı.

> **Operatörün kayıtlı düzenlemesi tanımınızdan üstündür**
> 
> Widget adınız için saklanmış bir rank, daraltılmış durum ya da gizli işaret tanımınızı yener ve çerezde taşınır: açık kaydettiğiniz kart bir yönetici için daralmış, meslektaşı için açık olabilir. Daraltılmış kartın içeriği sayfadadır, CSS ile gizlenir.

> **Dinleyiciniz her pano gösteriminde çalışır, bir kez değil**
> 
> Herhangi bir kartın her yenilenmesinde de çalışır, çünkü o operation tüm listeyi yeniden kurar. Yenile butonunu sizin için çalıştıran şey budur; oradaki bir sorgu da her seferinde ödenir. Önce yetki kontrolünü yapın, pahalı olan her şeyi önbelleğe alın.

> **Var olan bir adla kayıt o kartı değiştirir**
> 
> Birleştirme ada göre anahtarlanır, yani widget adınız `notes` ya da `tasks` ise yerleşik kartı devralır. Adın başına modülünüzü koyun.

## İlgili Makaleler

- [Modülden Kanca Kaydetme](https://dev.wisecp.com/tr/modulden-kanca-kaydetme)
- [Admin Sayfası Ekleme](https://dev.wisecp.com/tr/admin-sayfasi-ekleme)
- [Kanca Kataloğu](https://dev.wisecp.com/tr/kanca-alanlari)
- [Arayüz Bileşenleri](https://dev.wisecp.com/tr/arayuz-bilesenleri)
- [Modül Sistemi](https://dev.wisecp.com/tr/modul-sistemi)
- [Bağlantı ve Rota Kurma](https://dev.wisecp.com/tr/baglanti-ve-rota-kurma)
