Laravel 12 Nuxt UI #7 Modal AddFinance

Belajar cara membangun aplikasi fullstack modern menggunakan Laravel 12, Inertia.js, Nuxt UI, dan Tailwind CSS. Tutorial ini membahas langkah demi langkah mulai dari instalasi, konfigurasi, hingga integrasi Laravel dengan Nuxt tanpa perlu membuat REST API. Cocok untuk pemula yang ingin memahami konsep fullstack Laravel dengan tampilan modern dan reaktif.

✅ Telah dilihat 1005 kali

Rating: 5.00 ⭐

... 07 November 2025, 08:14

Baik, sekarang kita beralih ke bagian View. Silakan teman-teman buka folder components yang berada di dalam direktori resources/js.

Selanjutnya, buatlah sebuah folder baru dengan nama finance-records. Folder ini akan digunakan untuk menampung seluruh komponen yang berkaitan dengan fitur pencatatan keuangan.

Di dalam folder finance-records tersebut, buat dua buah file baru, yaitu:

  • FinanceAddModal.vue
  • FinanceEditModal.vue

Setelah langkah ini, struktur folder teman-teman seharusnya terlihat seperti berikut:

resources/
└── js/
    └── components/
        └── finance-records/
            ├── FinanceAddModal.vue
            └── FinanceEditModal.vue

Dengan struktur ini, kita sudah menyiapkan tempat yang rapi dan terorganisir untuk komponen-komponen yang akan menangani proses penambahan dan pengeditan data keuangan.


Konfigurasi FinanceAddModal.vue

Silakan buka file FinanceAddModal.vue kemudian masukkan kode berikut ini:

<script setup lang="ts">
import { reactive, ref } from 'vue'
import { router } from '@inertiajs/vue3'
import type { FormSubmitEvent } from '@nuxt/ui'
import * as z from 'zod'

// Schema validasi Finance Record
const schema = z.object({
  date: z.string().min(1, 'Tanggal harus diisi'),
  income: z
    .number({ invalid_type_error: 'Uang masuk harus berupa angka' })
    .nonnegative('Uang masuk tidak boleh negatif'),
  expense: z
    .number({ invalid_type_error: 'Uang keluar harus berupa angka' })
    .nonnegative('Uang keluar tidak boleh negatif'),
})
type Schema = z.output<typeof schema>

// State modal
const open = ref(false)

// State form
const state = reactive<Partial<Schema>>({
  date: '',
  income: 0,
  expense: 0,
})

// State alert
const showAlert = ref(false)
const alertMessage = ref('')
const alertColor = ref<'success' | 'error'>('success')

// Submit handler
async function onSubmit(event: FormSubmitEvent<Schema>) {
  router.post('/finance-records', event.data, {
    onSuccess: () => {
      alertMessage.value = 'Catatan keuangan berhasil ditambahkan.'
      alertColor.value = 'success'
      showAlert.value = true

      // Reset form dan tutup modal
      open.value = false
      state.date = ''
      state.income = 0
      state.expense = 0

      // Sembunyikan alert otomatis
      setTimeout(() => (showAlert.value = false), 3000)
    },
    onError: (errors: any) => {
      const allErrors =
        Object.values(errors || {}).flat().join(' | ') ||
        'Gagal menambahkan catatan keuangan.'
      alertMessage.value = allErrors
      alertColor.value = 'error'
      showAlert.value = true
      setTimeout(() => (showAlert.value = false), 3000)
    },
  })
}
</script>

<template>
  <!-- Tombol utama -->
  <UButton
    label="Tambah Catatan Keuangan"
    icon="i-lucide-plus"
    color="primary"
    @click="open = true"
  />

  <!-- Alert pojok kanan bawah -->
  <div class="fixed bottom-4 right-4 z-50">
    <UAlert
      v-if="showAlert"
      :title="alertMessage"
      :color="alertColor"
      variant="subtle"
      :icon="
        alertColor === 'success'
          ? 'i-lucide-check-circle'
          : 'i-lucide-alert-circle'
      "
      close
      @update:open="showAlert = false"
    />
  </div>

  <!-- Modal tambah catatan keuangan -->
  <UModal
    v-model:open="open"
    title="Tambah Catatan Keuangan"
    description="Isi tanggal, jumlah uang masuk, dan uang keluar."
    class="max-w-lg"
  >
    <template #body>
      <UForm :schema="schema" :state="state" class="space-y-5" @submit="onSubmit">
        <!-- Tanggal -->
        <UFormField name="date" label="Tanggal" required class="w-full">
          <UInput v-model="state.date" type="date" class="w-full" />
        </UFormField>

        <!-- Uang Masuk -->
        <UFormField name="income" label="Uang Masuk (Rp)" required class="w-full">
          <UInput
            v-model.number="state.income"
            type="number"
            min="0"
            placeholder="Masukkan jumlah uang masuk"
            class="w-full"
          />
        </UFormField>

        <!-- Uang Keluar -->
        <UFormField name="expense" label="Uang Keluar (Rp)" required class="w-full">
          <UInput
            v-model.number="state.expense"
            type="number"
            min="0"
            placeholder="Masukkan jumlah uang keluar"
            class="w-full"
          />
        </UFormField>

        <USeparator />

        <!-- Tombol aksi -->
        <div class="flex justify-end gap-3 mt-4">
          <UButton
            label="Batal"
            color="neutral"
            variant="subtle"
            @click="open = false"
          />
          <UButton label="Simpan" color="primary" variant="solid" type="submit" />
        </div>
      </UForm>
    </template>
  </UModal>
