Files
desa-darmasaba/QC/Landing-Page/QC-SDGS-DESA.md
nico b9b00f0a20 docs(qc): add quality control summaries for various modules
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)
2026-04-23 12:11:55 +08:00

17 KiB

QC Summary - SDGs Desa Module

Scope: List SDGs Desa, Create, Edit, Detail
Date: 2026-02-23
Status: Secara umum sudah baik, ada beberapa improvement yang diperlukan


📊 OVERVIEW

Aspect Schema API UI Admin State Management Overall
SDGs Desa Baik Baik Baik ⚠️ Ada issue 🟡 Perlu fix

YANG SUDAH BAIK

1. UI/UX Consistency

  • Responsive design (desktop table + mobile cards)
  • Loading states dengan Skeleton
  • Search dengan debounce (1000ms)
  • Pagination konsisten
  • Empty state handling yang informatif
  • Modal konfirmasi hapus

2. File Upload Handling

  • Dropzone dengan preview image
  • Validasi format gambar (JPEG, JPG, PNG, WEBP)
  • Validasi ukuran file (max 5MB)
  • Tombol hapus preview (IconX di pojok kanan atas)
  • URL.createObjectURL untuk preview lokal

3. Form Validation

  • Zod schema untuk validasi typed
  • isFormValid() check sebelum submit
  • Error toast dengan pesan spesifik
  • Button disabled saat invalid/loading
  • Type number input untuk jumlah

4. CRUD Operations

  • Create dengan upload file
  • FindMany dengan pagination & search
  • FindUnique untuk detail
  • Delete dengan hard delete (via Prisma)
  • Update dengan file replacement

5. Edit Form - Original Data Tracking

  • Original data state untuk reset form
  • Load data existing dengan benar
  • Preview image dari data lama
  • Reset form mengembalikan ke data original

Code Example ( GOOD):

// Line ~60-80 - Load data
const data = await sdgsState.edit.load(id);

setFormData({
  name: data.name || "",
  jumlah: data.jumlah || "",
  imageId: data.imageId || "",
});

setOriginalData({
  ...newForm,
  imageUrl: data.image?.link || "",
});

setPreviewImage(data.image?.link || null);

// Line ~90 - Handle reset
const handleResetForm = () => {
  setFormData({
    name: originalData.name,
    jumlah: originalData.jumlah,
    imageId: originalData.imageId,
  });
  setPreviewImage(originalData.imageUrl || null);
  setFile(null);
  toast.info("Form dikembalikan ke data awal");
};

Verdict: SUDAH BENAR - Original data tracking sudah implementasi dengan baik.


⚠️ ISSUES & SARAN PERBAIKAN

🔴 CRITICAL

1. State Management - Inconsistency Fetch Pattern

Lokasi: src/app/admin/(dashboard)/_state/landing-page/sdgs-desa.ts

Masalah: Ada 2 pattern berbeda untuk fetch API:

// ❌ Pattern 1: ApiFetch (create, findMany)
const res = await ApiFetch.api.landingpage.sdgsdesa["create"].post({...});
const res = await ApiFetch.api.landingpage.sdgsdesa["findMany"].get({query});

// ❌ Pattern 2: fetch manual (findUnique, edit, delete)
const res = await fetch(`/api/landingpage/sdgsdesa/${id}`);
const response = await fetch(`/api/landingpage/sdgsdesa/del/${id}`, {
  method: "DELETE",
  headers: { "Content-Type": "application/json" },
});

Dampak:

  • Code consistency buruk
  • Sulit maintenance
  • Type safety tidak konsisten
  • Duplikasi logic error handling

Rekomendasi: Gunakan ApiFetch untuk semua operasi:

// ✅ Unified pattern
const res = await ApiFetch.api.landingpage.sdgsdesa["create"].post(data);
const res = await ApiFetch.api.landingpage.sdgsdesa[id].get();
const res = await ApiFetch.api.landingpage.sdgsdesa[id].put(data);
const res = await ApiFetch.api.landingpage.sdgsdesa["del"][id].delete();

Priority: 🔴 High
Effort: Medium (refactor di semua state methods)


2. findUnique State - Tidak Ada Loading State Management

Lokasi: src/app/admin/(dashboard)/_state/landing-page/sdgs-desa.ts

Masalah:

// Line ~125 - sdgsDesa.findUnique.load()
async load(id: string) {
  try {
    const res = await fetch(`/api/landingpage/sdgsdesa/${id}`);
    if (res.ok) {
      const data = await res.json();
      sdgsDesa.findUnique.data = data.data ?? null;
    } else {
      console.error("Failed to fetch data", res.status, res.statusText);
      sdgsDesa.findUnique.data = null;
    }
  } catch (error) {
    console.error("Error fetching data:", error);
    sdgsDesa.findUnique.data = null;
  }
  // ❌ MISSING: finally block untuk stop loading
}

