<p>Bileşen, mevcut tüm sorgu parametrelerini koruyarak sayfalama bağlantılarını yeniden oluşturur:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


const queryString = computed(() => {
const url = new URL(window.location.href);
url.searchParams.delete('page');
const qs = url.searchParams.toString();
return qs ? &${qs} : '';
});

// Akıllı sayfa aralığı: maksimum 7 buton + üç nokta
const pages = computed(() => {
const total = p.value.last_page;
const current = p.value.currentpage;
if (total <= 7) return Array.from({ length: total }, (
, i) => i + 1);
if (current < 4) return [1, 2, 3, 4, 5, '...', total];
if (current >= total - 3) return [1, '...', total - 4, total - 3, total - 2, total];
return [1, '...', current - 1, current, current + 1, '...', total];
});

<p>Her sayfa butonu Inertia'nın <code>&lt;link/&gt;</code> ile SPA navigasyonu kullanır:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight html">

{{ item }}

<p>Ülke filtresi aktif mi? Saklandı. Sıralama yönü? Saklandı. Arama sorgusu? Saklandı. Durum yönetiminde bir gereklilik yok.</p>

<h3>
  <a name="filter-autodiscovery-pattern" href="#filter-autodiscovery-pattern"></a> Filtre Otomatik Keşif Deseni
</h3>

<p>Filtre sistemi yöntem otomatik keşfine dayanıyor:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php">


abstract class Filter {
public function apply(Builder $builder) {
$this->builder = $builder;
foreach ($this->request->all() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], [$value]);
}
}
return $this->builder;
}
}

<p>İstek parametresi <code>country=DE</code> → <code>$filter-&gt;country('DE')</code> çağrısını tetikler. Rota yapılandırması yok. Hiçbir switch ifadesi yok. Yöntem mevcutsa, filtre uygulanır.</p>

<p>Somut bir filtre örneği:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php">


class ContragentsFilter extends Filter {
public function search_string($value = null) {
$words = preg_split('/\s+/', trim($value));
return $this->builder->where(function($query) use ($words) {
foreach ($words as $word) {
$query->whereRaw('LOWER(name) LIKE ?', ['%' . mb_strtolower($word) . '%']);
}
});
}

public function sort_by($value = null) {
    $allowed = ['name', 'country', 'balance', 'created_at'];
    if (in_array($value, $allowed)) {
        $direction = $this->request->input('sort_direction', 'asc');
        return $this->builder->orderBy($value, $direction);
    }
    return $this->builder->orderBy('name', 'asc');
}

}

<p>Beyaz listeye alınmış sıralama sütunları SQL enjeksiyonunu engeller. Kelime bazında arama eşleştirme. <code>starts_with()</code> yöntemi ile alfabetik navigasyon. Ürünler için hızlı filtre ön ayarları (<code>min_stock</code>, <code>has_reserved</code>).</p>

<h3>
  <a name="frontend-filter-integration" href="#frontend-filter-integration"></a> Ön Uç Filtre Entegrasyonu
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


// Inertia yönlendirme navigasyonu ile durum koruma
const performSearch = () => {
const params = {};
if (filters.value.search) params.search_string = filters.value.search;
if (filters.value.country) params.country = filters.value.country;
// ... tüm filtre parametreleri

router.get(route(routeName.value), params, {
    preserveState: true,    // Bileşen durumu korunur
    preserveScroll: true,   // Kaydırma pozisyonu korunur
    replace: true,          // Tek bir geçmiş girişi
});

};

<p><code>preserveState: true</code> anahtardır. Bileşen reaktif durumu Inertia yönlendirmeleri sırasında korunur. Filtreleri bir Vuex store veya yerel depolama kullanarak saklamaya ihtiyaç yok.</p>

<h2>
  <a name="modal-amp-popup-system" href="#modal-amp-popup-system"></a> Modal &amp; Popup Sistemi
</h2>

<p>LaraFoundry, karmaşık etkileşimler için özel modallar ile onaylar için SweetAlert2 kullanarak bir hibrit yaklaşım benimsemektedir.</p>

<h3>
  <a name="custom-form-modal-inertia-useform" href="#custom-form-modal-inertia-useform"></a> Özel Form Modal (Inertia useForm)
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight vue">



