Lompat ke konten
dokumentasi.id
Commit 32b2830
v1
Star0

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

8

Frontend Next.js dan Lapisan Data

Frontend Hydro adalah aplikasi Next.js 16 dengan React 19 yang dibangun sebagai static export. Seluruh halaman dirender di klien, dan setiap halaman berbicara ke backend lewat satu modul transport tunggal, api.ts, lalu lewat hook react-query di kait.ts. Halaman ini menjelaskan struktur App Router, peran api.ts, kebijakan cache dan retry milik QueryClient, hook yang dipakai halaman, serta cara izin membentuk menu. Detail aturan akses di sisi server ada di Autentikasi, 2FA, dan Izin Peran; daftar endpoint ada di Endpoint API dan Skema OpenAPI.

Struktur App Router dan layout aplikasi

Rute aplikasi berada di grup (app). Berkas frontend/src/app/(app)/layout.tsx:1-7 hanya membungkus children dengan komponen Kerangka. Kerangka inilah yang memegang sesi, menu, dan pengalihan halaman. Layout akar frontend/src/app/layout.tsx:1-20 memasang Penyedia sebagai pembungkus QueryClientProvider untuk seluruh pohon komponen.

Setiap halaman di grup itu adalah komponen klien ("use client"). Pola yang berulang: satu komponen Isi() berisi query dan tabel, lalu ekspor default yang membungkusnya dengan WajibIzin. Contoh paling ringkas ada di halaman stok: Isi memanggil useDaftar<Produk> dengan q, status, dan aktif: 1 di frontend/src/app/(app)/stok/page.tsx:13-16, sedangkan ekspor defaultnya menyaring halaman dengan izin kelola_stok di frontend/src/app/(app)/stok/page.tsx:79-85. Komponen WajibIzin sendiri menampilkan skeleton selama sesi dimuat dan Alert berjudul Tidak ada akses bila punya(izin) bernilai false — lihat frontend/src/components/bagian.tsx:69-80.

Satu pintu transport: api.ts

Semua request melewati fungsi minta di frontend/src/lib/api.ts:31-58. Fungsi itu menentukan apakah body berupa FormData, memasang Content-Type: application/json hanya untuk body non-berkas, dan menyisipkan X-CSRFToken untuk setiap metode selain GET. tokenCsrf membaca cookie csrftoken langsung dari document.cookie di frontend/src/lib/api.ts:14-17. Parameter query disusun oleh denganParam, yang membuang nilai undefined, null, dan string kosong sehingga endpoint tidak menerima filter kosong — frontend/src/lib/api.ts:21-29.

Ketika response bukan 2xx, minta melempar ApiError yang membawa status, kode, pesan, dan rincian (frontend/src/lib/api.ts:3-12). Dua status ditangani khusus di dalam minta: 401 mengalihkan ke /login/, dan kode === "wajib_2fa" mengalihkan ke /keamanan/, keduanya dengan penjagaan agar tidak terjadi pengalihan berulang saat halaman tujuan sedang dibuka — frontend/src/lib/api.ts:31-58.

sequenceDiagram
  participant H as Halaman
  participant K as useAksi di kait.ts
  participant A as minta di api.ts
  participant S as Server Django
  H->>K: mutation memanggil api.post
  K->>A: api.post dengan url dan body
  A->>A: tokenCsrf membaca cookie csrftoken
  A->>S: fetch dengan credentials same-origin dan X-CSRFToken
  S-->>A: 401 dengan kode wajib_2fa
  A->>A: location.href sama dengan /keamanan/
  A-->>K: throw ApiError
  K->>H: toast.error dengan pesanGalat
  Note over K,H: klien.invalidateQueries hanya berjalan saat sukses
%% fig: Siklus satu request yang ditolak wajib_2fa
%% desc: api.post meneruskan token CSRF, menerima 401 berkode wajib_2fa, mengalihkan ke /keamanan/, lalu melempar ApiError yang ditampilkan useAksi sebagai toast.
Fig. 1: Siklus satu request yang ditolak wajib_2faapi.post meneruskan token CSRF, menerima 401 berkode wajib_2fa, mengalihkan ke /keamanan/, lalu melempar ApiError yang ditampilkan useAksi sebagai toast.

