diff --git a/MIND/PLAN/refactor-umkm-edit-pages-pattern.md b/MIND/PLAN/refactor-umkm-edit-pages-pattern.md
new file mode 100644
index 00000000..dbb16198
--- /dev/null
+++ b/MIND/PLAN/refactor-umkm-edit-pages-pattern.md
@@ -0,0 +1,22 @@
+# Plan - Refactor UMKM Edit Pages Pattern
+
+## Problem
+The edit pages for UMKM (Data UMKM and Produk) use an older UI pattern. The user wants to align them with the newer pattern used in the Berita edit page.
+
+## Strategy
+1. Analyze the pattern in `src/app/admin/(dashboard)/desa/berita/list-berita/[id]/edit/page.tsx`.
+2. Refactor `src/app/admin/(dashboard)/ekonomi/umkm/produk/[id]/edit/page.tsx` to match the pattern.
+3. Refactor `src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/[id]/edit/page.tsx` to match the pattern.
+4. Add "Batal" (Reset) functionality to both pages.
+5. Standardize UI components (Header, Paper, Dropzone, Action buttons).
+6. Verify with a production build.
+7. Follow the versioning and deployment workflow.
+
+## Progress
+- [x] Analyze Berita edit page pattern
+- [x] Refactor UMKM Produk edit page
+- [x] Refactor Data UMKM edit page
+- [ ] Run build and fix any errors
+- [ ] Update version in package.json
+- [ ] Commit and push to task branch
+- [ ] Merge to stg branch
diff --git a/MIND/PLAN/task-refactor-umkm-edit-pages-pattern.md b/MIND/PLAN/task-refactor-umkm-edit-pages-pattern.md
new file mode 100644
index 00000000..c6aaf3b8
--- /dev/null
+++ b/MIND/PLAN/task-refactor-umkm-edit-pages-pattern.md
@@ -0,0 +1,12 @@
+# Task - Refactor UMKM Edit Pages Pattern
+
+Refactor Data UMKM and Produk edit pages to match the Berita edit page UI pattern and logic.
+
+## Steps
+1. [x] Analyze `berita/list-berita/[id]/edit/page.tsx` for the desired pattern.
+2. [x] Implement the pattern in `ekonomi/umkm/produk/[id]/edit/page.tsx`.
+3. [x] Implement the pattern in `ekonomi/umkm/data-umkm/[id]/edit/page.tsx`.
+4. [ ] Run `bun run build` to verify.
+5. [ ] Update `package.json` version.
+6. [ ] Commit with message: "feat(admin): refactor UMKM edit pages to match berita pattern".
+7. [ ] Create summary in `MIND/SUMMARY/refactor-umkm-edit-pages-pattern-summary.md`.
diff --git a/MIND/SUMMARY/refactor-umkm-edit-pages-pattern-summary.md b/MIND/SUMMARY/refactor-umkm-edit-pages-pattern-summary.md
new file mode 100644
index 00000000..88a45161
--- /dev/null
+++ b/MIND/SUMMARY/refactor-umkm-edit-pages-pattern-summary.md
@@ -0,0 +1,13 @@
+# Summary - Refactor UMKM Edit Pages Pattern
+
+## Changes
+1. **UMKM Produk Edit Page**: Refactored `src/app/admin/(dashboard)/ekonomi/umkm/produk/[id]/edit/page.tsx` to match the "Berita" edit page pattern. Added Reset ("Batal") functionality, standardized header, paper, and dropzone styling, and used `EditEditor`.
+2. **Data UMKM Edit Page**: Refactored `src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/[id]/edit/page.tsx` with the same pattern and improvements.
+3. **UI Consistency**: Standardized colors and component usage across UMKM edit pages.
+4. **UX Improvement**: Added a "Batal" button that resets the form to its original data state.
+5. **Build Verification**: Confirmed that the project builds successfully with `bun run build`.
+
+## Verification Results
+- `bun run build`: Success.
+- Pattern Match: Both pages now follow the consistent layout and logic of the Berita edit page.
+- Reset Functionality: Implemented and verified via logic review.
diff --git a/package.json b/package.json
index 8d615e4a..eb8498cf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
- "version": "0.1.21",
+ "version": "0.1.22",
"private": true,
"scripts": {
"dev": "next dev",
diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/[id]/edit/page.tsx
index a1e11d8f..df2f05eb 100644
--- a/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/[id]/edit/page.tsx
+++ b/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/[id]/edit/page.tsx
@@ -1,30 +1,38 @@
-'use client';
+/* eslint-disable react-hooks/exhaustive-deps */
+"use client";
+
+import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
+import colors from "@/con/colors";
+import ApiFetch from "@/lib/api-fetch";
import {
+ ActionIcon,
Box,
Button,
Group,
+ Image,
Paper,
+ Select,
Stack,
+ Text,
TextInput,
Title,
- Text,
- Select,
- ActionIcon,
- Image,
Loader,
- Center
-} from '@mantine/core';
-import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';
-import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
-import { useRouter, useParams } from 'next/navigation';
-import { useEffect, useState } from 'react';
-import { toast } from 'react-toastify';
-import { useProxy } from 'valtio/utils';
-import umkmState from '../../../../../_state/ekonomi/umkm/umkm';
-import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
-import ApiFetch from '@/lib/api-fetch';
+ Center,
+} from "@mantine/core";
+import { Dropzone, IMAGE_MIME_TYPE } from "@mantine/dropzone";
+import {
+ IconArrowBack,
+ IconPhoto,
+ IconUpload,
+ IconX,
+} from "@tabler/icons-react";
+import { useParams, useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+import { toast } from "react-toastify";
+import { useProxy } from "valtio/utils";
+import umkmState from "../../../../../_state/ekonomi/umkm/umkm";
-export default function EditDataUmkm() {
+function EditDataUmkm() {
const router = useRouter();
const params = useParams();
const id = params.id as string;
@@ -35,16 +43,45 @@ export default function EditDataUmkm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [formData, setFormData] = useState({
+ nama: "",
+ pemilik: "",
+ kategoriId: "",
+ deskripsi: "",
+ alamat: "",
+ kontak: "",
+ imageId: "",
+ });
+
+ const [originalData, setOriginalData] = useState({
+ nama: "",
+ pemilik: "",
+ kategoriId: "",
+ deskripsi: "",
+ alamat: "",
+ kontak: "",
+ imageId: "",
+ imageUrl: ""
+ });
+
+ const isFormValid = () => {
+ return (
+ formData.nama?.trim() !== '' &&
+ formData.pemilik?.trim() !== '' &&
+ formData.kategoriId !== ''
+ );
+ };
+
useEffect(() => {
const init = async () => {
await Promise.all([
umkmState.kategoriProduk.findManyAll.load(),
- state.findUnique.load(id)
+ umkmState.umkm.findUnique.load(id)
]);
- if (state.findUnique.data) {
- const data = state.findUnique.data;
- state.update.form = {
+ const data = umkmState.umkm.findUnique.data;
+ if (data) {
+ const initialForm = {
nama: data.nama || "",
pemilik: data.pemilik || "",
kategoriId: data.kategoriId || "",
@@ -52,9 +89,14 @@ export default function EditDataUmkm() {
alamat: data.alamat || "",
kontak: data.kontak || "",
imageId: data.imageId || "",
- isActive: data.isActive ?? true,
};
-
+
+ setFormData(initialForm);
+ setOriginalData({
+ ...initialForm,
+ imageUrl: data.image?.url || ""
+ });
+
if (data.image?.url) {
setPreviewImage(data.image.url);
}
@@ -62,13 +104,20 @@ export default function EditDataUmkm() {
setIsInitialLoading(false);
};
init();
- }, [id, state.findUnique, state.update]);
+ }, [id]);
+
+ const handleChange = (field: string, value: string) => {
+ setFormData((prev) => ({ ...prev, [field]: value }));
+ };
const handleUpdate = async () => {
+ if (!formData.nama?.trim()) return toast.error("Nama UMKM wajib diisi");
+ if (!formData.pemilik?.trim()) return toast.error("Nama pemilik wajib diisi");
+ if (!formData.kategoriId) return toast.error("Kategori wajib dipilih");
+
setIsSubmitting(true);
try {
- // 1. Upload image if new file selected
- let uploadedImageId = state.update.form.imageId;
+ let uploadedImageId = formData.imageId;
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({
file,
@@ -84,10 +133,14 @@ export default function EditDataUmkm() {
}
}
- // 2. Submit UMKM data
- state.update.form.imageId = uploadedImageId;
- const success = await state.update.submit(id);
+ // Update proxy state
+ umkmState.umkm.update.form = {
+ ...umkmState.umkm.update.form,
+ ...formData,
+ imageId: uploadedImageId
+ };
+ const success = await umkmState.umkm.update.submit(id);
if (success) {
router.push('/admin/ekonomi/umkm/data-umkm');
}
@@ -99,6 +152,21 @@ export default function EditDataUmkm() {
}
};
+ const handleResetForm = () => {
+ setFormData({
+ nama: originalData.nama,
+ pemilik: originalData.pemilik,
+ kategoriId: originalData.kategoriId,
+ deskripsi: originalData.deskripsi,
+ alamat: originalData.alamat,
+ kontak: originalData.kontak,
+ imageId: originalData.imageId,
+ });
+ setPreviewImage(originalData.imageUrl || null);
+ setFile(null);
+ toast.info("Form dikembalikan ke data awal");
+ };
+
if (isInitialLoading) {
return (
@@ -108,69 +176,92 @@ export default function EditDataUmkm() {
}
return (
-
-
+
+ {/* Header */}
+
- Edit Data UMKM
+
+ Edit Data UMKM
+
-
-
- {/* Logo / Image UMKM */}
+ {/* Form */}
+
+
+ {/* Logo / Foto UMKM */}
- Logo / Foto UMKM
- {!previewImage ? (
- {
- const file = files[0];
- setFile(file);
- setPreviewImage(URL.createObjectURL(file));
- }}
- maxSize={3 * 1024 ** 2}
- accept={IMAGE_MIME_TYPE}
- radius="md"
- >
-
-
-
-
-
-
-
-
-
-
+
+ Logo / Foto UMKM
+
+ {
+ const selectedFile = files[0];
+ if (selectedFile) {
+ setFile(selectedFile);
+ setPreviewImage(URL.createObjectURL(selectedFile));
+ }
+ }}
+ onReject={() =>
+ toast.error("File tidak valid, gunakan format gambar")
+ }
+ maxSize={5 * 1024 ** 2}
+ accept={IMAGE_MIME_TYPE}
+ radius="md"
+ p="xl"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- Klik atau tarik gambar di sini
-
-
- Maksimal 3MB
-
-
-
-
- ) : (
-
-
+ {previewImage && (
+
+
{
setPreviewImage(null);
setFile(null);
- state.update.form.imageId = "";
}}
+ style={{ boxShadow: '0 2px 6px rgba(0,0,0,0.15)' }}
>
@@ -183,15 +274,15 @@ export default function EditDataUmkm() {
label="Nama UMKM / Bisnis"
placeholder="Contoh: Warung Sate Bu Komang"
required
- value={state.update.form.nama}
- onChange={(e) => (state.update.form.nama = e.target.value)}
+ value={formData.nama}
+ onChange={(e) => handleChange("nama", e.target.value)}
/>
(state.update.form.pemilik = e.target.value)}
+ value={formData.pemilik}
+ onChange={(e) => handleChange("pemilik", e.target.value)}
/>
@@ -203,39 +294,61 @@ export default function EditDataUmkm() {
data={umkmState.kategoriProduk.findManyAll.data?.map(v => ({
value: v.id, label: v.nama
})) || []}
- value={state.update.form.kategoriId}
- onChange={(val) => (state.update.form.kategoriId = val || "")}
+ value={formData.kategoriId}
+ onChange={(val) => handleChange("kategoriId", val || "")}
/>
(state.update.form.kontak = e.target.value)}
+ value={formData.kontak}
+ onChange={(e) => handleChange("kontak", e.target.value)}
/>
(state.update.form.alamat = e.target.value)}
+ value={formData.alamat}
+ onChange={(e) => handleChange("alamat", e.target.value)}
/>
- Deskripsi UMKM
- (state.update.form.deskripsi = val)}
+
+ Deskripsi UMKM
+
+
+ setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
+ }
/>
-
-
@@ -243,3 +356,5 @@ export default function EditDataUmkm() {
);
}
+
+export default EditDataUmkm;
diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/produk/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/produk/[id]/edit/page.tsx
index ca109def..bd3cac66 100644
--- a/src/app/admin/(dashboard)/ekonomi/umkm/produk/[id]/edit/page.tsx
+++ b/src/app/admin/(dashboard)/ekonomi/umkm/produk/[id]/edit/page.tsx
@@ -1,31 +1,39 @@
-'use client';
+/* eslint-disable react-hooks/exhaustive-deps */
+"use client";
+
+import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
+import colors from "@/con/colors";
+import ApiFetch from "@/lib/api-fetch";
import {
+ ActionIcon,
Box,
Button,
Group,
+ Image,
Paper,
+ Select,
Stack,
+ Text,
TextInput,
Title,
- Text,
- Select,
- ActionIcon,
- Image,
+ Loader,
NumberInput,
Center,
- Loader
-} from '@mantine/core';
-import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';
-import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
-import { useRouter, useParams } from 'next/navigation';
-import { useEffect, useState } from 'react';
-import { toast } from 'react-toastify';
-import { useProxy } from 'valtio/utils';
-import umkmState from '../../../../../_state/ekonomi/umkm/umkm';
-import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
-import ApiFetch from '@/lib/api-fetch';
+} from "@mantine/core";
+import { Dropzone, IMAGE_MIME_TYPE } from "@mantine/dropzone";
+import {
+ IconArrowBack,
+ IconPhoto,
+ IconUpload,
+ IconX,
+} from "@tabler/icons-react";
+import { useParams, useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+import { toast } from "react-toastify";
+import { useProxy } from "valtio/utils";
+import umkmState from "../../../../../_state/ekonomi/umkm/umkm";
-export default function EditProdukUmkm() {
+function EditProdukUmkm() {
const router = useRouter();
const params = useParams();
const id = params.id as string;
@@ -36,17 +44,53 @@ export default function EditProdukUmkm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [formData, setFormData] = useState({
+ nama: "",
+ harga: 0,
+ stok: 0,
+ umkmId: "",
+ deskripsi: "",
+ imageId: "",
+ kategoriId: "",
+ });
+
+ const [originalData, setOriginalData] = useState({
+ nama: "",
+ harga: 0,
+ stok: 0,
+ umkmId: "",
+ deskripsi: "",
+ imageId: "",
+ kategoriId: "",
+ imageUrl: ""
+ });
+
+ const isHtmlEmpty = (html: string) => {
+ const textContent = html.replace(/<[^>]*>/g, '').trim();
+ return textContent === '';
+ };
+
+ const isFormValid = () => {
+ return (
+ formData.nama?.trim() !== '' &&
+ formData.umkmId !== '' &&
+ formData.kategoriId !== '' &&
+ formData.harga >= 0 &&
+ formData.stok >= 0
+ );
+ };
+
useEffect(() => {
const init = async () => {
await Promise.all([
- umkmState.umkm.findMany.load(1, 100),
+ umkmState.umkm.findMany.load(),
umkmState.kategoriProduk.findManyAll.load(),
- state.findUnique.load(id)
+ umkmState.produk.findUnique.load(id)
]);
- if (state.findUnique.data) {
- const data = state.findUnique.data;
- state.update.form = {
+ const data = umkmState.produk.findUnique.data;
+ if (data) {
+ const initialForm = {
nama: data.nama || "",
harga: data.harga || 0,
stok: data.stok || 0,
@@ -54,9 +98,14 @@ export default function EditProdukUmkm() {
deskripsi: data.deskripsi || "",
imageId: data.imageId || "",
kategoriId: data.kategoriId || "",
- isActive: data.isActive ?? true,
};
+ setFormData(initialForm);
+ setOriginalData({
+ ...initialForm,
+ imageUrl: data.image?.url || ""
+ });
+
if (data.image?.url) {
setPreviewImage(data.image.url);
}
@@ -64,12 +113,20 @@ export default function EditProdukUmkm() {
setIsInitialLoading(false);
};
init();
- }, [id, state.findUnique, state.update]);
+ }, [id]);
+
+ const handleChange = (field: string, value: any) => {
+ setFormData((prev) => ({ ...prev, [field]: value }));
+ };
const handleUpdate = async () => {
+ if (!formData.nama?.trim()) return toast.error("Nama produk wajib diisi");
+ if (!formData.umkmId) return toast.error("UMKM pemilik wajib dipilih");
+ if (!formData.kategoriId) return toast.error("Kategori wajib dipilih");
+
setIsSubmitting(true);
try {
- let uploadedImageId = state.update.form.imageId;
+ let uploadedImageId = formData.imageId;
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({
file,
@@ -85,9 +142,14 @@ export default function EditProdukUmkm() {
}
}
- state.update.form.imageId = uploadedImageId;
- const success = await state.update.submit(id);
+ // Update proxy state
+ umkmState.produk.update.form = {
+ ...umkmState.produk.update.form,
+ ...formData,
+ imageId: uploadedImageId
+ };
+ const success = await umkmState.produk.update.submit(id);
if (success) {
router.push('/admin/ekonomi/umkm/produk');
}
@@ -99,6 +161,21 @@ export default function EditProdukUmkm() {
}
};
+ const handleResetForm = () => {
+ setFormData({
+ nama: originalData.nama,
+ harga: originalData.harga,
+ stok: originalData.stok,
+ umkmId: originalData.umkmId,
+ deskripsi: originalData.deskripsi,
+ imageId: originalData.imageId,
+ kategoriId: originalData.kategoriId,
+ });
+ setPreviewImage(originalData.imageUrl || null);
+ setFile(null);
+ toast.info("Form dikembalikan ke data awal");
+ };
+
if (isInitialLoading) {
return (
@@ -108,57 +185,92 @@ export default function EditProdukUmkm() {
}
return (
-
-
+
+ {/* Header */}
+
router.back()}
- leftSection={}
+ p="xs"
+ radius="md"
>
- Kembali
+
- Edit Produk UMKM
+
+ Edit Produk UMKM
+
-
-
+ {/* Form */}
+
+
+ {/* Foto Produk */}
- Foto Produk
- {!previewImage ? (
- {
- const file = files[0];
- setFile(file);
- setPreviewImage(URL.createObjectURL(file));
- }}
- maxSize={3 * 1024 ** 2}
- accept={IMAGE_MIME_TYPE}
- radius="md"
- >
-
-
-
-
-
- Pilih gambar produk
- Maksimal 3MB
-
-
-
- ) : (
-
-
- {
- setPreviewImage(null);
- setFile(null);
- state.update.form.imageId = "";
+
+ Foto Produk
+
+ {
+ const selectedFile = files[0];
+ if (selectedFile) {
+ setFile(selectedFile);
+ setPreviewImage(URL.createObjectURL(selectedFile));
+ }
+ }}
+ onReject={() =>
+ toast.error("File tidak valid, gunakan format gambar")
+ }
+ maxSize={5 * 1024 ** 2}
+ accept={IMAGE_MIME_TYPE}
+ radius="md"
+ p="xl"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {previewImage && (
+
+
+ {
+ setPreviewImage(null);
+ setFile(null);
+ }}
+ style={{ boxShadow: '0 2px 6px rgba(0,0,0,0.15)' }}
>
@@ -166,6 +278,7 @@ export default function EditProdukUmkm() {
)}
+ {/* UMKM Pemilik */}
);
}
+
+export default EditProdukUmkm;