Skip to content

Counter Per-Status di Stats Admin: Angka dari Kontrak

Adityo Guni Waluyo

Kartu dan tab dashboard admin KotaPortal akhirnya punya angka dari counter per-status stats admin, aditif dan bebas trik UI.

Ringkasan

Dashboard KotaPortal ngaco karena API cuma ngirim dua counter jadi banyak kartu kosong melompong. Akhirnya ditambahin enam counter snapshot baru tanpa ngerusak kontrak lama terus frontend dirapiin lewat codegen biar angka konsisten. Counter-nya juga nurut scope bidang dan badge antrean direfresh tiap menit pake polling aja.

Saya buka dashboard admin KotaPortal pagi itu, dan yang saya lihat di kartu statistik cuma tanda strip: "—". Tab antrean juga nggak punya angka sama sekali. Dugaan awal saya, mungkin frontend cuma salah parsing data, atau backend lupa nambahin field baru di response JSON. Saya bahkan sempat mencoba menebak angka dari array yang ada di sisi klien. Ternyata jalan buntu, karena datanya memang nggak pernah dikirim.

Masalahnya ada di kontrak API. Endpoint AdminLayananStats cuma punya counter diproses dan selesai, plus empat field legacy dari zaman pending-reviewed-approved-rejected yang selalu nol di data baru pasca-migrasi 000063. Sementara untuk status pengajuan, verifikasi, disetujui, dan ditolak? Nggak ada angkanya di mana-mana. Enam kartu di dashboard, cuma dua yang punya isi.

Bukan Sekadar Nambah Field

Pelajarannya di sini bukan soal asal nambah field baru sesuka hati. Menambah nilai enum itu tidak kompatibel untuk klien yang menganggap dia sudah tahu semua nilai yang mungkin [2]. Jadi jalurnya harus aditif: field lama nggak disentuh sedikit pun, field baru ditambah di belakangnya. Counter legacy itu saya pertahankan meskipun isinya selalu nol di data baru. Kedengarannya buang-buang tempat, tapi ini langkah konservatif untuk menjaga kontrak lama tetap utuh buat siapa pun yang masih membacanya; yang berubah cuma bertambah, bukan berpindah makna.

Sebagai gantinya, saya tambahkan enam counter snapshot baru: satu untuk tiap status dari pengajuan sampai selesai, lengkap dengan nama fieldnya di kontrak. Di komentar modelnya saya tegaskan sifatnya: angka per status ini snapshot baris berstatus tersebut saat ini, bukan kumulatif riwayat. Snapshot menjawab pertanyaan "berapa baris berstatus X sekarang", yang beda dari counter periode yang menghitung dari riwayat transisi. Di PostgreSQL, setiap pernyataan SQL memang melihat snapshot data pada satu titik waktu [1], jadi cara pandang "kondisi terkini" adalah hal yang wajar ditagih dari database.

Codegen dan Derivasi Angka di Top Level

Setelah kontrak backend beres, giliran frontend merapikan rumah. Ini lanjutan cerita peta transisi enam status yang kemarin saya tulis. Saya regenerate file v1.d.ts langsung dari openapi.yaml lewat codegen, karena tipe statis dari skema OpenAPI itu zero runtime cost [6]. Nggak ada lagi tombol yang manggil field yang ternyata nggak ada; compiler yang nyamber kalau nama field salah ketik.

Dengan tipe yang menyempit lewat type guard [3], saya ubah struktur STATUS_TABS dan STAT_CARDS. Dulu cuma tab diproses dan selesai yang punya countKey, sisanya tampil strip. Sekarang properti itu wajib di semua item. Tab baru tanpa angka sekarang gagal di build, bukan baru ketahuan pas ada yang lihat dashboard.

Transformasi data untuk render saya taruh di top level komponen, bukan di dalam Effect [4]. Sesuai panduan Redux, simpan data seminimal mungkin di state, sisanya derive [7]. Hasilnya paling kerasa di badge antrean: dulu dia pakai trik listLayanan({ status: "pengajuan", limit: 1 }) cuma buat curi angka total dari metadata, sekarang tinggal baca counter pengajuan dari stats yang sama. Satu sumber angka, dipakai tiga tempat: kartu, tab, dan badge menu.

Scope Tetap Berlaku, Badge Tetap Segar

Satu detail yang nggak boleh lolos: scope bidang. Counter per-status ini menghormati scope bidang aktor yang login. Di test, empat baris seed saya geser ke empat status berbeda, lalu counter global dan counter per bidang dicek berdampingan; angka per bidang cuma ikut baris milik bidangnya. Satu catatan jujur soal sifat angka ini: snapshot pagi bisa beda dengan snapshot siang, dan itu memang perilakunya, bukan cacat. Yang bikin saya betah dengan pendekatan ini adalah kontraknya yang jujur; field nggak pernah bohong soal maknanya. Dashboard admin bidang jadi nggak mungkin menghitung tiket di luar wilayahnya, meskipun query-nya tinggal satu fungsi yang sama untuk semua peran.

Terakhir, badge antrean tetap disegarkan berkala. setInterval memang fitur standar untuk repeated call dengan delay tetap [5], dan intervalnya saya bersihkan saat komponen unmount. Nggak ada websocket, nggak ada push. Untuk ukuran antrean pengajuan, polling ringan per menit itu cukup, dan jujur soal batasnya: angka basi sesaat masih jauh lebih baik daripada angka yang dari awal nggak pernah benar.

Sumber

Artikel terkait