Dampak: UI mungkin stuck di loading state jika ada error.

Rekomendasi: Tambahkan loading state dan finally block:

async load(id: string) {
  try {
    sdgsDesa.findUnique.loading = true; // ✅ Start loading
    const res = await fetch(`/api/landingpage/sdgsdesa/${id}`);
    if (res.ok) {
      const data = await res.json();
      sdgsDesa.findUnique.data = data.data ?? null;
    }
  } catch (error) {
    console.error("Error:", error);
  } finally {
    sdgsDesa.findUnique.loading = false; // ✅ Stop loading
  }
}

Priority: 🔴 Medium
Effort: Low


3. findManyAll - Tidak Digunakan di UI

Lokasi: src/app/admin/(dashboard)/_state/landing-page/sdgs-desa.ts

Masalah:

// Line ~95 - findManyAll state
findManyAll: {
  data: null as any[] | null,
  loading: false,
  load: async () => {
    // ... fetch all data tanpa pagination
  },
}

Analysis:

  • ⚠️ UNUSED: Tidak ada component yang menggunakan findManyAll
  • ⚠️ DEAD CODE: Menambah bundle size tanpa manfaat
  • ⚠️ CONFUSING: Developer baru bisa bingung kapan pakai findMany vs findManyAll

Rekomendasi: Remove jika tidak digunakan:

// ❌ Remove entire findManyAll block

Atau jika diperlukan untuk future feature, tambahkan comment:

// Reserved for future use - dropdown select without pagination
findManyAll: { ... }

Priority: 🔴 Low-Medium
Effort: Low


🟡 MEDIUM

4. Type Safety - Any Usage

Lokasi: src/app/admin/(dashboard)/_state/landing-page/sdgs-desa.ts

Masalah:

// Line ~58
data: null as any[] | null, // ❌ Using 'any'

// Line ~96
data: null as any[] | null, // ❌ Using 'any'

// Line ~118
data: null as Prisma.SdgsDesaGetPayload<{...}> | null, // ✅ Typed

Rekomendasi: Gunakan typed data consistently:

// findMany
data: null as Prisma.SdgsDesaGetPayload<{
  include: { image: true };
}>[] | null,

// findManyAll (jika tidak dihapus)
data: null as Prisma.SdgsDesaGetPayload<{
  include: { image: true };
}>[] | null,

Priority: 🟡 Medium
Effort: Medium (perlu update semua reference)


5. Console.log di Production

Lokasi: Multiple places di state file

Masalah:

// Line ~48
console.log(error);
toast.error("Gagal menambahkan data");

// Line ~80
console.error("Failed to load media sosial:", res.data?.message);

// Line ~85
console.error("Error loading media sosial:", error);

// Line ~132
console.error("Failed to fetch data", res.status, res.statusText);

// Line ~136
console.error("Error fetching data:", error);

// ... dan banyak lagi

Rekomendasi: Gunakan conditional logging:

if (process.env.NODE_ENV === 'development') {
  console.error("Error:", error);
}

Atau gunakan logging library (winston, pino, dll) dengan levels yang jelas.

Priority: 🟡 Low
Effort: Low


6. Error Message Tidak Konsisten

Lokasi: Multiple places

Masalah:

// Create - Line ~44
return toast.error("Gagal menambahkan data");

// Create - Line ~46
toast.error("Gagal menambahkan data");

// Delete - Line ~165
toast.error("Terjadi kesalahan saat menghapus sdgs desa");

// Edit - Line ~210
toast.error("Gagal memuat data");

// Edit update - Line ~250
toast.error("Gagal mengupdate sdgs desa");

// Toast success - Line ~240
toast.success("Berhasil update sdgs desa");

Issue:

  • Inconsistent capitalization ("sdgs desa" vs "Sdgs Desa")
  • Mixed patterns ("Gagal menambahkan" vs "Terjadi kesalahan")
  • Typo: "sdgs" seharusnya "SDGs" (acronym)

Rekomendasi: Standardisasi error messages:

// Pattern: "[Action] [resource] gagal" dengan proper casing
toast.error("Menambahkan data SDGs Desa gagal");
toast.error("Menghapus data SDGs Desa gagal");
toast.error("Memuat data SDGs Desa gagal");
toast.error("Memperbarui data SDGs Desa gagal");