</template>

Kode ini merupakan sebuah komponen Vue 3 dengan sintaks <script setup> yang digunakan untuk menampilkan modal form penambahan catatan keuangan (Finance Record). Komponen ini menggunakan beberapa library modern seperti:

  • Vue 3 Composition API (refreactive),
  • Inertia.js untuk komunikasi dengan Laravel backend,
  • Zod untuk validasi data form,
  • serta komponen antarmuka dari Nuxt UI (UForm, UButton, UAlert, dll).

Mari kita bahas bagian demi bagian.


Bagian <script setup lang="ts">

Bagian ini berisi logika dan data reaktif dari komponen. Penjelasannya sebagai berikut:

1. Import library

import { reactive, ref } from 'vue'
import { router } from '@inertiajs/vue3'
import type { FormSubmitEvent } from '@nuxt/ui'
import * as z from 'zod'
  • reactive dan ref digunakan untuk membuat state reaktif.
  • router dari Inertia digunakan untuk mengirim data form ke backend Laravel.
  • FormSubmitEvent hanya untuk memberikan tipe data pada event submit (karena menggunakan TypeScript).
  • zod digunakan untuk mendefinisikan skema validasi form.

2. Skema Validasi dengan Zod

const schema = z.object({
  date: z.string().min(1, 'Tanggal harus diisi'),
  income: z
    .number({ invalid_type_error: 'Uang masuk harus berupa angka' })
    .nonnegative('Uang masuk tidak boleh negatif'),
  expense: z
    .number({ invalid_type_error: 'Uang keluar harus berupa angka' })
    .nonnegative('Uang keluar tidak boleh negatif'),
})

Skema ini memastikan bahwa:

  • date wajib diisi.
  • income dan expense harus berupa angka dan tidak boleh negatif.

Zod akan memvalidasi input sebelum form dikirim ke server.

Kemudian dibuat alias tipe datanya:

type Schema = z.output<typeof schema>

Ini membantu TypeScript mengenali struktur data yang valid sesuai schema tersebut.


3. State Modal dan Form

const open = ref(false)

Variabel ini digunakan untuk mengontrol apakah modal terbuka atau tertutup.

const state = reactive<Partial<Schema>>({
  date: '',
  income: 0,
  expense: 0,
})

state menyimpan nilai form yang sedang diisi pengguna. Dideklarasikan dengan reactive agar setiap perubahan langsung terlihat di tampilan.


4. State untuk Alert

const showAlert = ref(false)
const alertMessage = ref('')
const alertColor = ref<'success' | 'error'>('success')

Ketiga variabel ini digunakan untuk menampilkan pesan notifikasi (alert) setelah proses submit berhasil atau gagal.


5. Fungsi Submit Form

async function onSubmit(event: FormSubmitEvent<Schema>) {
  router.post('/finance-records', event.data, {
    onSuccess: () => { ... },
    onError: (errors: any) => { ... },
  })
}

Fungsi ini akan dijalankan ketika form dikirim. router.post() mengirim data form ke endpoint Laravel /finance-records menggunakan Inertia.

Jika sukses:
  • Menampilkan pesan “Catatan keuangan berhasil ditambahkan.”
  • Menutup modal.
  • Mereset form.
  • Menyembunyikan alert otomatis setelah 3 detik.
Jika gagal:
  • Menampilkan pesan error hasil validasi dari server.
  • Menampilkan alert dengan warna merah.
  • Menutup alert otomatis setelah 3 detik.

Bagian <template>

Bagian ini mengatur tampilan (UI) dari komponen.


1. Tombol Utama

<UButton
  label="Tambah Catatan Keuangan"
  icon="i-lucide-plus"
  color="primary"
  @click="open = true"
/>

Tombol ini membuka modal ketika diklik.


2. Komponen Alert (Notifikasi)

<div class="fixed bottom-4 right-4 z-50">
  <UAlert
    v-if="showAlert"
    :title="alertMessage"
    :color="alertColor"
    variant="subtle"
    :icon="alertColor === 'success' ? 'i-lucide-check-circle' : 'i-lucide-alert-circle'"
    close
    @update:open="showAlert = false"
  />
</div>

Alert ini muncul di pojok kanan bawah layar untuk memberi tahu pengguna hasil proses (sukses/gagal).


3. Modal Form

<UModal
  v-model:open="open"
  title="Tambah Catatan Keuangan"
  description="Isi tanggal, jumlah uang masuk, dan uang keluar."
  class="max-w-lg"
>

Komponen modal yang akan terbuka atau tertutup sesuai dengan state open.

Di dalamnya terdapat form:


4. Form Input dan Validasi

<UForm :schema="schema" :state="state" @submit="onSubmit">

Form ini:

  • Terhubung dengan schema dari Zod untuk validasi otomatis.
  • Menggunakan state untuk data form.
  • Menjalankan fungsi onSubmit ketika dikirim.

Setiap field menggunakan komponen UFormField dan UInput untuk input dengan validasi otomatis.


5. Tombol Aksi di Dalam Modal

<div class="flex justify-end gap-3 mt-4">
  <UButton label="Batal" color="neutral" variant="subtle" @click="open = false" />
  <UButton label="Simpan" color="primary" variant="solid" type="submit" />
</div>

Dua tombol utama:

  • Batal: Menutup modal tanpa menyimpan.
  • Simpan: Mengirim data ke server melalui fungsi onSubmit.

Pada materi berikutnya, kita akan melakukan konfigurasi pada modal edit kita.

Daftar eBook