223 lines
8.3 KiB
Markdown
223 lines
8.3 KiB
Markdown
# Panduan Gaya Kode
|
|
|
|
Dokumen ini mencatat konvensi penulisan kode di proyek ini, supaya kontributor
|
|
(termasuk AI assistant) mengikuti pola yang sama dan tidak menulis ulang
|
|
solusi yang sudah ada dengan cara berbeda-beda di tiap file.
|
|
|
|
Setiap bagian punya status:
|
|
|
|
- ✅ **Selesai** — sudah diterapkan di seluruh kode yang relevan.
|
|
- 🚧 **Sebagian** — sudah ada polanya, tapi belum diterapkan di semua tempat.
|
|
- 📝 **Rencana** — baru berupa kesepakatan, belum diterapkan.
|
|
|
|
---
|
|
|
|
## 1. Enum ✅
|
|
|
|
**Aturan:** setiap backed enum (`enum X: string`) wajib pakai trait
|
|
`App\Enums\Concerns\HasValues`. Jangan pernah menulis
|
|
`array_values(X::cases())` atau `array_column(X::cases(), 'value')` secara
|
|
manual — pakai `X::values()` (atau `X::options()` kalau butuh label untuk
|
|
dropdown/select).
|
|
|
|
```php
|
|
// app/Enums/Concerns/HasValues.php
|
|
trait HasValues
|
|
{
|
|
public static function values(): array // ['male', 'female']
|
|
public static function options(): array // [{value: 'male', label: 'Laki-laki'}, ...]
|
|
}
|
|
```
|
|
|
|
Struktur enum standar:
|
|
|
|
```php
|
|
enum Gender: string
|
|
{
|
|
use HasValues;
|
|
|
|
case Male = 'male';
|
|
case Female = 'female';
|
|
|
|
public function label(): string
|
|
{
|
|
return match ($this) {
|
|
self::Male => 'Laki-laki',
|
|
self::Female => 'Perempuan',
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
**Sebelum → Sesudah**
|
|
|
|
```php
|
|
// ❌ Jangan
|
|
'gender' => ['required', 'string', Rule::in(array_values(Gender::cases()))],
|
|
'type' => ['required', 'string', Rule::in(array_column(FeedbackType::cases(), 'value'))],
|
|
|
|
// ✅ Pakai
|
|
'gender' => ['required', 'string', Rule::in(Gender::values())],
|
|
'type' => ['required', 'string', Rule::in(FeedbackType::values())],
|
|
```
|
|
|
|
Untuk data yang dikirim ke frontend (mis. isi `<Select>`), pakai `options()`
|
|
alih-alih membangun array `{value, label}` manual dengan `array_map`:
|
|
|
|
```php
|
|
// ❌ Jangan
|
|
'types' => array_map(fn (FeedbackType $type) => [
|
|
'value' => $type->value,
|
|
'label' => $type->label(),
|
|
], FeedbackType::cases()),
|
|
|
|
// ✅ Pakai
|
|
'types' => FeedbackType::options(),
|
|
```
|
|
|
|
Enum baru **wajib** langsung pakai `use HasValues;` sejak awal dibuat, tidak
|
|
perlu menunggu ada kebutuhan `values()`/`options()` baru ditambahkan.
|
|
|
|
---
|
|
|
|
## 2. Migration ✅
|
|
|
|
**Aturan:** kolom `enum()` di migration wajib pakai `EnumClass::values()`,
|
|
konsisten dengan aturan Enum di atas. Kolom `->default()` wajib pakai
|
|
`EnumClass::Case->value` (string), bukan objek case-nya langsung.
|
|
|
|
```php
|
|
// ❌ Jangan
|
|
$table->enum('status', array_values(StudentStatus::cases()))
|
|
->nullable()
|
|
->default(StudentStatus::Active); // objek enum, bukan string
|
|
|
|
// ✅ Pakai
|
|
$table->enum('status', StudentStatus::values())
|
|
->nullable()
|
|
->default(StudentStatus::Active->value);
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Bahasa UI ✅
|
|
|
|
Seluruh teks yang tampil ke pengguna (label, judul halaman, pesan toast,
|
|
placeholder, pesan error) ditulis dalam **Bahasa Indonesia**. Istilah teknis
|
|
dan identifier kode tetap dalam bahasa aslinya (bahasa Inggris), tapi teks
|
|
yang dibaca pengguna — termasuk teks di halaman auth/settings bawaan
|
|
starter-kit yang aslinya berbahasa Inggris — harus diterjemahkan supaya
|
|
konsisten dengan sisa aplikasi.
|
|
|
|
---
|
|
|
|
## 4. Struktur modul Admin ✅
|
|
|
|
Semua fitur berada di bawah `/admin` — tidak ada lagi controller atau
|
|
halaman yang menggantung di top-level `App\Http\Controllers` /
|
|
`resources/js/pages/` di luar folder `admin/`. Setiap fitur admin baru
|
|
(CRUD) mengikuti struktur namespace yang sama, di salah satu dari lima grup:
|
|
|
|
| Grup | Namespace Controller/Request | Prefix route |
|
|
| ------------------ | --------------------------------------- | ------------------ |
|
|
| Master data | `Admin\Master` | `admin/master/*` |
|
|
| Data operasional | `Admin\Manage` | `admin/manage/*` |
|
|
| Manajemen pengguna | `Admin\Users` | `admin/users/*` |
|
|
| Pengaturan akun | `Admin\Settings` | `admin/settings/*` |
|
|
| Resource mandiri | `Admin` (langsung, tanpa sub-namespace) | `admin/<resource>` |
|
|
|
|
"Resource mandiri" dipakai untuk fitur yang tidak cocok masuk ke 4 grup di
|
|
atas — misalnya `Admin\FeedbackController` di `admin/feedback` (data
|
|
personal milik user yang sedang login, bukan data akademik yang dikelola
|
|
admin atas user lain).
|
|
|
|
Nama route mengikuti pola `admin.<grup>.<resource>.<action>` (atau
|
|
`admin.<resource>.<action>` untuk resource mandiri), dan file Wayfinder
|
|
hasil generate mengikuti struktur folder yang sama di
|
|
`resources/js/routes/admin/<grup>/<resource>/`.
|
|
|
|
Halaman CRUD frontend memakai pola: `PageHeader` (judul + tombol Tambah) →
|
|
`FormDialog` (Create/Edit) → `DataTable` + `columns.tsx` → `DeleteConfirmDialog`.
|
|
|
|
### 4.1 Sub-namespace di dalam `Admin\Manage`
|
|
|
|
`Admin\Manage` sendiri dipecah lagi jadi 5 sub-namespace, mengikuti
|
|
pengelompokan yang sama seperti grup di `app-sidebar.tsx` (Kelas, Kelola,
|
|
Pengumuman, Keuangan, Layanan). **URL/nama route tidak berubah** (tetap flat
|
|
`admin/manage/*` / `admin.manage.*`) — hanya struktur folder PHP-nya
|
|
(Controller, Request, Service) yang mengikuti sub-namespace ini.
|
|
|
|
| Sub-namespace | Grup sidebar | Resource |
|
|
| --------------------------- | ------------ | -------------------------------------------------------------------------- |
|
|
| `Admin\Manage\Course` | Kelola | Course, CourseClass, ClassEnrollment |
|
|
| `Admin\Manage\Academic` | Kelas | CourseRegistration, Material, Assignment, Submission, Schedule, Attendance |
|
|
| `Admin\Manage\Announcement` | Pengumuman | Announcement |
|
|
| `Admin\Manage\Finance` | Keuangan | TuitionInvoice, TuitionPayment |
|
|
| `Admin\Manage\Service` | Layanan | LetterRequest, AcademicAdvisingLog |
|
|
|
|
Kalau menambah resource baru di salah satu grup ini, taruh Controller di
|
|
`app/Http/Controllers/Admin/Manage/<SubNamespace>/`, Request-nya di
|
|
`app/Http/Requests/Admin/Manage/<SubNamespace>/`, dan Service-nya di
|
|
`app/Services/Admin/Manage/<SubNamespace>/` — jangan taruh langsung di
|
|
`Admin\Manage` tanpa sub-namespace lagi.
|
|
|
|
---
|
|
|
|
## 5. Service ✅
|
|
|
|
**Method listing berpaginasi wajib bernama `paginated()`**, bukan
|
|
`getPaginated()` atau nama lain. Signature-nya seragam di seluruh Service
|
|
(kecuali ada parameter domain tambahan di depan, seperti `User $user` di
|
|
`FeedbackService`):
|
|
|
|
```php
|
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
|
```
|
|
|
|
Jangan tambahkan parameter `array $filters = []` kalau tidak benar-benar
|
|
dipakai di dalam method — `PaginatedRequest::validatedWithDefaults()` cuma
|
|
menghasilkan `perPage`/`search`/`sort`/`direction`, jadi parameter `$filters`
|
|
di banyak Service sebelumnya selalu kosong dan tidak pernah terisi oleh
|
|
controller manapun.
|
|
|
|
Controller yang memanggilnya selalu pakai `PaginatedRequest` + spread:
|
|
|
|
```php
|
|
'items' => $this->service->paginated(...$request->validatedWithDefaults()),
|
|
```
|
|
|
|
**Method yang mengembalikan daftar penuh tanpa paginasi** dibedakan sesuai
|
|
kegunaannya:
|
|
|
|
- **`getAllForSelect()`** — dipakai kalau hasilnya untuk mengisi dropdown
|
|
`<Select>` di form fitur *lain* (mis. `DepartmentService::getAllForSelect()`
|
|
dipakai di form Course/Lecturer/Student, bukan di halaman Department
|
|
sendiri).
|
|
- **Nama sesuai domain** — kalau daftar itu justru jadi listing utama
|
|
halaman itu sendiri (bukan sumber dropdown fitur lain) dan memang tidak
|
|
butuh paginasi, pakai nama yang menjelaskan isinya, mis.
|
|
`ScheduleService::all()` (listing halaman jadwal) atau
|
|
`AttendanceService::sessions()` (listing sesi presensi).
|
|
|
|
Jangan pernah menulis `getAll()` polos — nama itu ambigu antara dua kasus
|
|
di atas.
|
|
|
|
Urutan method standar dalam satu Service: `getAllForSelect()` (kalau ada) →
|
|
`paginated()` → `create()` → `update()` → `delete()` → helper `private` di
|
|
paling bawah.
|
|
|
|
---
|
|
|
|
## 6. Wayfinder ✅
|
|
|
|
Selalu jalankan generate dengan flag form variant, supaya `<Form
|
|
{...Controller.method.form()}>` tidak error saat runtime:
|
|
|
|
```bash
|
|
php artisan wayfinder:generate --with-form
|
|
```
|
|
|
|
`vite.config.ts` sudah diset `wayfinder({ formVariants: true })` sehingga
|
|
dev-server otomatis benar. Flag `--with-form` hanya wajib diingat kalau
|
|
generate manual lewat CLI.
|