<p><code>form.processing</code> butonu devre dışı bırakır. <code>form.errors</code> sunucu tarafı doğrulamayı gösterir. <code>onSuccess</code> modali kapatır. Üç özellik, sıfır özelleştirilmiş kod.</p>

<h3>
  <a name="async-dataloading-modal" href="#async-dataloading-modal"></a> Asenkron Veri Yükleme Modalı
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


const loading = ref(false);
const contragent = ref(null);

watch(() => props.visible, (newVal) => {
if (newVal && props.contragentUuid) {
fetchContragentData();
}
});

const fetchContragentData = async () => {
loading.value = true;
try {
const response = await axios.get(route('contragents.view_data', props.contragentUuid));
contragent.value = response.data.contragent;
} catch (err) {
error.value = t('Failed to load data');
} finally {
loading.value = false;
}
};

<p>Modal görünür hale geldiğinde veri çekilir. Yükleme döngüsü. Hata durumu. Farklı veri görünümleri için sekmeler. ESC tuşu ile kapama.</p>

<h3>
  <a name="sweetalert2-for-confirmations" href="#sweetalert2-for-confirmations"></a> Onaylar için SweetAlert2
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


const result = await Swal.fire({
title: t('Remove Employee?'),
text: t('This action will immediately remove the employee.'),
icon: 'error',
showCancelButton: true,
confirmButtonColor: '#ef4444',
});

if (result.isConfirmed) {
router.delete(route('my_company.employees.remove', employee.id));
}

<p>Tek await. Özel bileşen yok. Erişilebilir. Stilize edilmiş.</p>

<h3>
  <a name="layoutlevel-modal-orchestration" href="#layoutlevel-modal-orchestration"></a> Düzen Seviyesinde Modal Orkestrasyonu
</h3>

<p>Genel modallar, sağlama/iletişim yoluyla tutulmaktadır:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


// AuthLayout.vue
const modal = reactive({
view: false,
title: t('Confirm the action'),
content: null,
targetUrl: null,
modalType: 'confirmable',
});

provide('showViewContragentModal', showViewContragentModal);

<p>Sayfalar tetikleyici işlevleri enjekte eder. Düzen, z-index'leri, overlay yığınları ve geçiş sürelerini yönetir.</p>

<h2>
  <a name="full-inertia-wiring" href="#full-inertia-wiring"></a> Tam Inertia Bağlantısı
</h2>

<h3>
  <a name="blade-template" href="#blade-template"></a> Blade Şablonu
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight html">

inertia>{{ config('app.name') }}
@vite(['resources/js/app.js', 'resources/css/app.css'])
@inertiaHead
@routes
@inertia

<p><code>@routes</code> Ziggy rotalarını yazdırır. <code>@inertiaHead</code> Vue'dan dinamik başlık yönetimini etkinleştirir. <code>@inertia</code> SPA'yı monte eder.</p>

<h3>
  <a name="appjs-entry-point" href="#appjs-entry-point"></a> app.js Giriş Noktası
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


createInertiaApp({
title: (title) => ${title} - ${appName},

resolve: async (name) => {
    const pages = import.meta.glob('./pages/ /*.vue');
    let module = await pages[`./pages/${name}.vue`]();
    let page = module.default;
    page.layout = page.layout || LayoutSwitcher;
    return page;
},

setup({ el, App, props, plugin }) {
    const i18n = createI18n({
        legacy: false,
        locale: pageProps.locale || 'en',
        messages: { [locale]: translations },
    });

    createApp({
        render: () => h(App, props)
    })
        .use(plugin)
        .use(i18n)
        .use(ZiggyVue)
        .component('Head', Head)
        .component('Link', Link)
        .mount(el);

    // Küresel çeviri fonksiyonu
    app.config.globalProperties.t = (...args) => i18n.global.t(...args);
    globalThis.t = (...args) => i18n.global.t(...args);
},

progress: {
    delay: 0,
    color: '#3b82f6',
    showSpinner: false,
},

});

<p>Ana kararlar:</p>

