# Content Modules — Overview

## 1. Pengenalan

Terdapat **11 content modules** yang mengurus pelbagai jenis content untuk public site. Setiap module mempunyai struktur yang seragam — CRUD dengan **multi-language support** (translations), kecuali Public Holiday yang flat tanpa translation.

| Module | Content Type | Table (Main) | Table (Translation) | Parent-only? |
|--------|-------------|--------------|---------------------|--------------|
| **contentArticle** | Articles | `content_article` | `content_article_translation` | No (single table with translations) |
| **contentSlider** | Slider Images | `content_slider` | `content_slider_translation` | No (parent + translation tables) |
| **contentFeedback** | Feedback/Ticketing | `content_feedback_main` | `content_feedback_messages` | No (no translations, single language) |
| **contentDownload** | Downloads | `content_downloads` | Self-referencing | Yes (main + child translations) |
| **contentImage** | Images | `content_images` | Self-referencing | Yes (main + child translations) |
| **contentVideo** | Videos | `content_videos` | Self-referencing | Yes (main + child translations) |
| **contentApplication** | Links | `content_applications` | `content_application_translations` | No (parent + translation tables) |
| **contentPhotoGallery** | Photo Galleries | `content_photo_gallery` | `content_photo_gallery_translations` | No (parent + translation tables) |
| **contentPhotoList** | Photo Lists | `content_photo_list` | Self-referencing | Yes (main + child translations) |
| **contentPublicHoliday** | Public Holidays | `content_public_holidays` | - | Flat (no translations) |
| **contentCalendar** | Calendar Events | `content_calendar` | `content_calendar_translations` | No (parent + translation tables) |
| **contentDirectory** | Departments & Staff | `content_directory_departments` + `content_directory_staff` | - | Flat (no translations, 2 related tables) |

---

## 2. Common Architecture

### Translation Patterns

Ada **dua pattern translation** dalam sistem:

**Pattern A — Separate Translation Table:**
- Main table + Translation table (one-to-many)
- Contoh: `content_article` ↔ `content_article_translation`
- Controller delete all translations then recreate on update
- Used by: Article, Application, Slider, Video, Photo Gallery, Calendar

**Pattern B — Self-Referencing:**
- Satu table dengan `parent_id` pointing ke main record
- `download_main = '1'` untuk parent, `download_main = '0'` untuk child
- Contoh: `content_downloads`, `content_images`, `content_videos`
- Tambahan route: `addTranslation`, `deleteTranslation`

### Common Features
- **Multi-language** — Setiap content boleh ada translation dalam BM/EN
- **File/Image Upload** — Guna FilePond, simpan di `storage/app/public/{module}/`
- **Ref-based dropdowns** — Category, Status dari `ref` table
- **WYSIWYG Editor** — Summernote untuk content artikel
- **Sort Order** — Setiap module ada field sorting
- **Publish Schedule** — Start/End date dan time

---

## 3. contentArticle

### Database

**Table:** `content_article` (PK: `article_id`)
| Column | Type | Description |
|--------|------|-------------|
| article_id | bigint (PK) | Primary key |
| article_code | string (nullable) | Unique slug |
| article_date | date (nullable) | Article date |
| article_category | string (nullable) | Category (from ref: ARTICLE_CAT) |
| article_subcategory | string (nullable) | Subcategory (from ref: ARTICLE_SUBCAT) |
| article_status | string (nullable) | Status (from ref: ARTICLE_STATUS) |
| article_url | string (nullable) | External URL |
| article_image | string (nullable) | Featured image path |
| article_sorting | integer | Sort order |
| article_portal_category | string (nullable) | Portal display |
| article_start_date/start_time | string (nullable) | Publish start |
| article_end_date/end_time | string (nullable) | Publish end |
| menu_set | string (nullable) | Menu category assignment |

**Table:** `content_article_translation` (PK: `article_translation_id`)
| Column | Type | Description |
|--------|------|-------------|
| article_translation_parent_id | integer | FK to article_id |
| article_translation_title | text | Translated title |
| article_translation_content | longText | Translated content (HTML) |
| article_translation_main | integer | 1 = main translation |
| article_translation_language | string | Language code |