// Atau lebih spesifik dengan context
toast.error("Gagal menambahkan data SDGs Desa");
toast.error("Gagal menghapus SDGs Desa");
toast.success("Berhasil memperbarui SDGs Desa");

Priority: 🟡 Low
Effort: Low


7. Zod Schema - Error Message Tidak Akurat

Lokasi: src/app/admin/(dashboard)/_state/landing-page/sdgs-desa.ts

Masalah:

// Line ~8
const templatesdgsDesaForm = z.object({
  name: z.string().min(1, "Judul minimal 1 karakter"), // ❌ "Judul" instead of "Nama"
  jumlah: z.string().min(1, "Deskripsi minimal 1 karakter"), // ❌ "Deskripsi" instead of "Jumlah"
  imageId: z.string().min(1, "File minimal 1"),
});

Dampak: User confusion saat validasi error muncul:

Error: "Judul minimal 1 karakter" // User: "Lho, ini field nama bukan judul?"
Error: "Deskripsi minimal 1 karakter" // User: "Ini field jumlah bukan deskripsi?"

Rekomendasi: Fix error messages:

const templatesdgsDesaForm = z.object({
  name: z.string().min(1, "Nama SDGs Desa minimal 1 karakter"),
  jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
  imageId: z.string().min(1, "Gambar wajib dipilih"),
});

Priority: 🟡 Low
Effort: Low


🟢 LOW (Minor Polish)

8. Component Name Mismatch

Lokasi: src/app/admin/(dashboard)/landing-page/SDGs/[id]/edit/page.tsx

Masalah:

// Line ~30
export default function EditKolaborasiInovasi() { // ❌ Wrong name
  // ...
}

Dampak: Confusing untuk developer lain, sulit untuk search/reference.

Rekomendasi: Rename ke yang sesuai:

export default function EditSDGsDesa() { // ✅ Correct name
  // ...
}

Priority: 🟢 Low
Effort: Low (hanya rename)


9. Text Label Tidak Konsisten

Lokasi: Multiple files

Masalah:

// Create page - Line ~100
<Text fw="bold" fz="sm" mb={6}>
  Gambar Program Inovasi // ❌ Wrong label
</Text>

// Edit page - Line ~170
<Text fw="bold" fz="sm" mb={6}>
  Gambar Program Inovasi // ❌ Wrong label (copy-paste?)
</Text>

Rekomendasi: Fix label:

<Text fw="bold" fz="sm" mb={6}>
  Gambar SDGs Desa // ✅ Correct label
</Text>

Priority: 🟢 Low
Effort: Low


10. Placeholder Search Tidak Spesifik

Lokasi: page.tsx

Masalah:

// Line ~17
<HeaderSearch
  title='Sdgs Desa'
  placeholder='Cari Sdgs Desa...' // ⚠️ Generic
  // ...
/>

Rekomendasi: Lebih spesifik:

placeholder='Cari nama SDGs Desa...'

Priority: 🟢 Low
Effort: Low


11. Capitalization Inconsistency

Lokasi: Multiple files

Masalah:

// page.tsx - Line ~17
title='Sdgs Desa' // ❌ Mixed case

// create/page.tsx - Line ~90
<Title>Tambah Sdgs Desa</Title> // ❌ Mixed case

// edit/page.tsx - Line ~160
<Title>Edit Sdgs Desa</Title> // ❌ Mixed case

// Should be:
// "SDGs Desa" (all caps for acronym)

Rekomendasi: Standardisasi:

title='SDGs Desa'
<Title>Tambah SDGs Desa</Title>
<Title>Edit SDGs Desa</Title>

Priority: 🟢 Low
Effort: Low


12. Schema - deletedAt Default Value

Lokasi: prisma/schema.prisma

Masalah:

model SdgsDesa {
  // ...
  deletedAt  DateTime  @default(now())  // ❌ Always has default value
  isActive   Boolean   @default(true)
}

Issue: deletedAt @default(now()) berarti setiap record baru langsung punya deletedAt value, yang bisa membingungkan untuk soft delete logic.

Rekomendasi:

model SdgsDesa {
  // ...
  deletedAt  DateTime? @default(null)  // ✅ Nullable, null = not deleted
  isActive   Boolean   @default(true)
}

Priority: 🟢 Medium (potential logic issue)
Effort: Medium (perlu migration)


13. Duplicate Error Logging

Lokasi: edit/page.tsx

Masalah:

// Line ~80
} catch (error) {
  console.error("Error loading sdgs desa:", error); // ❌ Duplicate
  toast.error("Gagal memuat data sdgs desa");
}

