# Planning — Modul Yang Belum Dibuat

> Dikemas kini: 22 Okt 2026 · Status: cadangan (belum mula)

## 1. Status semasa

**Admin CRUD — SIAP (14 modul):**

| Modul | Table |
|---|---|
content-article | `content_article` + `content_article_translation` |
content-application | `content_applications` + `_translations` |
content-download | `content_downloads` |
content-image | `content_images` |
content-video | `content_video` + `_translation` |
content-slider | `content_slider` + `_translation` |
content-photo-gallery / photo-list | `content_photo_gallery(+_translations)`, `content_photo_list` |
content-public-holiday | `content_public_holidays` |
content-calendar | `content_calendar` + `_translation` |
content-feedback | `content_feedback_main` + `_messages` |
content-directory (department/staff) | `content_directory_departments`, `content_directory_staff` |
**content-tender** | `content_tender` + `_translation` + `_document` |

**Frontend (`storage/app/private/PHP/*.blade.php`) — ada 18 komponen:** navbar, footer, home, slider, ticker, article, news, faq, download, publication, gallery, video, holiday, calendar, directory, contact_us, sitemap, modules_showcase, accessibility-toolbar.

**Belum ada:** `tender_web` (komponen + page + menu) → **Fasa T3**.

---

## 2. Fasa T3 — Tender & Sebut Harga Web (prioriti 1)

### 2.1 Seed / data

| Table | Nilai |
|---|---|
`frontend_components` | `name = 'Tender Web'`, `code = 'tender_web'`, `type/category = 'module'`, `status = 1` |
`frontend_pages` | `site_fk = web`, `slug = 'tender-web'`, `name = 'Tender Web'`, `layout = default`, `status = 1`, `sort_order = 21` |
`frontend_page_components` | page `tender-web` → component `tender_web` (sort 1) |
`frontend_menu` | Menu "Tender" (BM) / "Tender" (EN) → `/web/tender-web` |

### 2.2 Fail

| Fail | Kandungan |
|---|---|
`storage/app/private/PHP/tender_web.blade.php` | Komponen senarai + butiran (satu fail, pattern sama `article_web`) |
`storage/app/private/PAGE_CONTENT/web/tender-web.blade.php` | `{!! $tender_web !!}` |

### 2.3 Logik

```php
$code = request()->query('id');                       // butiran: ?id=NRES/T/2026/001
ContentTender::with(['translations','documents'])
    ->where('tender_status','PUBLISHED')
    ->when($q, …)                                     // carian kod/tajuk
    ->when($type, …)->when($category, …)
    ->when($state === 'open', fn ($x) => $x->where(...))   // guna logik display
    ->orderByDesc('tender_close_date')->paginate(10);
```

### 2.4 Field yang dipaparkan

**Senarai (kad/baris):** kod rujukan · tajuk (main translation) · badge **Open/Closed** (`display_status`) · jenis + kategori (badge) · tarikh buka · tarikh tutup · bilangan dokumen · butang **Lihat Butiran**.

**Butiran (`?id=`):**
- Header: kod, tajuk, badge Open/Closed, jenis, kategori, kaedah.
- `description` · `eligibility` · `scope_of_work` (ikut bahasa aktif `$lang`).
- Tarikh: buka (tarikh+masa), tutup (tarikh+masa), taklimat (tarikh/masa/tempat), lawatan tapak.
- Dokumen: **Tender Document (main)**, lampiran (`documents`), **Result/Award** + nota keputusan.
- Hubungi: pegawai, telefon, emel, pautan ePerolehan.
- Butang **Muat Turun** (`url('storage/'.$file)`, `target=_blank`).

### 2.5 Acceptance

- [ ] Menu portal → senarai tender, badge Open/Closed betul ikut tarikh.
- [ ] Filter jenis/kategori + carian + pagination berfungsi.
- [ ] Butiran papar semua dokumen & pautan boleh dimuat turun (200).
- [ ] Tender `DRAFT`/`CANCELLED` **tidak** muncul.
- [ ] Test `tests/Feature/TenderWebTest.php`: senarai render, butiran render, draft tersembunyi.

