v9 — KESİN PLAN 24 Şubat 2026

Dinamik İçerik Sistemi

Tüm kararlar, DB şeması, dosya haritası, UX kuralları, greeting, zaman yönetimi, 18 TODO.

Kesinleşen Kararlar (14 Madde)

B2B Only — Her kullanıcı bir işletme sahibi. Guest/bireysel yok. Soundtrack Your Brand modeli.
5 Polimorfik Tip — Playlist, Album, Radio, Genre, Sector. Aynı koleksiyonda karışık kullanılır.
Users Sektörusers.sector_id FK → muzibu_sectors.sector_id. Kullanıcı profilinden seçer.
Esnek Saat Aralığı — Sabit slot isimleri yok. Admin start_hour ve end_hour seçer. Saat seçici sadece düz saatler: 00:00, 01:00, 02:00 ... 23:00.
Koleksiyon Tipleri — curated (el seçimi), daypart (zamana göre), sector (sektöre göre), featured (öne çıkan).
Tıklama Davranışları — Radio → hemen çalar (sayfa değişmez). Playlist/Album → şarkı listesi açılır. Genre → genre sayfası. Sector → sektör sayfası.
"Tümü" Butonu — Koleksiyonda 6'dan fazla öğe varsa görünür → /koleksiyon/{slug}. 6 ve altı → buton gizli, yatay scroll yeterli.
Sıralama — Koleksiyon içi: position (sürükle-bırak). Koleksiyonlar arası: priority (büyük = önce).
Cache — Key: smart_feed:{userId}:{sectorId}:{saat_block}, TTL: 300 saniye.
Koleksiyon SayfasıGET /koleksiyon/{slug} → Grid görünüm, aynı tıklama kuralları.
Zaman Güncelleme — Tek setTimeout, saat başı +1dk'da tetiklenir. Client RAM: 8 byte. CPU: sıfır.
Otomatik Sessiz Yenileme — Sadece anasayfa #feed-container. fetch('/api/feed-partial')innerHTML swap. Sayfa reload yok, müzik kesilmez. İç sayfalarda hiçbir şey yapma.
Admin Saat Gösterimi — Dropdown'larda sadece düz saatler: 00:00, 01:00, 02:00 ... 23:00. Dakika/yarım saat yok.
Greeting Sistemi — Koleksiyonlardan bağımsız, hardcoded PHP. Saate göre 4 selamlama + kullanıcı adı + sektör adı. DB sorgusu yok, admin ayarı yok, sadece SmartFeedService::getGreeting().

Greeting Sistemi YENİ

4 Zaman Dilimi

Saat AralığıSelamlamaİkon
06:00 - 12:00Günaydın☀️
12:00 - 18:00İyi günler🌤️
18:00 - 22:00İyi akşamlar🌅
22:00 - 06:00İyi geceler🌙

Kişiselleştirilmiş Alt Başlık

☀️
Günaydın, Ahmet!
Kafe & Restoran için seçilmiş içerikler
sector_id: 1
🌤️
İyi günler, Zeynep!
Spor Salonu & Fitness için seçilmiş içerikler
sector_id: 3
🌙
İyi geceler, Can!
Senin için seçilmiş içerikler
sector_id: NULL

SmartFeedService::getGreeting()

public function getGreeting(User $user): array
{
    $hour = now()->hour;

    if ($hour >= 6 && $hour < 12)       $greet = 'Günaydın';
    elseif ($hour >= 12 && $hour < 18)  $greet = 'İyi günler';
    elseif ($hour >= 18 && $hour < 22)  $greet = 'İyi akşamlar';
    else                                 $greet = 'İyi geceler';

    $sector = $user->sector?->title;  // eager loaded

    return [
        'title'    => "$greet, {$user->name}!",
        'subtitle' => $sector
            ? "{$sector} için seçilmiş içerikler"
            : 'Senin için seçilmiş içerikler',
    ];
}
Koleksiyonlardan bağımsız. DB sorgusu yok (sector eager loaded). Admin ayarı yok. Saat başı yenilemede greeting de güncellenir (partial içinde).
Saat başı yenilemede greeting de güncellenir
feed-collections.blade.php partial'ı greeting + koleksiyonların ikisini de içerir. Saat başı innerHTML swap yapıldığında "Günaydın" → "İyi günler" otomatik değişir.

Zaman Yönetimi

Admin Panelde Saat Seçimi

Bu koleksiyon 09:00-14:00 arası görünür
Dropdown: 00:00, 01:00, 02:00 ... 23:00 (24 seçenek, sadece düz saat)

display_rules JSON

{
  "start_hour": 9,       // 09:00 (int, 0-23)
  "end_hour": 14,        // 14:00 (int, 0-23)
  "days": ["mon","tue","wed","thu","fri","sat","sun"],
  "is_always": false     // true ise start/end yoksayılır
}

Otomatik Sessiz Yenileme Akışı