### Routes

| Method | URI | Permission | Name |
|--------|-----|------------|------|
| GET | `/admin/content-article` | `content-article.view` | `content-article.index` |
| GET | `/admin/content-article/create` | `content-article.create` | `content-article.create` |
| POST | `/admin/content-article` | `content-article.create` | `content-article.store` |
| GET | `/admin/content-article/{id}/edit` | `content-article.update` | `content-article.edit` |
| PUT | `/admin/content-article/{id}` | `content-article.update` | `content-article.update` |
| DELETE | `/admin/content-article/{id}` | `content-article.delete` | `content-article.destroy` |
| POST | `/admin/content-article/reorder` | `content-article.update` | `content-article.reorder` |

### Controller — `ContentArticleController`

- `index()` — List articles with search (code, category, status, translation title), eager load translations
- `create()` — Load refs: ARTICLE_CAT, ARTICLE_SUBCAT, ARTICLE_STATUS, LANGUAGE, PORTAL, menu categories
- `store(ContentArticleRequest)` — Upload image → create article → create translations
- `edit($id)` — Load article with translations + refs
- `update(ContentArticleRequest, $id)` — Handle image replace → update article → delete old translations → recreate
- `destroy($id)` — Delete image, translations, article

### Views

- `index.blade.php` — Table: No, Slug, Title, Category, Status, Sort, Actions
- `create.blade.php` — Translations card (Alpine.js language switcher + Summernote for content) + Article Info card (Portal, Menu Category, Slug auto-generate UUID, Category, Subcategory, Status, Date, Publish Schedule, Sort, Image via FilePond)
- `edit.blade.php` — Same with pre-filled + image preview

### Frontend Display

```php
// HomeController@contentArticle($slug)
$article = ContentArticle::where('article_code', $slug)
    ->where('article_status', 'ACTIVE')
    ->first();
return view('home.module.article', ['article' => $article]);
```

---

## 4. contentSlider

### Database

**Table:** `content_slider` (PK: `slider_id`)
**Table:** `content_slider_translation` (PK: `slider_translation_id`)

Slider fields:
- slider_portal, slider_status, slider_sort, slider_type, slider_dimension, slider_transition
- slider_url_type (external/upload), slider_url, slider_url_upload
- slider_start_date/start_time/end_date/end_time

Translation fields:
- slider_translation_parent_id, slider_translation_title, slider_translation_img, slider_translation_main, slider_translation_language

### Routes

Standard CRUD with `content-slider.*` permission prefix.

### Features
- **Image Type** — Dynamic show/hide dimension & transition based on type
- **URL Type** — Toggle between external URL and file upload
- **Per-language image upload** — Setiap translation boleh ada gambar berbeza

---

## 5. contentDownload

### Database

**Table:** `content_downloads` (PK: `download_id`)

Self-referencing with `download_main` flag:
- `download_main = '1'` — Main record
- `download_main = '0'` — Translation child (`download_parent_id` → parent)

Fields: download_category, download_status, download_date, download_source, download_title, download_img, download_file, download_language, start/end dates/times

### Routes

| Method | URI | Permission | Name |
|--------|-----|------------|------|
| GET | `/admin/content-download` | `content-download.view` | `content-download.index` |
| GET | `/admin/content-download/create` | `content-download.create` | `content-download.create` |
| POST | `/admin/content-download` | `content-download.create` | `content-download.store` |
| GET | `/admin/content-download/{id}/edit` | `content-download.update` | `content-download.edit` |
| PUT | `/admin/content-download/{id}` | `content-download.update` | `content-download.update` |
| DELETE | `/admin/content-download/{id}` | `content-download.delete` | `content-download.destroy` |
| GET | `/admin/content-download/{id}/add-translation` | `content-download.update` | `content-download.add-translation` |
| DELETE | `/admin/content-download/translation/{id}` | `content-download.delete` | `content-download.delete-translation` |