// Line ~120
} catch (error) {
  console.error("Error updating sdgs desa:", error); // ❌ Duplicate
  toast.error("Terjadi kesalahan saat memperbarui sdgs desa");
}

Rekomendasi: Cukup satu logging yang informatif:

} catch (error) {
  console.error('Failed to load SDGs Desa:', err);
  toast.error('Gagal memuat data SDGs Desa');
}

Priority: 🟢 Low
Effort: Low


14. API Response Handling - Inconsistent Error Messages

Lokasi: API endpoints

Masalah: (dari grep search results)

// del.ts - Line ~18
message: "Berhasil menghapus SDGS Desa", // ✅ Proper

// updt.ts - Line ~38
message: "SDGS Desa berhasil diperbarui", // ✅ Proper

// create.ts - (assumed)
// Might have inconsistent casing

Rekomendasi: Ensure all API responses use consistent "SDGs Desa" casing.

Priority: 🟢 Low
Effort: Low


📋 RINGKASAN ACTION ITEMS

Priority Issue Module Impact Effort Status
🔴 P0 Fetch method inconsistency State Medium Medium Perlu refactor
🔴 P0 Missing loading state in findUnique State Medium Low Perlu fix
🔴 P1 Unused findManyAll code State Low Low Should remove
🟡 M Type safety (any usage) State Low Medium Optional
🟡 M Console.log in production State Low Low Optional
🟡 M Error message inconsistency State/UI Low Low Optional
🟡 M Zod schema error messages State Low Low Should fix
🟢 L Component name mismatch Edit page Low Low Optional
🟢 L Wrong label text ("Program Inovasi") Create/Edit Low Low Should fix
🟢 L Placeholder tidak spesifik List page Low Low Optional
🟢 L Capitalization inconsistency All UI Low Low Should fix
🟢 M deletedAt default value Schema Medium Medium Should fix
🟢 L Duplicate error logging Edit page Low Low Optional

KESIMPULAN

Overall Quality: 🟢 BAIK (7.5/10)

Strengths:

  1. UI/UX konsisten & responsive
  2. File upload handling solid
  3. Form validation dengan Zod schema
  4. State management terstruktur (Valtio)
  5. Edit form reset sudah benar (original data tracking)
  6. Modal konfirmasi hapus untuk user safety
  7. Type number input untuk field jumlah

Areas for Improvement:

  1. ⚠️ Consistency: Fetch method pattern (ApiFetch vs fetch manual)
  2. ⚠️ Loading States: findUnique tidak ada loading state management
  3. ⚠️ Dead Code: findManyAll tidak digunakan
  4. ⚠️ Type Safety: Reduce any usage, gunakan Prisma types
  5. ⚠️ Schema: deletedAt default value bisa menyebabkan logic issue
  6. ⚠️ Naming: Component name & label text masih ada yang salah

Recommended Next Steps:

  1. Refactor fetch methods untuk gunakan ApiFetch consistently
  2. Add loading state di findUnique operations
  3. Remove findManyAll jika tidak digunakan
  4. Fix component name (EditKolaborasiInovasi → EditSDGsDesa)
  5. Fix label text ("Gambar Program Inovasi" → "Gambar SDGs Desa")
  6. Fix capitalization (Sdgs → SDGs)
  7. Optional: Improve type safety dengan remove any

📈 COMPARISON WITH OTHER MODULES

Aspect Profil Desa Anti Korupsi SDGs Desa Notes
Fetch Pattern ⚠️ Mixed ⚠️ Mixed ⚠️ Mixed All perlu refactor
Loading State ⚠️ Some missing ⚠️ Some missing ⚠️ Missing Same issue
Edit Form Reset Good Good Good Consistent
Type Safety ⚠️ Some any ⚠️ Some any ⚠️ Some any Same issue
File Upload Images Documents Images Different use case
Error Handling Good Good (better) Good Consistent
Dead Code None None ⚠️ findManyAll SDGs unique issue
Naming Issues None ⚠️ Some ⚠️ Some Similar level

Catatan: Secara keseluruhan, modul SDGs Desa sudah production-ready dengan beberapa improvements yang bisa dilakukan secara incremental. Module ini memiliki struktur yang mirip dengan modul lain (Profil, Desa Anti Korupsi) sehingga pattern improvement yang sama bisa diterapkan.

Unique Issues:

  1. findManyAll unused code (tidak ada di modul lain)
  2. Component name mismatch (EditKolaborasiInovasi)
  3. Wrong label text ("Gambar Program Inovasi") - kemungkinan copy-paste dari modul Program Inovasi