# QC Summary - Profil Landing Page Module
**Scope:** Media Sosial, Pejabat Desa, Program Inovasi
**Date:** 2026-02-23
**Status:** β
Secara umum sudah baik, ada beberapa improvement minor
---
## π OVERVIEW
| Module | Schema | API | UI Admin | Public Page | Overall |
|--------|--------|-----|----------|-------------|---------|
| Media Sosial | β
Baik | β
Baik | β
Baik | N/A | π’ Baik |
| Pejabat Desa | β
Baik | β οΈ Ada issue | β
Baik | N/A | π‘ Perlu fix |
| Program Inovasi | β
Baik | β
Baik | β
Baik | N/A | π’ Baik |
---
## β
YANG SUDAH BAIK (COMMON)
### **1. Konsistensi UI/UX**
- β
Semua halaman menggunakan pattern yang sama (list β detail β edit)
- β
Responsive design (desktop table + mobile cards)
- β
Loading states dengan Skeleton
- β
Empty state handling yang informatif
- β
Search dengan debounce (1000ms)
- β
Pagination konsisten di semua modul
### **2. File Upload Handling**
- β
Dropzone dengan preview image
- β
Validasi format & ukuran file (max 5MB)
- β
Tombol hapus preview (IconX di pojok kanan atas)
- β
URL.createObjectURL untuk preview lokal
- β
Cleanup file state saat reset form
### **3. Form Validation**
- β
Zod schema untuk validasi typed
- β
isFormValid() check sebelum submit
- β
Error toast dengan pesan spesifik
- β
Button disabled saat invalid/loading
### **4. State Management (Valtio)**
- β
Proxy state untuk reaktivitas
- β
Separate state per modul (programInovasi, pejabatDesa, mediaSosial)
- β
Reset form function di setiap create/edit
- β
Original data tracking untuk reset
### **5. Error Handling**
- β
Try-catch di semua async operation
- β
Toast error dengan pesan user-friendly
- β
Console.error untuk debugging
- β
Modal konfirmasi hapus
---
## β οΈ ISSUES & SARAN PERBAIKAN
### **π΄ CRITICAL**
#### **1. Pejabat Desa - Edit Form Tidak Reset imageId ke Original**
**Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/pejabat-desa/[id]/edit/page.tsx`
**Masalah:**
```typescript
// Line ~100 - Load data
setFormData({
name: profileData.name || "",
position: profileData.position || "",
imageId: profileData.imageId || "", // β
Sudah benar
});
// Line ~170 - Handle reset
setFormData({
name: originalData.name,
position: originalData.position,
imageId: originalData.imageId, // β
Sudah benar
});
```
**Status:** β
**SUDAH BENAR** - Tidak ada issue di sini
**Verdict:** Tidak ada action needed.
---
#### **2. Media Sosial - Edit Form Sudah Benar**
**Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/media-sosial/[id]/edit/page.tsx`
**Verdict:** β
**SUDAH BENAR** - Original data tracking sudah implementasi dengan baik:
```typescript
const [originalData, setOriginalData] = useState({
name: '',
icon: '',
iconUrl: '',
imageId: '',
imageUrl: '',
});
// Load data
setOriginalData({
...newForm,
imageUrl: data.image?.link || '',
});
// Reset form
setFormData({
name: originalData.name,
icon: originalData.icon,
iconUrl: originalData.iconUrl,
imageId: originalData.imageId,
});
```
**Verdict:** Tidak ada action needed.
---
#### **3. Program Inovasi - Edit Form Sudah Benar**
**Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/edit/page.tsx`
**Verdict:** β
**SUDAH BENAR** - Original data tracking sudah implementasi dengan baik.
**Verdict:** Tidak ada action needed.
---
### **π‘ MEDIUM**
#### **4. Inconsistency: Fetch Method di State**
**Lokasi:** `src/app/admin/(dashboard)/_state/landing-page/profile.ts`
**Masalah:** Ada 3 pattern berbeda untuk fetch API:
```typescript
// β Pattern 1: ApiFetch (programInovasi.create)
const res = await ApiFetch.api.landingpage.programinovasi["create"].post(formData);
// β Pattern 2: fetch manual (programInovasi.findUnique)
const res = await fetch(`/api/landingpage/programinovasi/${id}`);
// β Pattern 3: fetch dengan headers (programInovasi.update)
const response = await fetch(`/api/landingpage/programinovasi/${this.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({...}),
});
// β Pattern 4: fetch dengan delete (programInovasi.delete)
const response = await fetch(`/api/landingpage/programinovasi/del/${id}`, {
method: "DELETE",
...
});
```
**Dampak:**
- Code consistency buruk
- Sulit maintenance
- Type safety tidak konsisten
**Rekomendasi:** Gunakan **ApiFetch** untuk semua operasi:
```typescript
// β
η»δΈ pattern
const res = await ApiFetch.api.landingpage.programinovasi["create"].post(formData);
const res = await ApiFetch.api.landingpage.programinovasi[id].get();
const res = await ApiFetch.api.landingpage.programinovasi[id].put(data);
const res = await ApiFetch.api.landingpage.programinovasi["del"][id].delete();
```
**Priority:** π‘ Medium
**Effort:** Low (refactor saja, tidak ada logic change)
---
#### **5. Media Sosial - Validasi IconUrl Tidak Selalu Relevan**
**Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/media-sosial/create/page.tsx`
**Masalah:**
```typescript
// Line ~67
const isFormValid = () => {
const isNameValid = stateMediaSosial.create.form.name?.trim() !== '';
const isIconUrlValid = stateMediaSosial.create.form.iconUrl?.trim() !== ''; // β Selalu required
const isCustomIconValid = selectedSosmed !== 'custom' || file !== null;
return isNameValid && isIconUrlValid && isCustomIconValid;
};
```
**Scenario:**
- User pilih icon "telephone" β iconUrl **seharusnya** required (nomor telepon)
- User pilih icon "facebook" β iconUrl **seharusnya** required (URL profile)
- Tapi jika user hanya mau tampil icon tanpa link β **tidak bisa**
**Rekomendasi:** Jadikan optional atau berikan default value:
```typescript
const isFormValid = () => {
const isNameValid = stateMediaSosial.create.form.name?.trim() !== '';
// IconUrl optional, atau validasi berdasarkan selectedSosmed
const isIconUrlValid = true; // atau validasi spesifik
const isCustomIconValid = selectedSosmed !== 'custom' || file !== null;
return isNameValid && isCustomIconValid;
};
```
**Priority:** π‘ Medium
**Effort:** Low
---
#### **6. Pejabat Desa - Hanya Ada 1 Data (Hardcoded ID "edit")**
**Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/pejabat-desa/page.tsx`
**Masalah:**
```typescript
// Line ~17
useShallowEffect(() => {
allList.findUnique.load("edit"); // β Hardcoded ID
}, []);
```
**Dampak:**
- Tidak scalable jika nanti ada multiple pejabat desa
- Pattern berbeda dari modul lain (yang pakai findMany)
- Confusing untuk developer baru
**Rekomendasi:**
- Jika memang hanya 1 data, tambahkan komentar:
```typescript
// Note: "edit" adalah special ID untuk single pejabat desa record
// Backend akan return data pertama jika ID tidak ditemukan
allList.findUnique.load("edit");
```
- Atau gunakan pattern yang lebih clear:
```typescript
allList.findUnique.load("single"); // atau "default"
```
**Priority:** π‘ Low-Medium
**Effort:** Low
---
#### **7. Program Inovasi - HTML Injection Risk di Deskripsi**
**Lokasi:**
- `src/app/admin/(dashboard)/landing-page/profil/program-inovasi/page.tsx` (line ~107)
- `src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/page.tsx` (line ~105)
**Masalah:**
```typescript
// β Direct HTML render tanpa sanitization
`, `