# Validation & Error UI Guide

Dokumen ini menjadi rujukan standard untuk **server-side validation** dan **UI error state** dalam admin CMS.

> Prinsip utama: validation dilakukan di server. Jangan bergantung pada HTML `required`. Border merah dan mesej error hanya dipaparkan selepas validation gagal.

---

## 1. Peraturan Umum

| Perkara | Standard |
|---------|----------|
| Server validation | Guna `FormRequest` atau `$request->validate()` |
| HTML `required` | Tidak digunakan untuk field yang divalidasi |
| Label required | Tambah `<span class="text-error-500">*</span>` |
| Border error | `border-error-300` |
| Focus error | `focus:border-error-300 focus:ring-error-500/10` |
| Dark mode error | `dark:border-error-700` |
| Border normal | `border-gray-300 dark:border-gray-700` |
| Mesej error | `<p class="text-theme-xs text-error-500 mt-1.5">{{ $message }}</p>` |

### Elakkan override CSS

Jangan letak `border-gray-300` dalam class asas yang sama dengan `border-error-300`. Jika kedua-dua class ada, border default boleh mengalahkan border error mengikut susunan CSS.

Guna salah satu pattern berikut.

**Pattern A — conditional penuh:**

```blade
class="... border ... @error('name')
    border-error-300 focus:border-error-300 focus:ring-error-500/10 dark:border-error-700
@else
    border-gray-300 dark:border-gray-700
@enderror"
```

**Pattern B — class berasingan:**

```php
$inputBaseClass = '... border ...';
$inputClass = $inputBaseClass . ' border-gray-300 dark:border-gray-700';
$inputErrorClass = $inputBaseClass . ' border-error-300 focus:border-error-300 focus:ring-error-500/10 dark:border-error-700';
```

```blade
class="{{ $errors->has('name') ? $inputErrorClass : $inputClass }}"
```

---

## 2. Required Fields by Module

### Content Modules

| Module | Required | Optional / Nota |
|--------|----------|-----------------|
| `contentArticle` | Main-language title, Category, Status | Sort optional (`nullable|integer|min:0`); bahasa kedua boleh kosong |
| `contentApplication` | Main-language name/title, Category, Status | Sort optional |
| `contentDownload` | Title, Category, Status | Sort optional; start/end dates optional |
| `contentImage` | Title, Category, Status | Sort optional (`min:0`); Language default kepada bahasa pertama |
| `contentVideo` | Main-language title, Category, Status | Sort optional |
| `contentSlider` | Main-language title, Status | Sort optional |
| `contentPhotoGallery` | Main-language title, Category, Status | Sort optional; tiada input Sort dalam create/edit |
| `contentPhotoList` | Tiada perubahan required | `photo_gallery_id`, `photo_url`, `photo_sort` kekal optional |
| `contentPublicHoliday` | Title, Start Date, Status | End Date optional |
| `contentCalendar` | Main-language title, Category, Status | Start Date, Start Time, End Date, End Time optional |
| `contentDirectoryDepartment` | Code, Name (BM), Name (EN) | Sort optional, default `0` |
| `contentDirectoryStaff` | Name, Status (`0`/`1`) | Sort optional, default `0` |

### Translation

| Module | Required | Nota |
|--------|----------|------|
| `appTranslation` (create) | Key, Language, Value | Value guna textarea `rows="6"` |

### Frontend Site

| Module | Required | Optional / Nota |
|--------|----------|-----------------|
| `frontendSite/site` | Name, Slug | Sort optional (`min:0`) |
| `frontendSite/component` | Name, Code | Content optional |
| `frontend-menu` | Menu Name | Parent, link, icon, class, status optional |
| `menu-category` | Category Name | Description dan Status optional |

---

## 3. Sort Fields

Semua Sort menggunakan rule:

```php
'..._sort' => 'nullable|integer|min:0'
```

Maksudnya:

| Nilai | Keputusan |
|-------|-----------|
| Kosong | Valid — tiada border error |
| `0`, `1`, `10` | Valid |
| `-1` | Gagal `min:0` |
| `abc` | Gagal `integer` |
| `1.5` | Gagal `integer` |

Border error Sort hanya muncul apabila validation Sort gagal, bukan apabila field lain gagal.

Module yang mempunyai input Sort dan error border:

- `contentApplication`
- `contentArticle`
- `contentDownload`
- `contentImage`
- `contentPhotoList`
- `contentSlider`
- `contentVideo`
- `contentDirectoryDepartment`
- `contentDirectoryStaff`
- `frontendSite/site`
- `dataDashboard`
- `ref`

---

## 4. Frontend Component — Content Optional

`content` dalam `frontendSite/component` ialah **optional**:

```php
'content' => 'nullable|string',
```

Ini membolehkan komponen lama yang `content = null` atau kosong dikemas kini tanpa perlu mengisi editor.

- Label: `Content` (tiada `*`).
- Editor trigger tidak menggunakan border error khas.
- `Name` dan `Code` kekal required.

---

## 5. Calendar — Tarikh & Masa Optional

`ContentCalendarRequest` menggunakan:

```php
'calendar_date_start' => 'nullable|date',
'calendar_time_start' => 'nullable|string|max:25',
'calendar_date_end' => [
    'nullable',
    'date',
    Rule::when($this->filled('calendar_date_start'), ['after_or_equal:calendar_date_start']),
],
```

`after_or_equal` hanya aktif jika `calendar_date_start` diisi.

---

## 6. Upload Limits

### Images

Semua image upload menggunakan:

```php
'nullable|image|mimes:jpg,jpeg,png,webp|max:15360' // 15MB
```

- `15360` KB = **15MB**
- Format: JPG, JPEG, PNG, WebP
- Theme logo/favicon juga menggunakan 15MB, dengan format tambahan SVG/ICO mengikut field.
- Cadangan dimensi dipaparkan di bawah setiap FilePond.

### Video

```php
'nullable|file|mimes:mp4,avi,mov,wmv,flv,mkv|max:307200' // 300MB
```

- `307200` KB = **300MB**
- Format: MP4, AVI, MOV, WMV, FLV, MKV

### Dokumen

- `download_file`: max **150MB** — PDF, DOC/DOCX, PPT/PPTX, XLS/XLSX
- `slider_url_upload`: max **150MB** — PDF, DOC/DOCX, PPT/PPTX, XLS/XLSX

---

## 7. Test Coverage

Test berkaitan validation/error UI:

- `AppTranslationValidationTest`
- `ContentApplicationValidationTest`
- `ContentArticleTitleTest`
- `ContentCalendarValidationTest`
- `ContentDirectoryDepartmentValidationTest`
- `ContentDirectoryStaffValidationTest`
- `ContentDownloadValidationTest`
- `ContentImageValidationTest`
- `ContentPhotoCRUDTest`
- `ContentPublicHolidayValidationTest`
- `ContentSliderValidationTest`
- `ContentVideoValidationTest`
- `FrontendMenuValidationTest`
- `FrontendSiteValidationTest`
- `MenuCategoryValidationTest`

Jalankan:

```bash
php artisan test
php artisan view:cache
```
