Teknik Analiz v2

WidgetManagement Modülü

Kapsamlı Mimari ve Kullanım Analizi

13 Ocak 2026
Versiyon 2.0

📝 Basit Anlatım (Herkes İçin)

WidgetManagement, web sitelerinde yeniden kullanılabilir içerik blokları oluşturmanızı sağlayan bir sistemdir. Düşünün ki Lego parçaları gibi hazır bloklar var - hero bölümü, iletişim formu, kart grid'i, galeri vb. Bunları bir araya getirerek sayfalar oluşturabilirsiniz.

Ne İşe Yarar?

  • Merkezi şablonlar: Bir kez tasarla, tüm sitelerde kullan
  • Özelleştirilebilir: Her site kendi renklerini, yazılarını belirleyebilir
  • Dinamik içerik: Kart grid'ine yeni kartlar, slider'a yeni resimler ekle
  • Kod yazmadan tasarım: HTML/CSS bilmeden widget ayarlarını değiştir

Örnek Senaryo

"Hero Section" adında bir widget şablonu var. Merkezi sistemde başlık, alt başlık, buton ve arka plan resmi alanları tanımlı.

ixtif.com bu widget'ı alıp "Forklift Çözümleri" yazıyor, mavi tema seçiyor.
muzibu.com aynı widget'ı "Müzik Dünyası" yazarak, mor tema ile kullanıyor.

Her ikisi de aynı kod tabanını kullanıyor ama farklı görünüyor!

📊 Modül İstatistikleri

151
PHP Dosyası
42
Blade View
40+
Widget Şablonu
12
Livewire Component

🗄️ Veritabanı Yapısı (Multi-Tenant)

📝 Basit Anlatım

Merkezi Veritabanı: Widget şablonları ve kategorileri burada saklanır. Tüm siteler bu şablonları görür.

Tenant Veritabanı: Her sitenin kendi widget örnekleri ve içerikleri burada. ixtif.com'un hero widget'ı muzibu.com'dan tamamen bağımsız.

🏛️ Central Database (tuufi_4ekim)

widget_categories

Widget kategorileri (hierarchical)

widget_category_id, title, slug, icon, parent_id, order
widgets

Widget şablonları (master templates)

id, name, slug, type, content_html/css/js, item_schema, settings_schema, has_items
widget_modules

Widget-Module pivot tablosu

widget_id, module_id

🏢 Tenant Database (tenant_X)

tenant_widgets

Kiracının widget örnekleri

id, widget_id (FK→central), settings (JSON), display_title, is_custom, custom_html/css/js, order
widget_items

Widget'ların dinamik öğeleri

id, tenant_widget_id (FK), content (JSON), order
⚠️ Cross-Database Relationship

tenant_widgets.widget_id → widgets tablosuna (central DB) işaret eder. MySQL FK kısıtlaması yok, Laravel ilişkisi ile yönetilir.

📊 Tablo İlişki Şeması


┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           CENTRAL DATABASE (tuufi_4ekim)                            │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                     │
│  ┌──────────────────────┐         ┌──────────────────────┐                          │
│  │   widget_categories  │◄────────┤       widgets        │                          │
│  ├──────────────────────┤  1:N    ├──────────────────────┤                          │
│  │ widget_category_id   │         │ id                   │                          │
│  │ title, slug          │         │ widget_category_id   │─────┐                    │
│  │ parent_id (self FK)  │         │ name, slug, type     │     │                    │
│  │ icon, order          │         │ content_html/css/js  │     │ 1:N               │
│  │ is_active            │         │ item_schema (JSON)   │     │                    │
│  └──────────────────────┘         │ settings_schema      │     ▼                    │
│                                   │ has_items, file_path │  ┌──────────────────┐    │
│                                   └──────────────────────┘  │  widget_modules  │    │
│                                                             │  widget_id       │    │
│                                                             │  module_id       │    │
│                                                             └──────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        │ Cross-DB Reference (widget_id)
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                    TENANT DATABASE (tenant_muzibu_1528d0 vb.)                       │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                     │
│  ┌──────────────────────┐         ┌──────────────────────┐                          │
│  │    tenant_widgets    │────────►│    widget_items      │                          │
│  ├──────────────────────┤   1:N   ├──────────────────────┤                          │
│  │ id                   │         │ id                   │                          │
│  │ widget_id ───────────┼─►Central│ tenant_widget_id     │                          │
│  │ settings (JSON)      │         │ content (JSON)       │                          │
│  │ display_title        │         │ order                │                          │
│  │ is_custom            │         └──────────────────────┘                          │
│  │ custom_html/css/js   │                                                           │
│  │ order, is_active     │                                                           │
│  └──────────────────────┘                                                           │
│                                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────┘
                    