## 3. Cadangan modul seterusnya (dengan field)

### 3.1 FAQ — admin module khas (S)

Sekarang FAQ guna `content_article` (`article_category = 'ARTICLE'`) — hack, susah nak susun soalan/jawapan berpasangan.

**Table `content_faq`** (PK `faq_id`):

| Kolum | Jenis | Nota |
|---|---|---|
`faq_id` | bigint PK | |
`faq_code` | varchar(50) null | rujukan dalaman |
`faq_category` | varchar(50) | `ref` cat `FAQ_CATEGORY` |
`faq_sort` | int default 0 | susunan |
`faq_status` | varchar(20) default `PUBLISHED` | `DRAFT`/`PUBLISHED` |
`created_by`, `updated_by` | varchar(50) | ikut konvensyen sedia ada |

**Table `content_faq_translation`** (FK `faq_id` cascade): `language`, `question`, `answer` (longtext), `main` (bool).

**Ref baru:** `FAQ_CATEGORY` (Umum, Perkhidmatan, Perolehan, Aduan).
**Frontend:** kemas kini `faq_web.blade.php` → baca `ContentFaq` (kekal UI sedia ada, tambah tapisan kategori).

---

### 3.2 Ticker / Pengumuman (S)

Sekarang ticker ambil 5 artikel `NEWS` terkini — tak boleh pilih manual.

**Pilihan A (murah, disyorkan):** tambah kolum pada `content_article`:
`article_ticker` (bool, default 0), `article_ticker_expiry` (datetime null), `article_ticker_sort` (int default 0).
**Pilihan B (modul penuh):** `content_ticker` — `ticker_id`, `ticker_text`, `ticker_url`, `ticker_start_date`, `ticker_end_date`, `ticker_status`, `ticker_sort`.

Kemas kini `ticker_web.blade.php` → tapis `article_ticker = 1` + belum luput.

---

### 3.3 Tetapan Hubungi & Media Sosial (S)

Sekarang `contact_us_web.blade.php` hardcode. Guna table `settings` sedia ada (kumpulan `contact`):

`contact_address_bm`, `contact_address_en`, `contact_phone`, `contact_fax`, `contact_email`, `contact_hours`, `contact_map_lat`, `contact_map_lng`, `contact_facebook`, `contact_x`, `contact_instagram`, `contact_youtube`, `contact_tiktok`.

**UI:** modul **Tetapan → Hubungi Kami** (borang mudah, tiada translation table — BM/EN dalam satu baris).

---

### 3.4 Jawatan Kosong / Career (M)

Pola sama seperti Tender (parent + translation + dokumen).

**`content_career`** (PK `career_id`): `career_code`, `career_department_fk` (→ `content_directory_departments`), `career_grade`, `career_location`, `career_type` (`ref` `CAREER_TYPE`: Tetap/Kontrak/Sambilan), `career_open_date`, `career_close_date`, `career_status` (`DRAFT`/`PUBLISHED`/`CLOSED`), `career_advert_file`, `career_url`, `career_sort`, `created_by`, `updated_by`.

**`content_career_translation`**: `language`, `position`, `description`, `requirements`, `main`.

**Frontend:** page `career-web` + komponen `career_web` (senarai + butiran `?id=`, badge Buka/Tutup).

---

### 3.5 Statistik / Data Terbuka (L)

**`content_dataset`**: `dataset_id`, `dataset_code`, `dataset_category` (`ref` `DATASET_CATEGORY`), `dataset_year`, `dataset_status`, `dataset_sort`.
**`content_dataset_translation`**: `language`, `title`, `description`, `main`.
**`content_dataset_resource`**: `resource_id`, `dataset_id` (FK), `resource_label`, `resource_type` (`CSV`/`XLSX`/`PDF`/`API`), `resource_file`, `resource_url`, `resource_size`, `resource_updated_at`.