Objek api hanya mengekspos tiga metode: get, post, dan patch (frontend/src/lib/api.ts:60-64). Tidak ada delete generik; operasi pembatalan dikirim lewat patch atau post ke endpoint khusus, seperti pemanggilan api.patch untuk menonaktifkan produk. Pesan galat untuk pengguna dirakit pesanGalat di frontend/src/lib/api.ts:66-80: kode stok_tidak_cukup diubah menjadi kalimat per produk dengan format diminta/tersedia dalam botol, kode validasi diratakan menjadi daftar teks, dan sisanya memakai galat.message. Galat yang bukan ApiError selalu menjadi Tidak bisa menghubungi server.

Alur allauth headless dipisah ke panggilAuth karena format balasannya berbeda dan 401 di sana adalah bagian normal alur login — frontend/src/lib/api.ts:82-107. Kerangka memakainya untuk keluar: panggilAuth("DELETE", "/auth/session"), lalu klien.clear(), lalu pindah ke /login/ (frontend/src/components/kerangka.tsx:126-145).

Penyedia react-query dan kebijakan retry

Penyedia membuat satu QueryClient dengan staleTime: 15_000, refetchOnWindowFocus: false, dan retry maksimal dua kali yang berhenti lebih awal bila galatnya ApiError berstatus di bawah 500 — frontend/src/components/penyedia.tsx:9-29. Artinya galat validasi dan izin tidak diulang, sedangkan galat 5xx diulang sampai dua kali. Komponen yang sama memasang Toaster di pojok kanan atas, sumber semua notifikasi toast.

Hook di kait.ts

Empat hook di kait.ts menjadi lapisan data yang dipakai halaman.

HookPerilakuNilai penting
useTundamenunda nilai dengan setTimeoutjeda bawaan 300 ms (frontend/src/lib/kait.ts:10-17)
useDaftaruseQuery berpaginasi atas Halaman<T>queryKey berisi [url, param, halaman], placeholderData: keepPreviousData (frontend/src/lib/kait.ts:21-31)
useAksiuseMutation dengan notifikasiinvalidateQueries() tanpa argumen setelah sukses, toast.error(pesanGalat(...)) saat gagal (frontend/src/lib/kait.ts:33-49)
useSesisesi penggunastaleTime 60_000 ms, punya(izin) membaca pengguna.izin (frontend/src/lib/kait.ts:51-63)

useDaftar mengembalikan halaman dan setHalaman bersama hasil query, dan mengembalikan halaman ke 1 setiap kali JSON.stringify(param) berubah (frontend/src/lib/kait.ts:21-31). useAksi memanggil klien.invalidateQueries() tanpa kunci, sehingga seluruh query di cache ditandai usang setelah satu aksi sukses — sederhana, tetapi berarti semua tabel memuat ulang.

Alur data halaman ke endpoint

graph TD
  Page["app/(app)/stok/page.tsx"] --> Izin["WajibIzin kelola_stok"]
  Page --> Daftar["useDaftar"]
  Page --> Tunda["useTunda"]
  Tunda --> Daftar
  Daftar --> Klien["QueryClient dari Penyedia"]
  Klien --> Get["api.get"]
  Get --> Minta["minta"]
  Minta --> Csrf["tokenCsrf"]
  Minta --> Param["denganParam"]
  Minta --> Fetch["fetch /api/produk/"]
  Fetch --> Galat["ApiError"]
  Galat --> Pesan["pesanGalat"]
  Pesan --> Tabel["PesanGalat di bagian.tsx"]
  Fetch --> Hasil["Halaman of Produk"]
  Hasil --> TabelData["TabelData"]
  style Fetch fill:#336791,color:#fff
  style Hasil fill:#8b5cf6,color:#fff
  style Klien fill:#8b5cf6,color:#fff
  style Minta fill:#f59e0b,color:#000
  style Csrf fill:#f59e0b,color:#000
  style Param fill:#f59e0b,color:#000
  style Galat fill:#dc2626,color:#fff
  style Izin fill:#2d7d46,color:#fff
