Jejak Audit Terstruktur dengan Deteksi Diff
Sistem pencatatan riwayat aktivitas (audit log) yang menyimpan rincian perubahan data dalam format JSON terstruktur lengkap dengan perbandingan nilai lama dan nilai baru.
Membaca sekitar 2 menit
Sistem pencatatan riwayat aktivitas (audit log) yang menyimpan rincian perubahan data dalam format JSON terstruktur lengkap dengan perbandingan nilai lama dan nilai baru.
Setiap operasi tambah, ubah, dan hapus memanggil helper buildLog() untuk menyusun payload JSON, yang kemudian di-render oleh komponen LogDetailView menjadi badge berwarna dan tampilan pergeseran nilai (old → new).
Pada operasi hapus tabungan, pencatatan log sengaja dieksekusi sebelum perintah DELETE utama dijalankan agar proses penulisan log tidak gagal akibat efek berantai foreign key cascade.
Masalahnya dulu
Ketika sebuah kantong tabungan dikelola bersama oleh beberapa anggota, pertanyaan seperti 'Siapa yang mengubah anggaran makan dari Rp 1.000.000 menjadi Rp 1.500.000?' atau 'Kapan transaksi ini dihapus?' pasti muncul. Pada awalnya, kolom detail di tabel log aktivitas hanya diisi teks biasa seperti 'Update pengeluaran'. Catatan seperti itu terlalu minim informasi karena tidak memperlihatkan nilai sebelum dan sesudah perubahan.
Kontrak JSON LogDetail dan Komponen LogDetailView
Tanpa perlu mengubah struktur tabel database di MySQL, kolom detail ditingkatkan fungsinya untuk menyimpan string JSON terstruktur yang dibangun oleh helper buildLog(). Pada setiap endpoint PUT (ubah data), server mengambil baris lama terlebih dahulu, membandingkan setiap field yang berubah, lalu menyusun pasangan { label, old, new }. Di antarmuka pengguna, komponen LogDetailView mem-parsing JSON tersebut menjadi tampilan diff lengkap dengan teks dicoret (line-through) untuk nilai lama dan tanda panah menuju nilai baru.
Log audit yang baik tidak hanya mencatat bahwa sesuatu telah berubah, tetapi memperlihatkan dengan jujur apa nilai sebelumnya dan menjadi apa sekarang.
Implementasi di ngodingan ini
Berikut adalah cara endpoint PUT membandingkan data lama dan baru menggunakan buildLog, serta bagaimana LogDetailView merendernya dengan tetap mendukung log versi lama (backward-compatible):
// 1. Server: menyusun diff perubahan sebelum disimpan ke log aktivitas
const fields: LogField[] = [];
if (category && category !== old.category)
fields.push({ label: "Kategori", old: old.category, new: category });
if (price && price !== old.price)
fields.push({ label: "Harga", old: fmtRp(old.price), new: fmtRp(price) });
if (total && total !== old.total)
fields.push({ label: "Total", old: fmtRp(old.total), new: fmtRp(newTotal) });
await insertActivityLog({
userUuid: user.uuid,
bankUuid: old.uuidbank,
type: "expense",
category: category ?? old.category,
action: "update",
detail: buildLog({
action: "update",
summary: `Pengeluaran: ${category ?? old.category}`,
fields,
}),
});
// 2. Client: merender perubahan nilai lama -> nilai baru
{f.old !== undefined && f.new !== undefined && (
<span className="flex items-baseline gap-1 flex-wrap">
<span className="line-through text-ink-400">{f.old}</span>
<span className="text-ink-300">→</span>
<span className="font-semibold text-ink-800">{f.new}</span>
</span>
)}Jebakan dan detail yang harus dijaga
- Menjaga kompatibilitas mundur (backward compatibility) di LogDetailView: pembungkus try-catch JSON.parse(detail) memastikan baris log lama yang masih berupa teks polos tetap tampil rapi tanpa menyebabkan komponen crash.