1
Sayfa yüklenir (08:29)
PHP: (60 - 29) * 60 - saniye + 60 = ~32dk → setTimeout
2
09:01'de timeout tetiklenir
fetch('/api/feed-partial') → sunucu yeni saatle HTML render eder (greeting + koleksiyonlar)
3
Sessiz güncelleme
#feed-container.innerHTML = yeni HTML → greeting + koleksiyonlar güncellenir, müzik kesilmez
4
Yeni timeout → 10:01 için
Döngü devam. Sayfa açık kaldığı sürece her saat başı +1dk.
Yenileme ÇALIŞIR
Anasayfa (/) — #feed-container varsa
Yenileme ÇALIŞMAZ
İç sayfalar (koleksiyon, playlist, albüm, genre, sektör detay)

Frontend JS (Tamamı)

// Sadece anasayfada çalışır (~15 satır, sıfır framework)
(function(){
    var el = document.getElementById('feed-container');
    if (!el) return;

    function refresh(){
        fetch('/api/feed-partial')
            .then(function(r){ return r.text() })
            .then(function(html){
                el.innerHTML = html;
                schedule();
            });
    }

    function schedule(){
        var d = new Date();
        var ms = (60 - d.getMinutes()) * 60000 - d.getSeconds() * 1000 + 60000;
        setTimeout(refresh, ms); // saat başı +1dk
    }

    setTimeout(refresh, {{ $msUntilNextHour }}); // ilk timeout (PHP hesaplar)
})();
RAM: 8 byte. CPU: sıfır. Framework: yok. Interval: yok.

Veritabanı Şeması (3 Migration)

Migration 1 users → sector_id ekleme
KolonTipDefaultIndexAçıklama
sector_id BIGINT UNSIGNED NULL NULL INDEX FK → muzibu_sectors.sector_id. corporate_account_id sonrası.
Migration 2 muzibu_content_collections (Yeni)
KolonTipDefaultAçıklama
collection_idBIGINT UNSIGNED AUTOPKBirincil anahtar
titleJSON{"tr": "...", "en": "..."}
slugJSON{"tr": "sabah-kafe-mix"}
descriptionJSON NULLNULLÇoklu dil açıklama
typeENUMcurated | daypart | sector | featured
iconVARCHAR(50) NULLNULLFontAwesome ikon
colorVARCHAR(20) NULLNULLHex renk
media_idBIGINT UNSIGNED NULLNULLKapak görseli
display_rulesJSON NULLNULL{start_hour, end_hour, days, is_always}
sector_rulesJSON NULLNULL{mode, sector_ids, show_to_all}
business_rulesJSON NULLNULL{subscription, min_months}
priorityINT DEFAULT 0INDEXBüyük = önce göster
is_activeBOOLEAN DEFAULT 1INDEXAktif/Pasif
is_featuredBOOLEAN DEFAULT 0Öne çıkan
cache_ttlINT DEFAULT 300Cache süresi (saniye)
timestampsTIMESTAMPcreated_at, updated_at
Migration 3 muzibu_collection_items (Yeni)
KolonTipDefaultAçıklama
idBIGINT UNSIGNED AUTOPKBirincil anahtar
collection_idBIGINT UNSIGNEDFK + INDEX→ content_collections.collection_id
itemable_typeVARCHAR(255)COMPOSITEPlaylist | Album | Radio | Genre | Sector
itemable_idBIGINT UNSIGNEDINDEXPolimorfik FK
positionINT DEFAULT 0INDEXSürükle-bırak sıralama
is_activeBOOLEAN DEFAULT 1Aktif/Pasif
timestampsTIMESTAMPcreated_at, updated_at
Unique: (collection_id, itemable_type, itemable_id)

UX Kuralları

TipTıklanıncaSayfa Değişir?
Radyo Hemen çalar Hayır — player bar güncellenir
Playlist Şarkı listesi Evet — playlist detay
Albüm Şarkı listesi Evet — albüm detay
Genre Genre sayfası Evet — genre içerikleri
Sektör Sektör sayfası Evet — sektör içerikleri
6 ve altı öğe
"Tümü" butonu gizli. Yatay scroll yeterli.
7+ öğe
"Tümü" görünür → /koleksiyon/{slug} grid sayfası.

Anasayfa Wireframe

┌─────────────────────────────────────────┐
│ Header  |  Player Bar                   │ ← dokunulmaz
├─────────────────────────────────────────┤
│ ☀️ Günaydın, Ahmet!                     │ ← greeting
│ Kafe & Restoran için seçilmiş içerikler  │ ← sektör adı
│                                         │
│ ★ Editörün Seçimi          Tümü ›      │ ← 8 öğe, Tümü VAR
│ [Radio][Playl][Album][Playl][Genre]→  │
│                                         │
│ ☕ Sabah Kafe Mix                       │ ← 4 öğe, Tümü YOK
│ [Radio][Playl][Playl][Album] →          │
│                                         │
│ 👆 Türkçe Pop Klasikler     Tümü ›      │ ← 9 öğe, Tümü VAR
│ [Radio][Playl][Album][Playl][Genre]→  │
├─────────────────────────────────────────┤
│ Footer                                  │ ← dokunulmaz
└─────────────────────────────────────────┘
     ↑
  #feed-container (greeting + koleksiyonlar)
  saat başı sessiz innerHTML swap

Dosya & Route Haritası

