📝 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
🗄️ 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 kategorileri (hierarchical)
Widget şablonları (master templates)
Widget-Module pivot tablosu
🏢 Tenant Database (tenant_X)
Kiracının widget örnekleri
Widget'ların dinamik öğeleri
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
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
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
⚙️ 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:
- Sistem önce cache'e bakar (24 saat boyunca saklanır)
- Cache'te yoksa veritabanından widget bilgilerini çeker
- Şablon içindeki {{değişkenler}} ayarlarla değiştirilir
- Dinamik items varsa döngüyle işlenir
- Sonuç HTML olarak döner, CSS/JS ayrı dosyalara eklenir
📊 Render Akış Şeması
@widget(tenantWidgetId)
→ Blade Directive tetiklenir
widget_by_id(id)
→ Helper function çağrılır
widget_{tenantId}_widget_{widgetId} TTL: 24 saatTenantWidget::with('widget', 'items')->find(id)
processItems(html, items) → {{#each items}} döngüleriprocessVariables(html, settings) → {{variable}} değişkenlerprocessConditionalBlocks(html, settings) → {{#if}} koşulları🌐 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
Değişken yerleştirme
Widget ayarlarından değer
Koşullu render
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
- Widget Galerisi'ne git
- "Hero Section" widget'ını seç
- "Instance Oluştur" tıkla
- Ayarları doldur
- Kaydet
- Widget (central) okunur
- TenantWidget (tenant DB) oluşturulur
- settings JSON olarak kaydedilir
- Cache temizlenir
📌 Senaryo 2: Widget Items Yönetimi
"Card Grid" widget'ına dinamik kartlar eklemek için:
- Widget Items sayfasına git → "Yeni Item Ekle"
- Form'u doldur (item_schema'dan dinamik oluşturulur)
- Kaydet → WidgetItem oluşur (tenant DB)
- Render'da {{#each items}} döngüsü çalışır
📌 Senaryo 3: Custom Override
Merkezi şablon yetersizse:
- "Özel HTML Kullan" aktifle (is_custom=true)
- Kod editöründe custom_html/css/js yaz
- Merkezi şablon bypass edilir
🚀 Cache Stratejisi
📊 Konfigürasyon
🔄 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
- • widgets
- • widget_categories
- • widget_modules
- • tenant_widgets
- • widget_items
- • file (Blade view)
- • module (Data source)
- • standard (DB content)
- • 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
@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
{
"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
}
}
// 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
📊 Ö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
{
"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
}
}
// 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ı
- Widget Galerisi → "Hizmetler Grid" seç
- "Instance Oluştur" tıkla
- Bölüm başlığını yaz
- Sütun sayısı, kart stili seç
- Kaydet → tenant_widgets'a kayıt oluşur
- "Items" sekmesine git
- "Yeni Item Ekle" tıkla
- Hizmet başlığı, açıklama, ikon gir
- Kaydet → widget_items'a kayıt
- Diğer hizmetler için tekrarla
- 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)