**Frontend:** page `data-web` + komponen `data_web` (senarai dataset + butiran + muat turun).

---

### 3.6 e-Participation / Survey (L)

`content_survey`: `survey_id`, `survey_title`, `survey_desc`, `survey_start`, `survey_end`, `survey_status`.
`content_survey_question`: `question_id`, `survey_id`, `question_text`, `question_type` (`RADIO`/`CHECKBOX`/`TEXT`/`SCALE`), `question_sort`, `question_required`.
`content_survey_option`: `option_id`, `question_id`, `option_text`, `option_sort`.
`content_survey_response`: `response_id`, `survey_id`, `frontend_user_fk` null, `ip_hash`, `submitted_at`.
`content_survey_answer`: `answer_id`, `response_id`, `question_id`, `option_id` null, `answer_text` null.

**Admin:** senarai survey + builder soalan + laporan agregat (peratus).

---

## 4. Modul kecil lain (cadangan)

### 4.1 Newsletter subscriber (S)
`frontend_newsletter`: `id`, `email` (unique), `status` (`PENDING`/`ACTIVE`/`UNSUBSCRIBED`), `token`, `verified_at`, `ip_hash`, `created_at`.

### 4.2 Popup / Pengumuman masuk (S)
`content_popup`: `popup_id`, `popup_title`, `popup_body`, `popup_image`, `popup_link`, `popup_start`, `popup_end`, `popup_frequency` (`ALWAYS`/`SESSION`/`DAY`), `popup_status`.

### 4.3 Carian portal (M)
`search_index`: `id`, `module` (`tender`/`article`/`download`/`career`/…), `record_id`, `language`, `title`, `summary`, `url`, `updated_at`.
Artisan command `search:reindex` (+ hook selepas save/delete). `GlobalSearchController` sedia ada boleh dikembangkan untuk portal awam.

---

## 5. Prioriti & anggaran

| # | Modul | Saiz | Sebab |
|---|---|---|---|
1 | **Tender Web (T3)** | M | Data tender dah siap, portal awam belum papar |
2 | **Tetapan Hubungi & Sosial** | S | Kandungan hardcode dalam blade, susah dikemas kini |
3 | **FAQ** | S | Guna artikel (hack) — susah maintain |
4 | **Ticker** | S | Pengumuman tak boleh dikawal dari admin |
5 | **Jawatan Kosong** | M | Keperluan biasa portal agensi, pola sama Tender |
6 | **Data Terbuka / Statistik** | L | Nilai tambah, perlu UI muat turun dataset |
7 | **Survey / e-Participation** | L | Paling kompleks (builder + laporan) |
8 | Newsletter · Popup · Carian | S–M | Sokongan, boleh dibuat bila perlu |

---

## 6. Konvensyen wajib ikut (sedia ada dalam projek)

- **Nama kolum** berprefix nama modul (`tender_*`, `career_*`, `faq_*`).
- **Terjemahan** guna table `*_translation` + kolum `main` (konsep sama `content-tender`).
- **Dropdown** dari `ref` (`*_CATEGORY`, `*_STATUS`), label `descr_en` fallback `descr`, `orderBy('sort')`.
- **Fail**: `storage/app/public/{module}/{files|results|attachments}`, muat naik FilePond (`storeAsFile: true`), pautan `url('storage/…')`.
- **Permission**: `{module}.view|create|update|delete` (super-admin), baris `backend_menu` + `backend_role_mapping`.
- **UX**: label `*` untuk wajib, border `error-300`, delete guna **SweetAlert2**, selepas create/update redirect ke **index**, butang **Update** pada halaman edit.
- **Docs & ujian**: `docs/module/{module}.md` + `tests/Feature/{Module}Test.php`.
