# Quality Control Report - Pengumuman Desa Admin **Lokasi:** `/src/app/admin/(dashboard)/desa/pengumuman/` **Tanggal QC:** 25 Februari 2026 **Status:** ⚠️ **Needs Improvement** (ada issue critical yang perlu segera diperbaiki) --- ## 📋 Ringkasan Eksekutif Halaman Pengumuman Desa memiliki implementasi yang **cukup baik** dengan CRUD lengkap dan state management terstruktur. Namun ditemukan **15 issue** dengan rincian: - 🔴 **High Priority:** 2 issue - 🟡 **Medium Priority:** 7 issue - 🟢 **Low Priority:** 6 issue **Overall Score: 6.5/10** - Needs Improvement --- ## 📁 Struktur File yang Diperiksa ``` /src/app/admin/(dashboard)/desa/pengumuman/ ├── layout.tsx ├── _com/ │ └── layoutTabs.tsx # Tab navigation component ├── kategori-pengumuman/ │ ├── page.tsx # List kategori dengan search & pagination │ ├── create/ │ │ └── page.tsx # Form create kategori │ └── [id]/ │ └── page.tsx # Edit kategori └── list-pengumuman/ ├── page.tsx # List pengumuman dengan search & pagination ├── create/ │ └── page.tsx # Form create pengumuman (rich text) └── [id]/ ├── page.tsx # Detail pengumuman └── edit/ └── page.tsx # Edit pengumuman ``` **File Terkait:** - State: `/src/app/admin/(dashboard)/_state/desa/pengumuman.ts` - API: `/src/app/api/[[...slugs]]/_lib/desa/pengumuman/` (9 files) - API: `/src/app/api/[[...slugs]]/_lib/desa/pengumuman/kategori-pengumuman/` (6 files) - Schema: `/prisma/schema.prisma` (Model `Pengumuman` & `CategoryPengumuman`) --- ## 🔴 HIGH PRIORITY ISSUES ### 1. API - Hard Delete vs Soft Delete Mismatch (DATA LOSS RISK) **File:** `src/app/api/[[...slugs]]/_lib/desa/pengumuman/del.ts` ```typescript export default async function pengumumanDelete(context: Context) { const id = context.params?.id as string; // ❌ HARD DELETE - Data benar-benar terhapus dari database await prisma.pengumuman.delete({ where: { id } }); return { success: true, message: "Pengumuman berhasil dihapus" }; } ``` **Schema yang Diharapkan:** ```prisma model Pengumuman { deletedAt DateTime? @default(null) // Soft delete field isActive Boolean @default(true) } ``` **Dampak:** - **DATA LOSS** - Data pengumuman terhapus permanen, tidak bisa direcover - Audit trail hilang (riwayat pengumuman tidak ada lagi) - Inconsistent dengan schema design yang sudah ada soft delete fields - Bisa melanggar compliance requirements untuk data retention **Solusi:** ```typescript // Ganti hard delete dengan soft delete export default async function pengumumanDelete(context: Context) { const id = context.params?.id as string; // ✅ SOFT DELETE - Update deletedAt dan isActive await prisma.pengumuman.update({ where: { id }, data: { deletedAt: new Date(), isActive: false } }); return { success: true, message: "Pengumuman berhasil dihapus" }; } ``` **File yang Perlu Diperbaiki:** - `src/app/api/[[...slugs]]/_lib/desa/pengumuman/del.ts` - `src/app/api/[[...slugs]]/_lib/desa/pengumuman/kategori-pengumuman/del.ts` --- ### 2. Schema - `deletedAt` Default Value `now()` Bermasalah **File:** `prisma/schema.prisma` ```prisma model Pengumuman { id String @id @default(cuid()) judul String deletedAt DateTime @default(now()) // ❌ PROBLEMATIC DEFAULT isActive Boolean @default(true) } model CategoryPengumuman { id String @id @default(cuid()) name String @unique deletedAt DateTime @default(now()) // ❌ PROBLEMATIC DEFAULT isActive Boolean @default(true) } ``` **Dampak:** - Setiap record **baru langsung ter-mark sebagai deleted** saat dibuat - Query dengan filter `deletedAt: null` tidak akan dapat data baru - Soft delete logic tidak bekerja dengan benar - Data inconsistency antara `deletedAt` (set) dan `isActive` (true) **Solusi:** ```prisma model Pengumuman { id String @id @default(cuid()) judul String deletedAt DateTime? // ✅ Nullable, tanpa default isActive Boolean @default(true) } model CategoryPengumuman { id String @id @default(cuid()) name String @unique deletedAt DateTime? // ✅ Nullable, tanpa default isActive Boolean @default(true) } ``` **Migration Required:** ```bash # Generate migration bunx prisma migrate dev --name fix_deleted_at_default # Atau jika tidak pakai migrate bunx prisma db push # Data cleanup untuk record yang sudah ter-affected UPDATE "Pengumuman" SET "deletedAt" = NULL WHERE "isActive" = true; UPDATE "CategoryPengumuman" SET "deletedAt" = NULL WHERE "isActive" = true; ``` --- ## 🟡 MEDIUM PRIORITY ISSUES ### 3. UI - Search Parameter Hilang Saat Pagination **File:** `src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/page.tsx` ```typescript { load(newPage, 10); // ❌ Missing search parameter }} /> ``` **Dampak:** - Saat user ganti halaman, search query hilang - User harus ketik ulang search query - UX sangat buruk untuk pagination dengan search - Inconsistent dengan page lain (berita, potensi) **Solusi:** ```typescript { load(newPage, 10, debouncedSearch); // ✅ Include search parameter }} /> ``` **Note:** Pastikan function `load` menerima parameter search: ```typescript const load = async (page: number, limit: number, searchQuery?: string) => { // ... }; ``` --- ### 4. UI - Duplicate State Management **File:** `src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/[id]/edit/page.tsx` ```typescript // Local state const [formData, setFormData] = useState({ judul: '', deskripsi: '', content: '', categoryPengumumanId: '', }); const [originalData, setOriginalData] = useState({...formData}); // Global state (Valtio) editState.pengumuman.edit.form = { ...editState.pengumuman.edit.form, ...formData, // ❌ Duplicate data }; ``` **Dampak:** - Data inconsistency antara local state dan global state - Sulit debug karena data ada di 2 tempat - Memory overhead - Potential bugs saat reset form **Solusi:** **Option A - Gunakan hanya global state:** ```typescript // Hapus local state, gunakan langsung global state const formData = editState.pengumuman.edit.form; const handleResetForm = () => { editState.pengumuman.edit.form = { ...originalData }; }; ``` **Option B - Sinkronisasi dengan useEffect:** ```typescript useEffect(() => { // Sync local state ke global state editState.pengumuman.edit.form = { ...formData }; }, [formData]); ``` --- ### 5. UI - Error Handling Silent Failures **File:** `src/app/admin/(dashboard)/_state/desa/pengumuman.ts` ```typescript // Line 266-268 catch (error) { console.log((error as Error).message); // ❌ Error tidak ditampilkan ke user, silent failure } ``` **Dampak:** - User tidak tahu ada error - Sulit debug production issues - User experience buruk (loading forever tanpa feedback) **Solusi:** ```typescript catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; console.error('Failed to load pengumuman:', errorMessage); toast.error(`Gagal memuat data: ${errorMessage}`); } ``` --- ### 6. UI - ColSpan Mismatch **File:** `src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/page.tsx` ```typescript Nama Dibuat Aksi {/* 3 kolom total */} {loading ? ( {/* ❌ colSpan 4, seharusnya 3 */} ) : ( // ... )} ``` **Dampak:** Layout table tidak rapi, colSpan terlalu lebar. **Solusi:** ```typescript // ✅ Match column count ``` --- ### 7. State Management - Copy-Paste Error Message **File:** `src/app/admin/(dashboard)/_state/desa/pengumuman.ts` ```typescript // Line 68-70 kategoriPengumuman: { findMany: { loading: false, async load(page = 1, limit = 10, search = '') { try { // ... } catch (error) { console.error("Failed to load potensi desa:", res.data?.message); // ❌ Copy-paste error dari file potensi! Seharusnya "kategori pengumuman" } } } } ``` **Dampak:** - Membingungkan saat debug - Tidak profesional - Menunjukkan kurangnya attention to detail **Solusi:** ```typescript console.error("Failed to load kategori pengumuman:", res.data?.message); ``` --- ### 8. UI - Button Text "Batal" Membingungkan **File:** `src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/[id]/page.tsx` ```typescript ``` **Dampak:** User mungkin bingung apakah button ini akan cancel edit atau reset form. **Solusi:** ```typescript ``` --- ### 9. UI - Button Order Tidak Mengikuti UX Best Practice **File:** `src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/[id]/page.tsx` ```typescript