### Features
- File upload (max 20MB) + Image upload (max 5MB)
- Translation children with add/delete per record

---

## 6. contentImage

### Database

**Table:** `content_images` (PK: `image_id`)

Self-referencing same pattern as contentDownload.

Fields: image_cat, image_type, image_status, image_title, image_file, image_url, image_sort, image_main, image_parent_id, image_language

### Routes

Standard CRUD + addTranslation/deleteTranslation, permission prefix `content-image.*`.

---

## 7. contentVideo

### Database

**Table:** `content_videos` (PK: `video_id`)

Self-referencing same pattern.

Fields: video_cat, video_status, video_title, video_url, video_sort, video_file, video_main, video_parent_id, video_language, video_date, video_source, start/end dates/times

### Routes

Standard CRUD + addTranslation/deleteTranslation, permission prefix `content-video.*`.

---

## 8. contentApplication

### Database

**Table:** `content_applications` (PK: `application_id`)
**Table:** `content_application_translations` (PK: `application_translation_id`)

Fields: application_cat, application_status, application_url, application_img, application_sort

Translation: application_translation_title, application_translation_main, application_translation_language

### Routes

Standard CRUD without addTranslation, permission prefix `content-application.*`.

---

## 9. contentPhotoGallery & contentPhotoList

Module untuk photo galleries — gallery mengandungi multiple photos.

**contentPhotoGallery** — Manage galleries (grouping).
**contentPhotoList** — Manage photos within each gallery.

### Routes

Standard CRUD with permission prefixes `content-photo-gallery.*` dan `content-photo-list.*`.

### Features
- Image upload via FilePond
- Multi-language support for gallery descriptions
- Sort order for photos

---

## 10. contentPublicHoliday

### Database

**Table:** `content_public_holidays` (PK: `public_holiday_id`)

| Column | Type | Description |
|--------|------|-------------|
| public_holiday_title | string (nullable) | Holiday title |
| public_holiday_start_date | date (nullable) | Start date |
| public_holiday_end_date | date (nullable) | End date |
| public_holiday_status | string (nullable) | Status (from ref: HOLIDAY_STATUS) |

### Routes

Standard CRUD with `content-public-holiday.*` permission prefix.

### Features
- Flat CRUD without translations
- Filter by status and year
- Status badge: ACTIVE (green), INACTIVE (red), ARCHIVE (gray)

---

## 11. contentCalendar

### Database

**Table:** `content_calendar` (PK: `calendar_id`)

| Column | Type | Description |
|--------|------|-------------|
| calendar_date_start | date (nullable) | Event start date |
| calendar_date_end | date (nullable) | Event end date |
| calendar_time_start | string (nullable) | Start time |
| calendar_time_end | string (nullable) | End time |
| calendar_category | string (nullable) | Category (from ref: CALENDAR_CATEGORY) |
| calendar_display | string (nullable) | Display mode (from ref: CALENDAR_DISPLAY) |
| calendar_status | string (nullable) | Status (from ref: CALENDAR_STATUS) |
| calendar_type | string (nullable) | Type (from ref: CALENDAR_TYPE) |
| calendar_owner_id | string (nullable) | Owner FK → backend_users.id |
| total_participant | integer (nullable) | Participant count |

**Table:** `content_calendar_translations` (PK: `calendar_translation_id`)

| Column | Type | Description |
|--------|------|-------------|
| calendar_translation_parent_id | integer | FK to calendar_id |
| calendar_translation_title | text | Agenda title |
| calendar_translation_content | text | Agenda details |
| calendar_translation_location | text | Event location |
| calendar_translation_main | integer | 1 = main translation |
| calendar_translation_language | string | Language code |

### Routes

Standard CRUD with `content-calendar.*` permission prefix.

