# Best Practices ## PHP ### Traits - Gabungkan multiple `use` trait menjadi satu baris dengan koma. - **Hindari:** ```php use FlashesEntityMessage; use ParsesDataTableQuery; ``` - **Gunakan:** ```php use FlashesEntityMessage, ParsesDataTableQuery; ``` ## Database & Migrasi ### Struktur File Migrasi - Gunakan *anonymous class* yang meng-extend `Migration`. - Tulis *return type hint* `: void` secara eksplisit pada method `up()` dan `down()`. - **Urutan & Pengelompokan Kolom (Ordering & Grouping):** 1. **Primary Key**: `$table->id()` ditaruh paling atas. 2. **Relasi (Foreign Keys / Morphs)**: Ditaruh tepat setelah Primary Key di bagian teratas. 3. **Kolom Data Utama**: Dikelompokkan secara logis menggunakan spasi (baris kosong) sebagai pemisah antar kelompok data. 4. **Timestamps & Soft Deletes**: Ditaruh paling bawah. - **Gunakan:** ```php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('table_name', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete(); $table->string('name', 100); $table->string('description', 255)->nullable(); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); $table->softDeletes(); }); } public function down(): void { Schema::dropIfExists('table_name'); } }; ``` ### Definisi Kolom & Tipe Data - **ID & Timestamps:** - Gunakan `$table->id()` sebagai primary key. - Gunakan `timestamp` eksplisit dengan default `useCurrent()` dan `useCurrentOnUpdate()`. **Hindari penggunaan `$table->timestamps()` bawaan Laravel agar format kolom presisi dan konsisten.** - Tambahkan `$table->softDeletes()` (atau `deleted_at`) jika model terkait menggunakan trait `SoftDeletes`. ```php $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); $table->softDeletes(); ``` - **Kolom Enum:** - Hubungkan dengan PHP Backed Enum menggunakan `array_column(EnumName::cases(), 'value')`. - **Contoh:** ```php $table->enum('status', array_column(StatusEnum::cases(), 'value'))->default(StatusEnum::PENDING->value); ``` ### Relasi & Foreign Key - Gunakan `$table->foreignId()`. - Tentukan constraint relasi secara eksplisit dengan `constrained()` (dan sebutkan nama tabel target jika nama kolom tidak sesuai dengan nama tabel, misal: `constrained('users')`). - Tentukan aksi penghapusan secara eksplisit (`cascadeOnDelete()`, `nullOnDelete()`, atau `restrictOnDelete()`) sesuai kebutuhan integritas data. - **Contoh:** ```php $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete(); ``` ### Modifikasi Tabel (Alter Schema) - Saat menambahkan kolom baru, gunakan modifier `->after('column_name')` agar tata letak kolom di database terstruktur dengan logis. - Jika menambahkan kolom baru yang non-nullable pada tabel yang sudah berisi data, jalankan data migration di dalam method `up()` setelah schema builder: ```php DB::table('table_name')->update(['column_name' => 'default_value']); ``` - Di dalam method `down()`, pastikan untuk melepas constraint foreign key terlebih dahulu sebelum menghapus kolom bersangkutan. Gunakan `dropConstrainedForeignId` atau `dropForeign` untuk cara yang aman dan ringkas. - **Gunakan:** ```php public function down(): void { Schema::table('table_name', function (Blueprint $table) { $table->dropConstrainedForeignId('foreign_key_id'); $table->dropColumn(['column_one', 'column_two']); }); } ``` ## JavaScript / Vue ### Struktur Folder Page-Specific Components Komponen yang hanya digunakan oleh satu page tertentu (misalnya modal form, kolom tabel, action button) **harus ditempatkan di folder page-nya langsung** di dalam subfolder sesuai fungsinya, bukan di `resources/js/components/`. Folder `resources/js/components/` hanya untuk komponen yang **reusable** lintas page (seperti UI primitives, DataTable, ConfirmDialog, dll). Subfolder di dalam page folder dikelompokkan berdasarkan fungsi: - `form/` — modal form, form fields - `table/` — columns definition, data-table-actions, cell renderers - **Hindari:** ``` resources/js/components/admin/master/categories/ CategoryFormModal.vue columns.ts data-table-actions.vue resources/js/pages/admin/master/categories/ Index.vue ``` - **Gunakan:** ``` resources/js/pages/admin/master/categories/ Index.vue form/ CategoryFormModal.vue table/ columns.ts data-table-actions.vue ``` ### Import Path Gunakan relative import (`./`) untuk file yang berada di folder/subfolder yang sama, bukan absolute path `@/...`. - **Hindari:** ```ts import CategoryFormModal from '@/components/admin/master/categories/CategoryFormModal.vue'; ``` - **Gunakan:** ```ts // Dari Index.vue ke subfolder import CategoryFormModal from './form/CategoryFormModal.vue'; import { createColumns } from './table/columns'; // Dari columns.ts ke file satu folder import DataTableActions from './data-table-actions.vue'; ``` ### Pola CRUD (Create/Edit) Gunakan **modal** untuk CRUD yang sederhana (sedikit field, tidak ada nested data complex), dan **page terpisah** untuk CRUD yang kompleks. - **Modal** — form sederhana, biasanya 1-3 field, tidak memerlukan layout khusus: ``` pages/admin/master/categories/ Index.vue form/ CategoryFormModal.vue ← modal form table/ columns.ts data-table-actions.vue ``` Contoh: categories (1 field), suppliers (3 field), customers (3 field) - **Page terpisah** — form kompleks, banyak field, nested data, file upload, atau memerlukan layout khusus: ``` pages/admin/master/raw-materials/ Index.vue Create.vue ← halaman tambah Edit.vue ← halaman ubah form/ RawMaterialForm.vue ← form component table/ RawMaterialGroupedTable.vue data-table-actions.vue raw-material-status-toggle.vue ``` Contoh: raw-materials (nested prices, multi-image upload, dynamic variant rows)