<ul>
    <li><strong><code>import.meta.glob</code></strong> - Vite her sayfayı kod parçalarına ayırır. Sadece mevcut sayfa yüklenir.</li>
    <li><strong>Küresel <code>t()</code> fonksiyonu</strong> - hem <code>globalProperties</code> (Options API) hem de <code>globalThis</code> (Composition API) üzerinde kaydedilir. Herhangi bir yerde içe aktarma gerekir.</li>
    <li><strong>İlerleme çubuğu gecikmesi: 0</strong> - navigasyonda hemen görünür. Kullanıcılar anlık geri bildirim görür.</li>
</ul>

<h3>
  <a name="shared-props-middleware" href="#shared-props-middleware"></a> Paylaşılan Özellikler (Middleware)
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight php">


public function share(Request $request): array {
return [
'layout' => fn() => $layout->getLayout(), // lazy
'flash' => fn() => session()->get('messages'), // lazy
'ziggy' => [...(new Ziggy)->toArray()],
'locale' => fn() => App::getLocale(),
'translations' => fn() => $mergedTranslations, // lazy
'visitor_status' => visitor()->status(),
'ui_settings' => fn() => user()->ui_preferences, // lazy
];
}

<p>Tüm detaylar lazy değerlendirme için kapanışlarla sarılmıştır. Düzen verileri yalnızca frontend <code>page.props.layout</code>'ı okuduğunda hesaplanır.</p>

<h3>
  <a name="vite-aliases" href="#vite-aliases"></a> Vite Aliyeleri
</h3>

<div class="highlight js-code-highlight">
    <pre class="highlight javascript">


resolve: {
alias: {
'@': path.resolve(dirname, './resources/js'),
'@css': path.resolve(
dirname, './resources/css'),
'@assets': path.resolve(dirname, './public/assets'),
'ziggy-js': resolve(
dirname, 'vendor/tightenco/ziggy'),
},
},

<p><code>@/components/PagePaginator.vue</code> yerine ilişkisel yol zincirleri kullanılır.</p>

<h2>
  <a name="testing" href="#testing"></a> Test
</h2>

<p>Ön uç davranışı, Inertia özelliklerini doğrulayan Pest özellik testleri aracılığıyla test edilir:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php">


it('serves auth layout for authenticated users', function () {
actingAs(User::factory()->create())
->get('/dashboard')
->assertInertia(fn($page) =>
$page->where('visitor_status', 'auth')
);
});

it('returns pagination data with filter state', function () {
Product::factory(30)->create(['company_id' => $company->id]);

actingAs($user)
    ->get('/warehouse/products?page=2')
    ->assertInertia(fn($page) =>
        $page->has('pagination', fn($p) =>
            $p->where('current_page', 2)
                ->where('per_page', 20)
                ->where('total', 30)
        )
    );

});

it('shares flash messages via Inertia', function () {
actingAs($user)
->post('/contragents', $validData);

actingAs($user)
    ->get('/contragents/customers')
    ->assertInertia(fn($page) =>
        $page->has('flash.info'));

});

it('returns contragent data for view modal', function () {
$contragent = Contragent::factory()->create();

actingAs($user)
    ->get(route('contragents.view_data', $contragent->uuid))
    ->assertJson(['contragent' => ['name' => $contragent->name]]);

});

<p>Şablon: sunucunun ne gönderdiğini test et. Eğer Inertia özellikleri doğruysa, Vue doğru render eder. Bu, düzen değiştirme, sayfalama, filtreler, flaş mesajlar ve modal verilerini kapsar.</p>

<p>Kompleks etkileşimli bileşenler (filtre izleyicileri, URL manipülasyonu) için birim testleri Vitest ile desteklenmelidir. Ancak çoğu ön uç davranışı için Pest özellik testleri yeterlidir.</p>

<h2>
  <a name="directory-structure" href="#directory-structure"></a> Dizin Yapısı
</h2>

<div class="highlight js-code-highlight">
    <pre class="highlight plaintext">


