Added comprehensive QC reports and fix summaries for: - Desa (Berita, Potensi, Profil, Layanan, Penghargaan, Pengumuman) - Kesehatan (Posyandu) - Landing Page (APBDes, SDGS, Anti-Korupsi, Profil, Prestasi) - PPID (Daftar Informasi, Dasar Hukum, IKM, Permohonan, Struktur, Visi Misi)
9.2 KiB
Quality Control Report - Profil Desa Admin
Lokasi: /src/app/admin/(dashboard)/desa/profil/
Tanggal QC: 25 Februari 2026
Status: ⚠️ Needs Improvement
📋 Ringkasan Eksekutif
Halaman Profil Desa sudah memiliki struktur yang baik dengan separation of concerns yang jelas antara UI, State Management, dan API. Namun ditemukan 16 issue dengan rincian:
- 🔴 High Priority: 5 issue
- 🟡 Medium Priority: 5 issue
- 🟢 Low Priority: 6 issue
📁 Struktur File yang Diperiksa
/src/app/admin/(dashboard)/desa/profil/
├── layout.tsx
├── _lib/
│ ├── layoutTabsDetail.tsx
│ └── layoutTabsEdit.tsx
├── profil-desa/
│ ├── page.tsx
│ └── [id]/
│ ├── sejarah_desa/page.tsx
│ ├── visi_misi_desa/page.tsx
│ ├── lambang_desa/page.tsx
│ └── maskot_desa/page.tsx
├── profil-perbekel/
│ ├── page.tsx
│ └── [id]/page.tsx
└── profil-perbekel-dari-masa-ke-masa/
├── page.tsx
├── create/page.tsx
└── [id]/
├── page.tsx
└── edit/page.tsx
File Terkait:
- State:
/src/app/admin/(dashboard)/_state/desa/profile.ts(1058 baris) - API:
/src/app/api/[[...slugs]]/_lib/desa/profile/(15+ files) - Schema:
/prisma/schema.prisma
🔴 HIGH PRIORITY ISSUES
1. Schema Bug - deletedAt Default Value Salah
File: prisma/schema.prisma
model SejarahDesa {
deletedAt DateTime @default(now()) // ❌ BUG: Record langsung ter-delete!
isActive Boolean @default(true)
}
Dampak: Setiap record baru langsung ter-mark sebagai deleted karena deletedAt di-set ke now() saat create.
Solusi:
deletedAt DateTime? // ✅ Nullable, tanpa default
Affected Models: SejarahDesa, VisiMisiDesa, LambangDesa, MaskotDesa
2. API Tidak Ada Authentication
File: Semua file di /src/app/api/[[...slugs]]/_lib/desa/profile/
export default async function sejarahDesaUpdate(context: Context) {
// ❌ Tidak ada validasi session/user
const id = context.params?.id as string;
// Langsung proses update...
}
Dampak: Siapa saja yang tahu endpoint bisa update/delete data tanpa login.
Solusi: Tambahkan middleware authentication di route handler atau di setiap endpoint.
3. Hardcoded Nama Perbekel di UI
File: src/app/admin/(dashboard)/desa/profil/profil-perbekel/page.tsx
<Text>
I.B. Surya Prabhawa Manuaba, S.H., M.H. // ❌ Hardcoded!
</Text>
Dampak: UI tidak update otomatis jika ada perbekel baru.
Solusi: Ambil data dari database ProfilPerbekel dengan filter isActive: true.
4. Maskot Image Delete Logic Bug
File: src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/maskot-desa/update.ts
// Hapus semua gambar lama
for (const old of existing.images) {
await prisma.fileStorage.delete({ where: { id: old.imageId } });
}
Dampak: Semua gambar lama selalu dihapus, bahkan jika user ingin mempertahankan beberapa gambar.
Solusi: Implementasi diff logic untuk membandingkan gambar yang dipertahankan vs dihapus.
5. Magic String "edit" sebagai ID
File: Multiple files di state dan API
stateProfileDesa.sejarahDesa.findUnique.load("edit"); // ❌ Magic string
Dampak: Tidak type-safe, rentan typo, tidak scalable.
Solusi: Buat endpoint khusus /first atau /active untuk get record pertama yang aktif.
🟡 MEDIUM PRIORITY ISSUES
6. ProfileDesaImage Tanpa Soft Delete
File: prisma/schema.prisma
model ProfileDesaImage {
// ❌ Tidak ada deletedAt, isActive, createdAt, updatedAt
id String @id @default(cuid())
label String
imageId String?
}
Solusi: Tambahkan audit fields:
deletedAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
7. HTML Validation dengan Regex
File: src/app/admin/(dashboard)/desa/profil/profil-desa/[id]/sejarah_desa/page.tsx
const isHtmlEmpty = (html: string) => {
const textContent = html.replace(/<[^>]*>/g, '').trim(); // ❌ Tidak robust
return textContent === '';
};
Dampak: Validasi bisa gagal untuk edge cases (nested tags, comments, script tags).
Solusi: Gunakan library sanitize-html atau DOMParser untuk extract text content.
8. Image Label Tidak Divvalidasi
File: src/app/admin/(dashboard)/desa/profil/profil-desa/[id]/maskot_desa/page.tsx
Dampak: User bisa submit dengan label kosong atau sangat panjang.
Solusi: Tambahkan validation:
z.object({
label: z.string().min(1, "Label wajib diisi").max(100, "Maksimal 100 karakter")
})
9. Typo Variable Name
File: src/app/api/[[...slugs]]/_lib/desa/profile/profilePerbekel/update.ts
if (exisitng.imageId !== imageId) { // ❌ Typo: "exisitng"
Solusi: Fix menjadi existing.
10. Tidak Ada Error Boundary
Dampak: Jika ada error di component tree, seluruh halaman bisa crash.
Solusi: Tambahkan React Error Boundary di layout.tsx:
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallback={<ErrorFallback />}>
{children}
</ErrorBoundary>
🟢 LOW PRIORITY ISSUES
11. Image Loading Tanpa Skeleton
File: src/app/admin/(dashboard)/desa/profil/profil-desa/page.tsx
Dampak: Layout shift saat image load, UX kurang smooth.
Solusi: Tambahkan Skeleton component:
{loading ? (
<Skeleton height={200} circle />
) : (
<Image src={imageUrl} alt="..." />
)}
12. Reset Form Tanpa Konfirmasi
File: src/app/admin/(dashboard)/desa/profil/profil-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx
Dampak: User bisa tidak sengaja reset form dan kehilangan perubahan.
Solusi: Tambahkan modal konfirmasi sebelum reset.
13. Sequential API Calls Tanpa Promise.all
File: src/app/admin/(dashboard)/desa/profil/profil-desa/page.tsx
useEffect(() => {
stateProfileDesa.sejarahDesa.findUnique.load("edit");
stateProfileDesa.visiMisiDesa.findUnique.load("edit"); // ❌ Sequential
stateProfileDesa.lambangDesa.findUnique.load("edit");
stateProfileDesa.maskotDesa.findUnique.load("edit");
}, []);
Solusi: Gunakan Promise.all untuk parallel loading.
14. FileStorage Validation di Server
Dampak: User bisa upload file dengan tipe yang tidak diinginkan.
Solusi: Tambahkan MIME type check di server-side upload handler.
15. Mantan Perbekel Create Tidak Return ID
File: src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/create.ts
return {
success: true,
data: { ...body }, // ❌ Tidak return ID
};
Solusi: Return ID record yang baru dibuat untuk referensi.
16. Tidak Ada Unique Constraint
Dampak: Bisa ada multiple record aktif untuk model yang seharusnya single-record.
Solusi: Tambahkan unique constraint atau validasi di API layer.
✅ Yang Sudah Baik
- ✅ Struktur folder terorganisir dengan separation of concerns
- ✅ Responsive design untuk mobile dan desktop
- ✅ Loading states dan error handling dasar
- ✅ Form validation client-side dengan Valtio
- ✅ Preview image sebelum upload
- ✅ Toast notifications untuk feedback user
- ✅ File cleanup (hapus file fisik + database) di API
- ✅ Consistent response format di semua API endpoint
📊 Metrics
| Aspek | Score | Keterangan |
|---|---|---|
| Schema Design | 6/10 | Ada bug critical di deletedAt |
| API Security | 4/10 | Tidak ada authentication |
| API Design | 7/10 | RESTful, tapi ada magic string |
| UI/UX | 8/10 | Responsive, tapi ada hardcoded data |
| State Management | 7/10 | Valtio works, tapi tidak type-safe |
| Code Quality | 7/10 | Ada typo, tidak ada error boundary |
Overall Score: 6.5/10 - Needs Improvement
🎯 Action Plan
Week 1 (Critical Fixes)
- Fix
deletedAt @default(now())di schema - Tambahkan authentication middleware di API
- Fix hardcoded nama perbekel
- Fix maskot image delete logic
Week 2 (Medium Priority)
- Tambahkan audit fields di ProfileDesaImage
- Fix HTML validation dengan library
- Tambahkan validasi image label
- Fix typo dan tambahkan error boundary
Week 3 (Polish)
- Tambahkan skeleton loading untuk images
- Tambahkan konfirmasi reset form
- Optimasi dengan Promise.all
- Tambahkan server-side file validation
📝 Notes
-
Database Migration Required: Setelah fix schema, jalankan:
bunx prisma db push -
Data Migration: Record yang sudah ter-create dengan
deletedAtset perlu di-update:UPDATE "SejarahDesa" SET "deletedAt" = NULL WHERE "isActive" = true; -
Testing: Setelah fix authentication, test semua endpoint dengan:
- User belum login (should redirect)
- User login dengan role berbeda (should respect permissions)
Dibuat oleh: QC Automation
Review Status: ⏳ Menunggu Review Developer