🎨 Widget Türleri

📄

File Widget

type: 'file'

Blade view dosyasından render edilir. Statik yapı, dinamik veri ile birleştirilebilir.

file_path: 'blocks/hero/simple/view'

widgetmanagement::blocks.hero.simple.view
Kullanım: Hero banners, static sections
🔌

Module Widget

type: 'module'

Diğer modüllerden (Blog, Portfolio vb.) veri çeker. Handlebars template ile render.

data_source: 'Portfolio/list'

getData($settings) → items

{{#each items}} template
Kullanım: Blog listesi, portfolio grid
🧩

Standard Widget

type: 'standard'

HTML/CSS/JS içeriği DB'de saklanır. Dinamik items ve settings desteği.

content_html + content_css + content_js

item_schema (JSON) → WidgetItems
settings_schema (JSON) → Settings
Kullanım: Card grids, testimonials

⚙️ Custom Override (is_custom: true)

TenantWidget'ın is_custom = true olduğunda, merkezi widget şablonu yerine custom_html/css/js alanları kullanılır. Bu sayede tenant tamamen farklı bir tasarım uygulayabilir.

Render Süreci (Frontend)

📝 Basit Anlatım

Blade dosyasında @widget(5) yazdığınızda:

  1. Sistem önce cache'e bakar (24 saat boyunca saklanır)
  2. Cache'te yoksa veritabanından widget bilgilerini çeker
  3. Şablon içindeki {{değişkenler}} ayarlarla değiştirilir
  4. Dinamik items varsa döngüyle işlenir
  5. Sonuç HTML olarak döner, CSS/JS ayrı dosyalara eklenir

📊 Render Akış Şeması

1
@widget(tenantWidgetId) → Blade Directive tetiklenir
2
widget_by_id(id) → Helper function çağrılır
3
Cache Check
Key: widget_{tenantId}_widget_{widgetId} TTL: 24 saat
4
TenantWidget::with('widget', 'items')->find(id)
5
Template Processing
processItems(html, items) → {{#each items}} döngüleri
processVariables(html, settings) → {{variable}} değişkenler
processConditionalBlocks(html, settings) → {{#if}} koşulları
6
CSS/JS Collection → Footer'da Output

🌐 Frontend Entegrasyonu

📌 Blade Directives

@widget(tenantWidgetId)

ID'ye göre tekil widget render

@widgetblock('slug')

Slug'a göre widget render

@widgets('position')

Pozisyondaki tüm widget'ları render

@parsewidgets($content)

İçerikteki shortcode'ları işle

@modulewidget(widgetId)

Module widget render

🔧 PHP Helper Functions

widget_by_id($id, $params, $cacheTtl)

TenantWidget ID ile render

widget_by_slug($slug, $params)

Widget slug ile render

widgets_by_position($position)

Pozisyondaki widget'lar

parse_widget_shortcodes($content)

[[widget:slug]] shortcode'ları işle

module_widget_by_id($id, $params)

Module widget render

🔤 Template Syntax

{{variable}}

Değişken yerleştirme

{{widget.title}}

Widget ayarlarından değer

{{#if show_button}}...{{/if}}

Koşullu render

{{#each items}}...{{/each}}

Items döngüsü

🎛️ Admin Panel (Livewire Components)

Component Route Fonksiyon
WidgetComponent /admin/widgetmanagement Widget listesi, instance oluştur
WidgetManageComponent /admin/widgetmanagement/manage/{id} Widget şablonunu düzenle
WidgetGalleryComponent /admin/widgetmanagement/gallery Widget galerisi
WidgetSettingsComponent /admin/widgetmanagement/settings/{id} TenantWidget ayarları
WidgetItemComponent /admin/widgetmanagement/items/{id} Widget items yönetimi
WidgetCodeEditorComponent /admin/widgetmanagement/code-editor/{id} HTML/CSS/JS editör
WidgetFormBuilderComponent /admin/widgetmanagement/form-builder/{id} Schema builder

🏢 Tenant Kullanım Senaryoları

📌 Senaryo 1: Widget Instance Oluşturma

Admin Panel:
  1. Widget Galerisi'ne git
  2. "Hero Section" widget'ını seç
  3. "Instance Oluştur" tıkla
  4. Ayarları doldur
  5. Kaydet
Arka Planda:
  1. Widget (central) okunur
  2. TenantWidget (tenant DB) oluşturulur
  3. settings JSON olarak kaydedilir
  4. Cache temizlenir

📌 Senaryo 2: Widget Items Yönetimi

"Card Grid" widget'ına dinamik kartlar eklemek için:

  1. Widget Items sayfasına git → "Yeni Item Ekle"
  2. Form'u doldur (item_schema'dan dinamik oluşturulur)
  3. Kaydet → WidgetItem oluşur (tenant DB)
  4. Render'da {{#each items}} döngüsü çalışır

📌 Senaryo 3: Custom Override

Merkezi şablon yetersizse:

  1. "Özel HTML Kullan" aktifle (is_custom=true)
  2. Kod editöründe custom_html/css/js yaz
  3. Merkezi şablon bypass edilir

🚀 Cache Stratejisi

📊 Konfigürasyon

Driver: Redis (Laravel Cache)
TTL: 1440 dk (24 saat)
Key Format: widget_{tenantId}_widget_{id}

🔄 Invalidation

  • ✓ TenantWidget güncellendi → Cache temizle
  • ✓ WidgetItem eklendi/silindi → Cache temizle
  • ✓ Widget şablonu değişti → Tüm cache temizle

📦 Mevcut Widget Şablonları

🎯 Hero

  • • blocks/hero/simple
  • • blocks/hero/hero-1
  • • blocks/hero/ixtif-hero

📐 Layout

  • • one-column, two-columns
  • • three-columns, four-columns
  • • masonry-grid, asymmetric-grid

📝 Form

  • • contact-form
  • • contact-assistant
  • • muzibu-contact

🔌 Module

  • • portfolio/list, portfolio/detail
  • • page/home, page/recent
  • • announcement/list

🃏 Cards

  • • cards/basic
  • • cards/grid

🎨 Diğer

  • • testimonials/basic
  • • features/basic
  • • media/image, content/*

📋 Özet

Central DB
  • • widgets
  • • widget_categories
  • • widget_modules
Tenant DB
  • • tenant_widgets
  • • widget_items
Widget Types
  • • file (Blade view)
  • • module (Data source)
  • • standard (DB content)
Services
  • • widget.service
  • • widget.item.service
  • • shortcode.parser

🛠️ Pratik Örnekler: Yeni Widget Oluşturma

Aşağıda 3 farklı widget türü için adım adım oluşturma süreci ve veritabanı kayıtları gösterilmektedir.

1️⃣ Basit Hero Widget (Slayt Yok)

📝 Basit Anlatım

Tek bir arka plan görseli, başlık, alt başlık ve buton içeren statik hero bölümü. Slayt/carousel yok, sadece sabit bir görsel ve metin. En basit hero türü.

🏛️ Central DB: widgets tablosu

{
  "id": 15,
  "widget_category_id": 1,  // Hero kategorisi
  "name": "Basit Hero",
  "slug": "simple-hero",
  "type": "standard",       // DB'de HTML saklanır
  "has_items": false,       // ❌ Item YOK (slayt yok)
  "item_schema": null,      // Item olmadığı için boş

  "settings_schema": [
    {
      "name": "title",
      "label": "Başlık",
      "type": "text",
      "required": true
    },
    {
      "name": "subtitle",
      "label": "Alt Başlık",
      "type": "textarea",
      "required": false
    },
    {
      "name": "background_image",
      "label": "Arka Plan Görseli",
      "type": "image",
      "required": true
    },
    {
      "name": "button_text",
      "label": "Buton Metni",
      "type": "text",
      "required": false
    },
    {
      "name": "button_url",
      "label": "Buton Linki",
      "type": "url",
      "required": false
    },
    {
      "name": "overlay_opacity",
      "label": "Overlay Opaklığı",
      "type": "range",
      "properties": {
        "min": 0,
        "max": 100,
        "default": 50
      }
    }
  ],

  "content_html": "<section class=\"hero-simple\"
    style=\"background-image: url({{background_image}})\">
    <div class=\"overlay\" style=\"opacity: {{overlay_opacity}}%\"></div>
    <div class=\"content\">
      <h1>{{title}}</h1>
      {{#if subtitle}}<p>{{subtitle}}</p>{{/if}}
      {{#if button_text}}
        <a href=\"{{button_url}}\" class=\"btn\">{{button_text}}</a>
      {{/if}}
    </div>
  </section>",

  "content_css": ".hero-simple {
    height: 80vh;
    background-size: cover;
    position: relative;
  }",

  "is_active": true
}

🏢 Tenant DB: tenant_widgets

{
  "id": 1,
  "widget_id": 15,          // Central'daki widget ID
  "display_title": "Ana Sayfa Hero",
  "order": 1,
  "is_active": true,
  "is_custom": false,       // Merkezi şablonu kullan

  "settings": {
    "title": "Forklift Çözümlerinde Lider",
    "subtitle": "25 yıllık tecrübe ile yanınızdayız",
    "background_image": "/storage/hero-bg.jpg",
    "button_text": "Hemen İletişime Geç",
    "button_url": "/iletisim",
    "overlay_opacity": 60
  }
}

// ⚠️ widget_items tablosuna kayıt YOK!
// Çünkü has_items = false
🌐 Frontend Kullanımı:
@widget(1)

settings'teki değerler {{variable}} yerine yerleştirilir.

📊 Özet: Basit Hero

has_items false
item_schema null (gerekmez)
settings_schema ✓ Tanımlı (title, image, button...)
widget_items kaydı YOK
Veri kaynağı tenant_widgets.settings JSON

2️⃣ Slider Hero Widget (Slayt Var)

📝 Basit Anlatım

Birden fazla slayt içeren carousel hero. Her slaytın kendi görseli, başlığı ve butonu var. Slayt sayısı dinamik - admin panelden yeni slayt ekleyip çıkarabilirsiniz. Fark: has_items = true ve widget_items tablosuna kayıtlar eklenir.

🏛️ Central DB: widgets tablosu

{
  "id": 16,
  "widget_category_id": 1,
  "name": "Slider Hero",
  "slug": "slider-hero",
  "type": "standard",
  "has_items": true,        // ✅ Item VAR (slaytlar)

  "item_schema": [
    {
      "name": "title",
      "label": "Slayt Başlığı",
      "type": "text",
      "required": true,
      "system": true,
      "protected": true
    },
    {
      "name": "subtitle",
      "label": "Slayt Alt Başlığı",
      "type": "textarea",
      "required": false
    },
    {
      "name": "image",
      "label": "Slayt Görseli",
      "type": "image",
      "required": true
    },
    {
      "name": "button_text",
      "label": "Buton Metni",
      "type": "text"
    },
    {
      "name": "button_url",
      "label": "Buton Linki",
      "type": "url"
    },
    {
      "name": "is_active",
      "label": "Durum",
      "type": "switch",
      "system": true,
      "protected": true,
      "properties": {
        "active_label": "Aktif",
        "inactive_label": "Pasif",
        "default_value": true
      }
    }
  ],

  "settings_schema": [
    {
      "name": "autoplay",
      "label": "Otomatik Oynat",
      "type": "switch",
      "properties": { "default_value": true }
    },
    {
      "name": "interval",
      "label": "Geçiş Süresi (ms)",
      "type": "number",
      "properties": { "default": 5000 }
    },
    {
      "name": "show_dots",
      "label": "Noktaları Göster",
      "type": "switch"
    },
    {
      "name": "show_arrows",
      "label": "Okları Göster",
      "type": "switch"
    }
  ],

  "content_html": "<div class=\"hero-slider\"
    data-autoplay=\"{{autoplay}}\"
    data-interval=\"{{interval}}\">
    {{#each items}}
      <div class=\"slide\">
        <img src=\"{{image}}\" alt=\"{{title}}\">
        <div class=\"slide-content\">
          <h2>{{title}}</h2>
          {{#if subtitle}}<p>{{subtitle}}</p>{{/if}}
          {{#if button_text}}
            <a href=\"{{button_url}}\">{{button_text}}</a>
          {{/if}}
        </div>
      </div>
    {{/each}}
  </div>",

  "js_files": ["/js/swiper.min.js"],
  "css_files": ["/css/swiper.min.css"],
  "is_active": true
}

🏢 Tenant DB

tenant_widgets:
{
  "id": 2,
  "widget_id": 16,
  "display_title": "Ana Sayfa Slider",
  "order": 1,
  "is_active": true,
  "is_custom": false,

  "settings": {
    "autoplay": true,
    "interval": 5000,
    "show_dots": true,
    "show_arrows": true
  }
}
widget_items (3 slayt):
// Slayt 1
{
  "id": 1,
  "tenant_widget_id": 2,
  "order": 1,
  "content": {
    "title": "Forklift Kiralama",
    "subtitle": "Günlük, haftalık, aylık",
    "image": "/storage/slide1.jpg",
    "button_text": "Fiyat Al",
    "button_url": "/kiralama",
    "is_active": true
  }
}

// Slayt 2
{
  "id": 2,
  "tenant_widget_id": 2,
  "order": 2,
  "content": {
    "title": "Transpalet Satış",
    "subtitle": "En uygun fiyatlar",
    "image": "/storage/slide2.jpg",
    "button_text": "Ürünleri Gör",
    "button_url": "/urunler",
    "is_active": true
  }
}

// Slayt 3
{
  "id": 3,
  "tenant_widget_id": 2,
  "order": 3,
  "content": {
    "title": "Teknik Servis",
    "subtitle": "7/24 destek",
    "image": "/storage/slide3.jpg",
    "button_text": "Servis Çağır",
    "button_url": "/servis",
    "is_active": true
  }
}

⚙️ Render Süreci

1 tenant_widgets.settings'ten autoplay, interval değerlerini al
2 widget_items'tan is_active=true olanları çek (order'a göre sıralı)
3 {{#each items}} döngüsünde her slaytı render et
4 swiper.min.js ve swiper.min.css footer'a eklenir

📊 Özet: Slider Hero

has_items true
item_schema ✓ Tanımlı (title, image, button...)
settings_schema ✓ Tanımlı (autoplay, interval...)
widget_items kaydı ✓ Her slayt için 1 kayıt
Veri kaynağı settings + widget_items.content

3️⃣ Hizmetler Widget (Kart Grid)

📝 Basit Anlatım

Şirketin sunduğu hizmetleri gösteren kart yapısı. Her kartın ikonu, başlığı, açıklaması ve linki var. Yeni hizmet eklemek için widget_items'a yeni kayıt eklenir. Grid layout ile responsive görüntülenir (mobilde 1, tablette 2, masaüstünde 3-4 sütun).

🏛️ Central DB: widgets tablosu

{
  "id": 17,
  "widget_category_id": 3,  // İçerik kategorisi
  "name": "Hizmetler Grid",
  "slug": "services-grid",
  "type": "standard",
  "has_items": true,        // ✅ Her hizmet bir item

  "item_schema": [
    {
      "name": "title",
      "label": "Hizmet Başlığı",
      "type": "text",
      "required": true,
      "system": true
    },
    {
      "name": "description",
      "label": "Açıklama",
      "type": "textarea",
      "required": false
    },
    {
      "name": "icon",
      "label": "İkon (FontAwesome)",
      "type": "icon",
      "required": false,
      "properties": {
        "default": "fa-cog"
      }
    },
    {
      "name": "image",
      "label": "Hizmet Görseli",
      "type": "image",
      "required": false
    },
    {
      "name": "link_url",
      "label": "Detay Linki",
      "type": "url"
    },
    {
      "name": "link_text",
      "label": "Link Metni",
      "type": "text",
      "properties": {
        "default": "Detaylı Bilgi"
      }
    },
    {
      "name": "is_active",
      "label": "Durum",
      "type": "switch",
      "system": true,
      "protected": true
    }
  ],

  "settings_schema": [
    {
      "name": "section_title",
      "label": "Bölüm Başlığı",
      "type": "text"
    },
    {
      "name": "section_subtitle",
      "label": "Bölüm Alt Başlığı",
      "type": "textarea"
    },
    {
      "name": "columns",
      "label": "Sütun Sayısı",
      "type": "select",
      "properties": {
        "options": [
          {"value": "2", "label": "2 Sütun"},
          {"value": "3", "label": "3 Sütun"},
          {"value": "4", "label": "4 Sütun"}
        ],
        "default": "3"
      }
    },
    {
      "name": "card_style",
      "label": "Kart Stili",
      "type": "select",
      "properties": {
        "options": [
          {"value": "simple", "label": "Basit"},
          {"value": "bordered", "label": "Kenarlıklı"},
          {"value": "shadow", "label": "Gölgeli"}
        ]
      }
    },
    {
      "name": "show_icon",
      "label": "İkon Göster",
      "type": "switch"
    },
    {
      "name": "show_image",
      "label": "Görsel Göster",
      "type": "switch"
    }
  ],

  "content_html": "<section class=\"services-section\">
    {{#if section_title}}
      <div class=\"section-header\">
        <h2>{{section_title}}</h2>
        {{#if section_subtitle}}<p>{{section_subtitle}}</p>{{/if}}
      </div>
    {{/if}}

    <div class=\"grid grid-cols-{{columns}}\">
      {{#each items}}
        <div class=\"service-card card-{{../card_style}}\">
          {{#if ../show_image}}
            {{#if image}}<img src=\"{{image}}\">{{/if}}
          {{/if}}
          {{#if ../show_icon}}
            <i class=\"fas {{icon}}\"></i>
          {{/if}}
          <h3>{{title}}</h3>
          {{#if description}}<p>{{description}}</p>{{/if}}
          {{#if link_url}}
            <a href=\"{{link_url}}\">{{link_text}}</a>
          {{/if}}
        </div>
      {{/each}}
    </div>
  </section>",

  "content_css": ".services-section { padding: 60px 0; }
  .service-card { padding: 30px; text-align: center; }
  .card-bordered { border: 1px solid #e5e7eb; }
  .card-shadow { box-shadow: 0 4px 6px rgba(0,0,0,0.1); }",

  "is_active": true
}

🏢 Tenant DB

tenant_widgets:
{
  "id": 3,
  "widget_id": 17,
  "display_title": "Hizmetlerimiz Bölümü",
  "order": 2,
  "is_active": true,
  "is_custom": false,

  "settings": {
    "section_title": "Hizmetlerimiz",
    "section_subtitle": "Size en iyi hizmeti sunuyoruz",
    "columns": "3",
    "card_style": "shadow",
    "show_icon": true,
    "show_image": false
  }
}
widget_items (4 hizmet):
// Hizmet 1
{
  "id": 4,
  "tenant_widget_id": 3,
  "order": 1,
  "content": {
    "title": "Forklift Kiralama",
    "description": "Kısa ve uzun vadeli kiralama",
    "icon": "fa-truck",
    "link_url": "/hizmetler/kiralama",
    "link_text": "Detaylı Bilgi",
    "is_active": true
  }
}

// Hizmet 2
{
  "id": 5,
  "tenant_widget_id": 3,
  "order": 2,
  "content": {
    "title": "Teknik Servis",
    "description": "7/24 arıza destek hizmeti",
    "icon": "fa-wrench",
    "link_url": "/hizmetler/servis",
    "link_text": "Servis Çağır",
    "is_active": true
  }
}

// Hizmet 3
{
  "id": 6,
  "tenant_widget_id": 3,
  "order": 3,
  "content": {
    "title": "Yedek Parça",
    "description": "Orijinal ve muadil parçalar",
    "icon": "fa-cogs",
    "link_url": "/hizmetler/yedek-parca",
    "link_text": "Parça Bul",
    "is_active": true
  }
}

// Hizmet 4
{
  "id": 7,
  "tenant_widget_id": 3,
  "order": 4,
  "content": {
    "title": "Eğitim",
    "description": "Operatör sertifika eğitimi",
    "icon": "fa-graduation-cap",
    "link_url": "/hizmetler/egitim",
    "link_text": "Eğitime Katıl",
    "is_active": true
  }
}

📋 Admin Panel'den Oluşturma Adımları

1. Widget Instance Oluştur:
  1. Widget Galerisi → "Hizmetler Grid" seç
  2. "Instance Oluştur" tıkla
  3. Bölüm başlığını yaz
  4. Sütun sayısı, kart stili seç
  5. Kaydet → tenant_widgets'a kayıt oluşur
2. Hizmetleri Ekle:
  1. "Items" sekmesine git
  2. "Yeni Item Ekle" tıkla
  3. Hizmet başlığı, açıklama, ikon gir
  4. Kaydet → widget_items'a kayıt
  5. Diğer hizmetler için tekrarla
  6. Sürükle-bırak ile sırala

📊 Özet: Hizmetler Grid

has_items true
item_schema ✓ Tanımlı (title, icon, desc, link...)
settings_schema ✓ Tanımlı (columns, style, show_icon...)
widget_items kaydı ✓ Her hizmet için 1 kayıt
Render settings + {{#each items}}

📊 Karşılaştırma: 3 Örnek Widget

Özellik Basit Hero Slider Hero Hizmetler Grid
has_items false true true
item_schema null slayt alanları hizmet alanları
settings_schema title, image, button autoplay, interval columns, style
Central DB kaydı widgets: 1 widgets: 1 widgets: 1
Tenant DB kaydı tenant_widgets: 1 tenant_widgets: 1
widget_items: N
tenant_widgets: 1
widget_items: N
Dinamik içerik ❌ Yok ✓ Slaytlar ✓ Hizmet kartları
Template döngüsü {{#each items}} {{#each items}}

💡 Karar Rehberi

  • has_items = false: Sabit içerik, tek seferlik ayar (hero, banner, CTA)
  • has_items = true: Dinamik içerik, ekle/çıkar/sırala (slider, grid, liste)
  • settings_schema: Widget geneli için ayarlar (renkler, layout, animasyon)
  • item_schema: Her item için tekrarlanan alanlar (başlık, görsel, link)