%% fig: Alur data dari halaman ke endpoint
%% desc: Halaman stok memakai useDaftar dan useTunda untuk membentuk query, api.get meneruskannya lewat minta ke endpoint, lalu hasil atau ApiError kembali menjadi tabel atau pesan galat.
Fig. 2: Alur data dari halaman ke endpointHalaman stok memakai useDaftar dan useTunda untuk membentuk query, api.get meneruskannya lewat minta ke endpoint, lalu hasil atau ApiError kembali menjadi tabel atau pesan galat.

Diagram itu memperlihatkan satu jalur saja; halaman lain memakai kombinasi query yang sama. Dasbor di frontend/src/app/(app)/page.tsx:39-96 menyusun sendiri titik grafik tren dari Dasbor["tren"] dan menyediakan tabel alternatif di dalam elemen details. Tabel daftar memakai TabelData, yang merender header dan sel lewat flexRender serta memotong data per ukuran bawaan 25 baris (frontend/src/components/bagian.tsx:155-179). Galat query ditampilkan PesanGalat sebagai Alert bertema destructive dengan judul Data gagal dimuat (frontend/src/components/bagian.tsx:83-90).

izin dan menu samping

Menu didefinisikan sebagai konstanta MENU bertipe Grup[], tempat setiap tautan membawa izin yang harus dimiliki (frontend/src/components/kerangka.tsx:29-87). Navigasi memfilter tautan dengan punya(t.izin) dan melewati grup yang seluruh tautannya tersaring habis, lalu menandai tautan aktif dengan aria-current="page" (frontend/src/components/kerangka.tsx:91-124).

Grup menuizin tautanTautan
Dasborlihat_produk1
Produklihat_produk2
Pembeliankelola_transaksi3
Pelanggankelola_transaksi2
Stokkelola_stok4
Laporanlihat_laporan1
Pengaturankelola_pengguna3

Kerangka juga menegakkan 2FA: nilai perlu2fa dihitung dari pengguna.wajib_2fa && !pengguna.punya_2fa, dan selama perlu2fa benar menu tidak dirender sama sekali (frontend/src/components/kerangka.tsx:126-145). Selama sesi dimuat atau pengguna belum ada, Kerangka menampilkan Skeleton setinggi h-40; setelah itu barulah main merender children.

Tipe dari schema OpenAPI

Tipe domain tidak ditulis tangan. api-types.ts dihasilkan dari backend/schema.yaml oleh perintah npm run tipe-api (frontend/package.json:9). tipe.ts mengambil alias dari components["schemas"] sehingga Produk, Transaksi, Dasbor, Sesi, dan SesiPengguna selalu mengikuti schema backend — frontend/src/lib/tipe.ts:1-3. Berkas yang sama juga mendefinisikan bentuk halaman paginasi Halaman<T> = { count: number; results: T[] } dan MetodeBayar (frontend/src/lib/tipe.ts:28-29). Karena itu perubahan schema wajib diikuti regenerasi tipe, dan npm run typecheck akan gagal bila nama field berubah.

Jebakan yang perlu diingat

⚠️ Di produksi frontend hanya berisi berkas statis. rewrites ke API_DEV hanya aktif saat next dev; di produksi Nginx yang meneruskan /api/ ke Django, dan output: "export" dengan trailingSlash: true dipakai untuk itu (frontend/next.config.ts:18-36). Mengandalkan rewrites di produksi akan menghasilkan 404.

⚠️ useAksi memanggil invalidateQueries() tanpa kunci (frontend/src/lib/kait.ts:33-49). Satu aksi tulis membatalkan seluruh cache, jadi jangan menambahkan query mahal tanpa staleTime yang memadai.

⚠️ tokenCsrf mengembalikan string kosong bila cookie csrftoken belum ada (frontend/src/lib/api.ts:14-17). endpoint /api/auth/sesi/ yang dipanggil useSesi ada untuk memberi cookie itu; halaman yang memanggil POST sebelum sesi dimuat akan gagal 403.

⚠️ Pengalihan wajib_2fa terjadi di dua tempat: di minta saat response galat, dan di Kerangka lewat perlu2fa (frontend/src/lib/api.ts:31-58 dan frontend/src/components/kerangka.tsx:126-145). Menambah penanganan serupa di halaman akan menimbulkan pengalihan berulang.