resources/js/
├── app.js # Giriş noktası, eklentiler, LayoutSwitcher varsayılan
├── layouts/
│ ├── LayoutSwitcher.vue # Dinamik düzen yönlendirici
│ ├── AuthLayout.vue # Doğrulanmış kullanıcılar (7 açılır menü)
│ ├── AdminLayout.vue # Admin paneli
│ ├── GuestLayout.vue # Kamu + giriş/kayıt modalları
│ ├── AuthBlockedLayout.vue # Engellenmiş kullanıcılar
│ ├── AuthDeletedLayout.vue # Silinmiş hesaplar
│ └── app/ # Alt düzen bileşenleri
│ ├── AppHeaderDesktopLayout.vue
│ ├── AppMainContentLayout.vue # İçeriği sarar + otomatik sayfalama
│ ├── AppModalLayout.vue
│ ├── AppFooterLayout.vue
│ ├── AppPulloutMobilemenuLayout.vue
│ ├── AppPulloutMenuRightLayout.vue
│ ├── AppPulloutMenuNotificationsLayout.vue
│ └── ... (7 taneden daha fazla açılır bileşen)
├── pages/ # Inertia sayfaları (otomatik çözümlenmiş)
├── components/ # Yeniden kullanılabilir bileşenler
│ ├── PagePaginator.vue
│ ├── contragents/ViewContragentModal.vue
│ ├── SendTestEmailModal.vue
│ └── ui/ # Form girdileri, filtreler vb.
├── composables/ # Vue composition işlevleri
└── store/
└── unreadCountStore.js # Basit ref-tabanlı store

<h2>
  <a name="whats-included" href="#whats-included"></a> Neler Dahil
</h2>

<div class="table-wrapper-paragraph">
    <table>
        <thead>
            <tr>
                <th>Özellik</th>
                <th>Uygulama</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Düzen değiştirme</td>
                <td>LayoutSwitcher + visitor_status özelliği (5 düzen)</td>
            </tr>
            <tr>
                <td>Overlay sistemi</td>
                <td>7 açılır panel, çift katman istifleme, ESC ile kapatma</td>
            </tr>
            <tr>
                <td>Sayfalama</td>
                <td>HasPagination trait + PagePaginator bileşeni (otomatik render)</td>
            </tr>
            <tr>
                <td>Filtreler</td>
                <td>Otomatik keşif deseni, alfabetik nav, hızlı ön ayarlar</td>
            </tr>
            <tr>
                <td>Modallar</td>
                <td>Özel (useForm, asenkron axios) + SweetAlert2 (onaylar)</td>
            </tr>
            <tr>
                <td>Durum yönetimi</td>
                <td>Reaktif referanslar + provide/inject (Vuex/Pinia yok)</td>
            </tr>
            <tr>
                <td>i18n</td>
                <td>vue-i18n v11 ile Laravel çevirileri, global t()</td>
            </tr>
            <tr>
                <td>Yönlendirme</td>
                <td>Ziggy adlandırılmış rotaları Vue şablonlarında</td>
            </tr>
            <tr>
                <td>Kod parçalama</td>
                <td>Vite import.meta.glob her sayfa için</td>
            </tr>
            <tr>
                <td>Test</td>
                <td>Pest özellik testleri Inertia özelliklerini doğrular</td>
            </tr>
        </tbody>
    </table>
</div>

<h2>
  <a name="key-takeaway" href="#key-takeaway"></a> Ana Gözlem
</h2>

<p>Karmaşıklığı arka uca it. Vue önceden hesaplanan verileri render eder.</p>

<p>Vue bileşenlerinde yetki kontrolleri yok. Auth guard yok. İstemci tarafında menü filtreleme yok. Durum hesaplamaları yok. Sunucu her kullanıcı türünün ihtiyaç duyduğu veriyi taşır. Ön uç bunu render eder.</p>

<p>LayoutSwitcher deseni, overlay sistemi, otomatik sayfalama, filtre otomatik keşfi - hepsi aynı ilkeye uyar. Arka uç gerçek bilgi kaynağıdır. Ön uç, bir renderleme motorudur.</p>

<hr/>

<p><strong>LaraFoundry</strong>, açık kaynak bir Laravel SaaS çerçevesidir ve kamuya açık olarak inşa edilmektedir, bir üretim CRM/ERP'sinden çıkarılmaktadır.</p>

Kaynak: Orijinal Makale

Bu Makaleyi Paylaş