### Features
- Multi-language translations via Alpine.js language dropdown
- Event Details card (translations) placed BEFORE Event Information card
- Start Time required
- Owner Select2 dropdown (backend_users) — selectable only on edit; `null` on create
- Index columns: No, Agenda, Category, Type, Start Date, End Date, Status, Actions
- Time displayed below date in index
- Status badge: ACTIVE (green), INACTIVE (red), ARCHIVE (gray)

---

## 12. contentDirectory

Module untuk mengurus **Departments** dan **Staff** dalam struktur hierarki.

### Database

**Table:** `content_directory_departments` (PK: `directory_department_id`)

| Column | Type | Description |
|--------|------|-------------|
| directory_department_id | bigint (PK) | Primary key |
| directory_department_code | string (nullable) | Department code |
| directory_department_name | string (nullable) | Name (BM) |
| directory_department_name_en | string (nullable) | Name (EN) |
| directory_department_parent_id | integer (nullable) | FK → self (parent) |
| directory_department_sort | integer | Sort order |
| created_by / updated_by | integer | User FKs |

**Table:** `content_directory_staff` (PK: `directory_staff_id`)

| Column | Type | Description |
|--------|------|-------------|
| directory_staff_id | bigint (PK) | Primary key |
| directory_staff_department_id | integer | FK → departments |
| directory_staff_name | string | Staff name |
| directory_staff_email | string (nullable) | Email |
| directory_staff_phone | string (nullable) | Phone |
| directory_staff_position | string (nullable) | Position |
| directory_staff_photo | string (nullable) | Photo path (FilePond upload) |
| directory_staff_sort | integer | Sort order |
| directory_staff_status | integer | 1=Active, 0=Inactive |
| created_by / updated_by | integer | User FKs |

### Routes

| Method | URI | Permission | Name |
|--------|-----|------------|------|
| GET | `/admin/content-directory-department` | `content-directory-department.view` | `content-directory-department.index` |
| GET/POST | `/admin/content-directory-department/create` | `content-directory-department.create` | `.create` / `.store` |
| GET/PUT | `/admin/content-directory-department/{id}/edit` | `content-directory-department.update` | `.edit` / `.update` |
| DELETE | `/admin/content-directory-department/{id}` | `content-directory-department.delete` | `.destroy` |
| POST | `/admin/content-directory-department/reorder` | `content-directory-department.update` | `.reorder` |
| *(same pattern for staff, prefix `content-directory-staff`)* | | | |

### Features

- **Hierarchical Departments** — Parent-child tree (up to 2 levels deep), All view shows flattened tree with indentation
- **Per-parent Display Numbering** — Children reset to #1, #2, #3 under each parent in All view
- **Per-department Staff Sorting** — Staff reorder only within selected department filter; pagination-aware page offset
- **Photo Upload** — FilePond, stored at `storage/directory-staff/`, auto-delete on update/replace
- **Drag-and-drop Reorder** — Only enabled when a parent filter is selected (grey icon + info banner when no filter)
- **Root Department Highlight** — `bg-brand-50` background for rows without parent
- **Info Banner** — Blue brand-50 info banner when no filter selected, guiding user to filter first

### Views — Department Index

Columns: #, Code, Name (BM), Name (EN), Parent, Actions
- Filter: Parent dropdown (All / Root / specific parent) + Search
- All view: hierarchical tree with per-parent numbering
- Filtered view: paginated flat list with drag handles

### Views — Staff Index

Columns: #, Photo, Name, Department, Position, Email, Phone, Sort, Status, Actions
- Filter: Department dropdown + Status dropdown + Search
- Drag handles only when department filter selected
- Pagination with page-offset sorting

---

## 13. Common Permissions

Setiap content module ada 4 permissions:

| Permission | Description |
|------------|-------------|
| `{module}.view` | View list |
| `{module}.create` | Create content |
| `{module}.update` | Edit content |
| `{module}.delete` | Delete content |

Module prefixes:
- `content-article`
- `content-slider`
- `content-download`
- `content-image`
- `content-video`
- `content-application`
- `content-photo-gallery`
- `content-photo-list`
- `content-public-holiday`
- `content-calendar`
- `content-directory-department`
- `content-directory-staff`