Modules/Muzibu/
├── database/migrations/
│   ├── ..._add_sector_id_to_users.php              ← M1 central
│   ├── ..._create_content_collections.php           ← M2 central
│   ├── ..._create_collection_items.php              ← M3 central
│   └── tenant/  (aynı 3 dosya)
├── App/Models/
│   ├── ContentCollection.php
│   └── CollectionItem.php
├── App/Services/
│   └── SmartFeedService.php                       ← getGreeting() + getFeed()
├── App/Http/Controllers/Front/
│   └── CollectionController.php
├── App/Http/Livewire/
│   ├── CollectionComponent.php
│   └── CollectionManageComponent.php
└── resources/views/livewire/
    ├── collection-component.blade.php
    └── collection-manage-component.blade.php

resources/views/themes/muzibu/
├── index.blade.php                                 ← güncelleme
├── partials/
│   └── feed-collections.blade.php                  ← greeting + koleksiyonlar (AJAX partial)
└── collection/
    └── show.blade.php

app/Models/User.php                               ← sector() relationship
routes/tenant.php                                  ← route'lar

Route'lar

MethodURIHandlerAçıklama
GET/koleksiyon/{slug}CollectionController@showKoleksiyon grid
GET/api/feed-partialClosure (HTML partial)Saat başı sessiz yenileme
GET/admin/koleksiyonlarLivewire: CollectionComponentAdmin liste
GET/admin/koleksiyonlar/manage/{id?}Livewire: CollectionManageComponentAdmin form 5 tab

TODO — 4 Faz, 18 Madde

Faz 1 — Migration & Modeller
1
Migration: users.sector_id
Central + Tenant. BIGINT UNSIGNED NULL. FK → muzibu_sectors.sector_id.
2
Migration: muzibu_content_collections
Central + Tenant. 15 kolon. display_rules: {start_hour, end_hour, days, is_always}.
3
Migration: muzibu_collection_items
Central + Tenant. Polimorfik. Unique: (collection_id, itemable_type, itemable_id).
4
Model: ContentCollection
$translatable, $casts JSON, items() hasMany, activeItems() scope.
5
Model: CollectionItem
itemable() morphTo. Morph map: playlist, album, radio, genre, sector.
6
User.php: sector() relationship + $fillable
belongsTo(Sector::class, 'sector_id', 'sector_id'). $fillable'a sector_id ekle.
Faz 2 — SmartFeedService & Backend
7
SmartFeedService
getGreeting(User): saat→selamlama + isim + sektör. getFeed(User): koleksiyonlar. 3 filtre: time (start_hour/end_hour), sector, business. Cache: smart_feed:{userId}:{sectorId}:{hour}, TTL: 300s.
8
CollectionController@show
GET /koleksiyon/{slug}. items() eager load + morphTo. "Tümü" grid sayfası.
9
HomeController güncelle
SmartFeedService entegrasyonu. $msUntilNextHour hesaplama. feed-collections partial.
10
API endpoint: /api/feed-partial
HTML partial döner (greeting + koleksiyonlar). Saat başı sessiz yenileme için.
11
Route'lar ekle
/koleksiyon/{slug} + /api/feed-partial → tenant.php
Faz 3 — Admin Panel (Livewire)
12
CollectionComponent (Liste)
Tablo: başlık, tip, sektör, saat aralığı (00:00 formatında), öğe sayısı, öncelik, durum. Filtreler + bulk.
13
CollectionManageComponent (Form — 5 Tab)
Tab 1: Temel. Tab 2: Zaman (is_always, start_hour/end_hour dropdown sadece düz saat 00:00-23:00, günler). Tab 3: Sektör (show_to_all, include/exclude, dual listbox). Tab 4: İş Kuralları. Tab 5: İçerikler (5 tip ekleme, Sortable.js).
14
Admin route + menü
/admin/koleksiyonlar, /admin/koleksiyonlar/manage/{id?}. Muzibu menüsüne ekle.
Faz 4 — Frontend
15
feed-collections.blade.php (partial)
Greeting (selamlama + sektör adı) + koleksiyon döngüsü + yatay scroll kartlar. AJAX swap icin bağımsız partial.
16
index.blade.php güncelle
#feed-container + @include partial + setTimeout JS (~15 satır vanilla). "Tümü": items_count > 6.
17
collection/show.blade.php (grid)
"Tümü" tıklanınca açılan grid sayfa. Aynı tıklama kuralları.
18
Radio tıklama: player entegrasyonu
Radio kartına tıklanınca sayfa değişmeden player-core.js üzerinden radyo çalma.
Versiyon Geçmişi
v1 — İlk analiz (Spotify + Apple + Soundtrack benchmark)
v2 — B2B pivot
v3 — Görsel simülasyon
v4 — 5 polimorfik tip kesinleşti
v5 — Örnek veri + Draft admin
v6 — Kullanıcı deneyimi mockup
v7 — İlk kesin plan (6 sabit slot)
v8 — Esnek saat + otomatik sessiz yenileme
v9 — KESİN PLAN: Greeting sistemi eklendi (4 zaman selamlaması + sektör adı, koleksiyonlardan bağımsız, partial içinde)