| Current Path : /var/www/html/dev/reddsis-dev/docs/ |
| Current File : /var/www/html/dev/reddsis-dev/docs/DYNAMIC-PAGES.md |
# REDD+SIS Dynamic Pages Documentation
## Overview
Dokumen ini menerangkan bagaimana semua page di REDD+SIS frontend (`resources/views/reddsis/`) ditukar dari **static content** kepada **dynamic content** yang boleh diurus melalui admin panel.
---
## 1. Sebelum (Static)
Setiap page adalah file `.blade.php` yang mengandungi HTML hardcode:
```
resources/views/reddsis/pages/
├── home.blade.php ← Content hardcode dalam HTML
├── what-is-redd-plus.blade.php ← Content hardcode dalam HTML
├── cancun-safeguards.blade.php ← Content hardcode dalam HTML
└── ... (13 files)
```
Untuk tukar content, kena edit file code secara manual.
---
## 2. Selepas (Dynamic)
Semua content disimpan dalam **database** (table `content_article` + `content_article_translation`). Admin boleh create/edit/delete content melalui admin panel tanpa sentuh code.
```
resources/views/reddsis/pages/
├── home.blade.php ← Fetch dari DB, ada fallback static
├── dynamic.blade.php ← Template universal untuk semua pages
└── login.blade.php ← Tetap static (form login)
```
---
## 3. Bagaimana Ia Berfungsi (Flow)
### 3.1 Request Flow
```
User buka URL: /what-is-redd-plus
│
▼
routes/web.php → Route::get('/what-is-redd-plus', [HomeController::class, 'whatIsReddPlus'])
│
▼
HomeController::whatIsReddPlus() → calls $this->page('what-is-redd-plus')
│
▼
HomeController::page($slug)
│
├── Query DB: ContentArticle::where('article_code', $slug)
│ ->where('article_status', 'Published')
│ ->with('translations')
│ ->first()
│
├── JIKA ADA DATA → return view('reddsis.pages.dynamic', compact('article'))
│
└── JIKA TIADA DATA → abort(404)
```
### 3.2 Database Structure
```
content_article content_article_translation
┌──────────────────────────┐ ┌──────────────────────────────────┐
│ article_id (PK) │◄─────┤ article_translation_parent_id │
│ article_code │ │ article_translation_title │
│ article_category │ │ article_translation_content │
│ article_status │ │ article_translation_main │
│ article_sorting │ │ article_translation_language │
│ article_image │ │ created_by, updated_by │
│ created_by, updated_by │ │ created_at, updated_at │
│ created_at, updated_at │ └──────────────────────────────────┘
└──────────────────────────┘
```
- **`content_article`** → Data utama (code, status, sorting, dll)
- **`content_article_translation`** → Title & content untuk setiap bahasa
- **`article_translation_main = 1`** → Bahasa utama yang akan display dulu
---
## 4. Files Yang Diubah / Dicipta
### 4.1 `app/Http/Controllers/HomeController.php`
**Apa yang dibuat:**
- Setiap method (`whatIsReddPlus()`, `cancunSafeguards()`, dll) panggil `$this->page('slug')`
- Method `page($slug)` → query database, return `dynamic.blade.php`
- Method `index()` → query `article_code = 'home'`, return `home.blade.php`
```php
public function page($slug)
{
$article = ContentArticle::with('translations')
->where('article_code', $slug)
->where('article_status', 'Published')
->first();
if (!$article) {
abort(404);
}
return view('reddsis.pages.dynamic', compact('article'));
}
```
### 4.2 `resources/views/reddsis/pages/dynamic.blade.php` (NEW)
**Apa yang dibuat:**
- Template universal untuk SEMUA pages
- Ambil title dari translation yang `article_translation_main = 1`
- Render content menggunakan `{!! !!}` (unescaped HTML untuk WYSIWYG content)
```blade
@extends('reddsis.layouts.app')
@section('title', $article->translations->firstWhere('article_translation_main', 1)->article_translation_title)
@section('content')
<div class="container">
<div class="breadcrumb">
<a href="{{ route('reddsis.home') }}">Home</a>
<span>›</span>
{{ $article->translations->firstWhere('article_translation_main', 1)->article_translation_title }}
</div>
<h2 class="section-title" style="text-align:left;">
{{ $article->translations->firstWhere('article_translation_main', 1)->article_translation_title }}
</h2>
<div class="content-section">
{!! $article->translations->firstWhere('article_translation_main', 1)->article_translation_content !!}
</div>
</div>
@endsection
```
### 4.3 `resources/views/reddsis/pages/home.blade.php`
**Apa yang dibuat:**
- Support **dual mode**:
- **Ada data DB** → render content dari database
- **Tiada data (`null`)** → fallback ke static HTML sedia ada
```blade
@if($home && $home->translations->isNotEmpty())
<div class="content-section">
{!! $home->translations->firstWhere('article_translation_main', 1)->article_translation_content !!}
</div>
@else
<!-- Fallback: static HTML content -->
<div class="two-col">
<div><img src="{{ asset('reddsis/img/forest-main.jpg') }}" alt="Malaysia Forest"></div>
<div><p>Malaysia became a Non-Annex I Party...</p></div>
</div>
@endif
```
### 4.4 `routes/web.php`
**Apa yang dibuat:**
- Setiap page ada route sendiri yang map ke method dalam `HomeController`
```php
Route::prefix('/')->group(function () {
Route::get('/', [HomeController::class, 'index'])->name('reddsis.home');
Route::get('/what-is-redd-plus', [HomeController::class, 'whatIsReddPlus'])->name('reddsis.whatIsReddPlus');
Route::get('/benefits-redd-plus', [HomeController::class, 'benefitsReddPlus'])->name('reddsis.benefitsReddPlus');
Route::get('/redd-plus-malaysia', [HomeController::class, 'reddPlusMalaysia'])->name('reddsis.reddPlusMalaysia');
Route::get('/cancun-safeguards', [HomeController::class, 'cancunSafeguards'])->name('reddsis.cancunSafeguards');
Route::get('/malaysia-interpretation', [HomeController::class, 'malaysiaInterpretation'])->name('reddsis.malaysiaInterpretation');
Route::get('/what-is-sis', [HomeController::class, 'whatIsSis'])->name('reddsis.whatIsSis');
Route::get('/about-my-sis', [HomeController::class, 'aboutMySis'])->name('reddsis.aboutMySis');
Route::get('/pci-status', [HomeController::class, 'pciStatus'])->name('reddsis.pciStatus');
Route::get('/soi', [HomeController::class, 'soi'])->name('reddsis.soi');
Route::get('/publications', [HomeController::class, 'publications'])->name('reddsis.publications');
Route::get('/contact', [HomeController::class, 'contact'])->name('reddsis.contact');
Route::get('/login', [HomeController::class, 'login'])->name('reddsis.login');
});
```
### 4.5 `app/Providers/AppServiceProvider.php`
**Apa yang dibuat:**
- Share visitor stats (`$visits`) secara global ke semua views
- Footer counter display data real-time dari table `visits`
```php
View::share('visits', [
'total' => Visit::totalVisits(),
'today' => Visit::todayVisits(),
'unique' => Visit::uniqueIps(),
]);
```
### 4.6 `resources/views/reddsis/layouts/app.blade.php`
**Apa yang dibuat:**
- Semua nav links guna `{{ route('reddsis.*') }}`
- Active state check guna `request()->routeIs('reddsis.*')`
- Top bar: conditional link (Dashboard jika dah login, Login jika belum)
- Footer visitor counter guna `$visits` dari AppServiceProvider
---
## 5. Mapping Article Code ke Pages
| URL | Route Name | `article_code` |
|---|---|---|
| `/` | `reddsis.home` | `home` |
| `/what-is-redd-plus` | `reddsis.whatIsReddPlus` | `what-is-redd-plus` |
| `/benefits-redd-plus` | `reddsis.benefitsReddPlus` | `benefits-redd-plus` |
| `/redd-plus-malaysia` | `reddsis.reddPlusMalaysia` | `redd-plus-malaysia` |
| `/cancun-safeguards` | `reddsis.cancunSafeguards` | `cancun-safeguards` |
| `/malaysia-interpretation` | `reddsis.malaysiaInterpretation` | `malaysia-interpretation` |
| `/what-is-sis` | `reddsis.whatIsSis` | `what-is-sis` |
| `/about-my-sis` | `reddsis.aboutMySis` | `about-my-sis` |
| `/pci-status` | `reddsis.pciStatus` | `pci-status` |
| `/soi` | `reddsis.soi` | `soi` |
| `/publications` | `reddsis.publications` | `publications` |
| `/contact` | `reddsis.contact` | `contact` |
---
## 6. Cara Guna (Admin Panel)
### 6.1 Create New Page Content
1. Login admin → pergi **Content Article**
2. Klik **Create**
3. Isi maklumat:
- **Article Code**: `what-is-redd-plus` (MESTI match dengan route slug)
- **Article Category**: `REDD_PLUS_PAGE`
- **Article Status**: `Published`
- **Article Sorting**: `1`
4. Isi **Translation**:
- **Language**: `en` (atau `BM` untuk Bahasa Melayu)
- **Title**: `What is REDD Plus?`
- **Content**: Isi content guna WYSIWYG editor
- **Main**: ✅ Check/tick untuk bahasa utama
5. Klik **Save**
### 6.2 Edit Existing Content
1. Pergi **Content Article**
2. Cari article yang nak edit
3. Klik **Edit**
4. Update title/content
5. Klik **Update**
### 6.3 Unpublish Page
1. Edit article
2. Tukar **Article Status** dari `Published` ke `Draft`
3. Page akan return **404** di frontend
---
## 7. Kenapa Ia Jadi Dynamic?
### Sebelum (Static):
```
Page = File .blade.php → HTML hardcode → Edit code untuk tukar content
```
### Selepas (Dynamic):
```
Page = Route → Controller → Query DB → Render dari content_article
```
**Key concept:**
- Controller **tidak** return static view (`return view('page-name')`)
- Controller **query database** untuk cari content yang match dengan `article_code`
- View (`dynamic.blade.php`) **render content dari variable** yang passed dari controller
- Admin panel **Content Article** → CRUD untuk table `content_article` + `content_article_translation`
---
## 8. Visitor Counter
Visitor counter di footer display data real-time dari table `visits`.
### Bagaimana Ia Diimplementasikan
**Step 1: `app/Providers/AppServiceProvider.php`**
- Share visitor stats (`$visits`) secara global ke semua views menggunakan `View::share()`
- Data auto-fetch setiap kali app boot, jadi tak perlu pass dari controller
```php
use App\Models\Backend\Visit;
use Illuminate\Support\Facades\View;
public function boot(): void
{
Activity::observe(ActivityObserver::class);
View::share('visits', [
'total' => Visit::totalVisits(),
'today' => Visit::todayVisits(),
'unique' => Visit::uniqueIps(),
]);
}
```
**Step 2: `app/Models/Backend/Visit.php`**
- Model `Visit` ada static methods untuk kira stats:
- `Visit::totalVisits()` → `count()` semua rows dalam table `visits`
- `Visit::todayVisits()` → `whereDate('created_at', today())->count()`
- `Visit::uniqueIps()` → `distinct('ip')->count('ip')`
**Step 3: `resources/views/reddsis/layouts/app.blade.php`**
- Footer counter display data dari variable `$visits` yang dah di-share global
```blade
<div class="visitor-counter">
<span>Visits {{ $visits['unique'] ?? 0 }}</span>
<span>Today {{ $visits['today'] ?? 0 }}</span>
<span>Total {{ $visits['total'] ?? 0 }}</span>
</div>
```
### Kenapa Guna `View::share()`?
- **Tak perlu pass dari setiap controller** → data auto-available di semua views
- **Consistent** → satu source of truth untuk visitor stats
- **Clean code** → controller jadi lebih simple, tak perlu load stats setiap method
### Cara Visitor Data Dicatat
Setiap kali user buka page, module visitor logging (jika aktif) akan auto-record:
- IP address, browser, platform, device
- URL, referer, user agent
- Timestamp (`created_at`)
Data ni disimpan dalam table `visits` dan digunakan untuk kira stats di atas.
---
## 9. Troubleshooting
### Content tak keluar di frontend?
1. Check `article_status` = `Published` (bukan `Draft`)
2. Check `article_code` match dengan route slug
3. Check ada translation dengan `article_translation_main = 1`
4. Clear cache: `php artisan view:clear`
### Nak debug data dari DB?
Tambah `@dump($article)` atau `@dump($home)` di view untuk inspect data.
### Route tidak ditemui?
Check `routes/web.php` → pastikan route untuk page tu ada dan map ke method yang betul.