diff --git a/.windsurf/rules/claude-mem-context.md b/.windsurf/rules/claude-mem-context.md
new file mode 100644
index 00000000..98e6203e
--- /dev/null
+++ b/.windsurf/rules/claude-mem-context.md
@@ -0,0 +1,5 @@
+# Memory Context from Past Sessions
+
+*No context yet. Complete your first session and context will appear here.*
+
+Use claude-mem's MCP search tools for manual memory queries.
diff --git a/MIND/PLAN/refactor-umkm-pasar-desa-v2.md b/MIND/PLAN/refactor-umkm-pasar-desa-v2.md
new file mode 100644
index 00000000..43387d8f
--- /dev/null
+++ b/MIND/PLAN/refactor-umkm-pasar-desa-v2.md
@@ -0,0 +1,24 @@
+# Plan: Refactor UMKM and Pasar Desa (Consolidation)
+
+## Objective
+Consolidate "Pasar Desa" into the UMKM module. Pasar Desa is no longer a separate entity; it is now strictly a collection of products belonging to UMKM entities.
+
+## Steps:
+1. **Cleanup API**: Remove `PasarDesa` and `KategoriProduk` (from `pasar-desa` folder) imports from `src/app/api/[[...slugs]]/_lib/ekonomi/index.ts`.
+2. **Admin UI**:
+ - Remove "Pasar Desa" menu from `src/app/admin/_com/list_PageAdmin.tsx`.
+ - Ensure "UMKM" menu handles all product management.
+3. **Public UI**:
+ - Remove "Pasar Desa" from `src/con/navbar-list-menu.ts`.
+ - Refactor `src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx` to remove the "Produk Pasar Desa" tab.
+ - Rename the page or adjust its purpose to be the unified UMKM/Product hub.
+4. **Prisma Schema**:
+ - Ensure `umkmId` is mandatory in `PasarDesa` model (already seems to be).
+ - (Optional) Rename `PasarDesa` to `ProdukUmkm` if requested, but user said it's optional. For now, keep it as `PasarDesa` to minimize breaking changes.
+5. **Build & Verify**: Run `bun run build` and check for any broken references.
+
+## Verification:
+- No "Pasar Desa" menu in Admin.
+- No "Pasar Desa" menu in Public Navbar.
+- Public page `/darmasaba/ekonomi/pasar-desa` (or new path) shows UMKM products only.
+- Successful build.
diff --git a/MIND/PLAN/task-refactor-umkm-pasar-desa-v2.md b/MIND/PLAN/task-refactor-umkm-pasar-desa-v2.md
new file mode 100644
index 00000000..bc8c6ca9
--- /dev/null
+++ b/MIND/PLAN/task-refactor-umkm-pasar-desa-v2.md
@@ -0,0 +1,8 @@
+# Task: Refactor UMKM and Pasar Desa (Consolidation)
+
+- [ ] Cleanup API imports in `src/app/api/[[...slugs]]/_lib/ekonomi/index.ts`
+- [ ] Remove "Pasar Desa" menu in `src/app/admin/_com/list_PageAdmin.tsx`
+- [ ] Remove "Pasar Desa" from public navbar in `src/con/navbar-list-menu.ts`
+- [ ] Refactor public page `src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx`
+- [ ] Run build and fix errors
+- [ ] Update version and commit
diff --git a/MIND/SUMMARY/refactor-umkm-pasar-desa-v2-summary.md b/MIND/SUMMARY/refactor-umkm-pasar-desa-v2-summary.md
new file mode 100644
index 00000000..cc6b46b5
--- /dev/null
+++ b/MIND/SUMMARY/refactor-umkm-pasar-desa-v2-summary.md
@@ -0,0 +1,34 @@
+# Summary: Refactor UMKM and Pasar Desa (Consolidation)
+
+## Objective
+Successfully consolidated "Pasar Desa" into the UMKM module. Pasar Desa is now strictly a part of the UMKM ecosystem, where every product must belong to an UMKM entity.
+
+## Changes Made:
+1. **Backend & API**:
+ - Removed redundant `pasar-desa` API endpoints from `src/app/api/[[...slugs]]/_lib/ekonomi/index.ts`.
+ - Removed invalid `not: null` filters for `umkmId` in UMKM dashboard and product findMany APIs (since `umkmId` is now mandatory).
+ - Updated `umkmState` to include `findUnique` for products.
+2. **Admin UI**:
+ - Removed "Pasar Desa" menu items from `src/app/admin/_com/list_PageAdmin.tsx` for all roles.
+ - Cleaned up unused state management for `pasar-desa`.
+3. **Public UI**:
+ - Replaced "Pasar Desa" with "UMKM" in the public navbar (`src/con/navbar-list-menu.ts`).
+ - Unified the public hub at `/darmasaba/ekonomi/umkm`.
+ - Refactored the hub page to remove the "Produk Pasar Desa" tab and rename other tabs to "Katalog Produk" and "Direktori Bisnis".
+ - Updated product detail routing to `/darmasaba/ekonomi/umkm/produk/[id]`.
+ - Updated UMKM profile routing to `/darmasaba/ekonomi/umkm/[id]`.
+4. **Database & Seeding**:
+ - Created a new UMKM seeder (`prisma/_seeder_list/ekonomi/seed_umkm.ts`).
+ - Updated `seedPasarDesa` to link products to UMKM entities, satisfying the mandatory `umkmId` constraint.
+ - Integrated `seedUmkm` into the main `seed.ts`.
+5. **Code Cleanup**:
+ - Fixed missing imports (e.g., `IconUser`).
+ - Removed unused imports across several files.
+ - Fixed copy-pasted toast messages in unrelated modules.
+
+## Verification**:
+- Build successful (`bun run build`).
+- No "Pasar Desa" menu in Admin.
+- "UMKM" menu in Public Navbar points to unified hub.
+- Unified hub shows products linked to UMKM.
+- Product detail pages correctly show seller information.
diff --git a/package.json b/package.json
index e18b0dfc..2c8f22fe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
- "version": "0.1.17",
+ "version": "0.1.18",
"private": true,
"scripts": {
"dev": "next dev",
diff --git a/prisma/_seeder_list/ekonomi/seed_pasar_desa.ts b/prisma/_seeder_list/ekonomi/seed_pasar_desa.ts
index b1754cf1..13dfe6e8 100644
--- a/prisma/_seeder_list/ekonomi/seed_pasar_desa.ts
+++ b/prisma/_seeder_list/ekonomi/seed_pasar_desa.ts
@@ -25,8 +25,11 @@ export async function seedPasarDesa() {
console.log("🔄 Seeding Pasar Desa...");
+ let i = 1;
for (const p of pasarDesa) {
let imageId: string | null = null;
+ const umkmId = `umkm-${i}`; // Map to umkm-1, umkm-2, etc.
+ i = (i % 4) + 1;
if (p.imageName) {
const image = await prisma.fileStorage.findUnique({
@@ -54,6 +57,7 @@ export async function seedPasarDesa() {
kontak: p.kontak,
imageId,
kategoriProdukId: p.kategoriProdukId,
+ umkmId: umkmId,
},
create: {
id: p.id,
@@ -65,6 +69,7 @@ export async function seedPasarDesa() {
kontak: p.kontak,
imageId,
kategoriProdukId: p.kategoriProdukId,
+ umkmId: umkmId,
},
});
diff --git a/prisma/_seeder_list/ekonomi/seed_umkm.ts b/prisma/_seeder_list/ekonomi/seed_umkm.ts
new file mode 100644
index 00000000..0dfeda8a
--- /dev/null
+++ b/prisma/_seeder_list/ekonomi/seed_umkm.ts
@@ -0,0 +1,67 @@
+import prisma from "@/lib/prisma";
+
+export const umkmData = [
+ {
+ id: "umkm-1",
+ nama: "Warung Pasar Darmasaba",
+ pemilik: "Pak Made",
+ deskripsi: "Warung tradisional kebutuhan pokok",
+ alamat: "Pasar Desa Darmasaba",
+ kontak: "081234567890",
+ kategoriId: "5c06chf7-123f-7igd-0663-5e9h76e55060"
+ },
+ {
+ id: "umkm-2",
+ nama: "Jajanan Pasar Bu Made",
+ pemilik: "Bu Made",
+ deskripsi: "Spesialis jajanan tradisional Bali",
+ alamat: "Pasar Desa Darmasaba",
+ kontak: "082145678901",
+ kategoriId: "4b95bge6-012e-5ged-9552-4d8g65d44959"
+ },
+ {
+ id: "umkm-3",
+ nama: "Sayur Segar Pak Wayan",
+ pemilik: "Pak Wayan",
+ deskripsi: "Sayuran lokal segar setiap hari",
+ alamat: "Pasar Desa Darmasaba",
+ kontak: "087865432109",
+ kategoriId: "5c06chf7-123f-8jhe-0663-5e9h76e55060"
+ },
+ {
+ id: "umkm-4",
+ nama: "Ayam & Daging Segar Darmasaba",
+ pemilik: "Pak Ketut",
+ deskripsi: "Daging ayam dan sapi segar",
+ alamat: "Pasar Desa Darmasaba",
+ kontak: "081998877665",
+ kategoriId: "5c06chf7-123f-9kif-0663-5e9h76e55060"
+ }
+];
+
+export async function seedUmkm() {
+ console.log("🔄 Seeding UMKM...");
+ for (const u of umkmData) {
+ await prisma.umkm.upsert({
+ where: { id: u.id },
+ update: {
+ nama: u.nama,
+ pemilik: u.pemilik,
+ deskripsi: u.deskripsi,
+ alamat: u.alamat,
+ kontak: u.kontak,
+ kategoriId: u.kategoriId,
+ },
+ create: {
+ id: u.id,
+ nama: u.nama,
+ pemilik: u.pemilik,
+ deskripsi: u.deskripsi,
+ alamat: u.alamat,
+ kontak: u.kontak,
+ kategoriId: u.kategoriId,
+ },
+ });
+ }
+ console.log("✅ UMKM seeded successfully");
+}
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index fba82beb..f4c3be9d 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1434,8 +1434,8 @@ model PasarDesa {
// Data Stok & UMKM
stok Int @default(0)
- umkm Umkm? @relation(fields: [umkmId], references: [id])
- umkmId String?
+ umkm Umkm @relation(fields: [umkmId], references: [id])
+ umkmId String
// Relasi Penjualan
penjualan PenjualanProduk[]
diff --git a/prisma/seed.ts b/prisma/seed.ts
index 85bb4ffe..e5861d40 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -23,6 +23,7 @@ import { seedPendudukUsiaKerjaYangMenganggur } from "./_seeder_list/ekonomi/seed
import { seedProgramKemiskinan } from "./_seeder_list/ekonomi/seed_program_kemiskinan";
import { seedSektorUnggulanDesa } from "./_seeder_list/ekonomi/seed_sektor_unggulan_desa";
import { seedStrukturBumdes } from "./_seeder_list/ekonomi/seed_struktur_bumdes";
+import { seedUmkm } from "./_seeder_list/ekonomi/seed_umkm";
import { seedAjukan } from "./_seeder_list/inovasi/seed_ajukan";
import { seedDesaDigital } from "./_seeder_list/inovasi/seed_desa_digital";
import { seedInfoTeknologi } from "./_seeder_list/inovasi/seed_info_teknologi";
@@ -274,6 +275,9 @@ import seedAssets from "./seed_assets";
await seedKeamananLingkungan();
// // ====================== MENU EKONOMI ========================
+ // // ==================== SUBMENU UMKM ==========================
+ await seedUmkm();
+
// // ==================== SUBMENU PASAR DESA ====================
await seedPasarDesa();
diff --git a/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts b/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts
deleted file mode 100644
index 4d64fda8..00000000
--- a/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts
+++ /dev/null
@@ -1,563 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import ApiFetch from "@/lib/api-fetch";
-import { Prisma } from "@prisma/client";
-import { toast } from "react-toastify";
-import { proxy } from "valtio";
-import { z } from "zod";
-
-const templatePasarDesaForm = z.object({
- nama: z.string().min(1, "Nama minimal 1 karakter"),
- harga: z.number().min(1, "Harga minimal 1"),
- alamatUsaha: z.string().min(1, "Alamat minimal 1 karakter"),
- imageId: z.string().min(1, "Gambar wajib dipilih"),
- rating: z.number().min(1, "Rating minimal 1"),
- kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
- kontak: z.string().min(1, "Kontak wajib diisi"),
- deskripsi: z.string().min(1, "Deskripsi wajib diisi"),
-});
-
-const defaultPasarDesaForm = {
- nama: "",
- harga: 0,
- alamatUsaha: "",
- imageId: "",
- rating: 0,
- kategoriId: [] as string[],
- kontak: "",
- deskripsi: ""
-};
-
-const pasarDesa = proxy({
- create: {
- form: { ...defaultPasarDesaForm },
- loading: false,
- async create() {
- const cek = templatePasarDesaForm.safeParse(pasarDesa.create.form);
- if (!cek.success) {
- const err = `[${cek.error.issues
- .map((v) => `${v.path.join(".")}`)
- .join("\n")}] required`;
- return toast.error(err);
- }
- try {
- pasarDesa.create.loading = true;
- const res = await ApiFetch.api.ekonomi.pasardesa["create"].post(
- pasarDesa.create.form
- );
- if (res.status === 200) {
- pasarDesa.findMany.load();
- return toast.success("Data berhasil ditambahkan");
- }
- return toast.error("Gagal menambahkan data");
- } catch (error) {
- console.log(error);
- toast.error("Gagal menambahkan data");
- } finally {
- pasarDesa.create.loading = false;
- }
- },
- },
- findMany: {
- data: null as
- | Prisma.PasarDesaGetPayload<{
- include: {
- image: true;
- KategoriToPasar: {
- include: {
- kategori: true;
- };
- };
- };
- }>[]
- | null,
- page: 1,
- totalPages: 1,
- loading: false,
- search: "",
- load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
- pasarDesa.findMany.loading = true;
- pasarDesa.findMany.page = page;
- pasarDesa.findMany.search = search;
-
- try {
- const query: any = { page, limit };
- if (search) query.search = search;
- if (categoryId) query.categoryId = categoryId;
-
- const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
-
- if (res.status === 200 && res.data?.success) {
- pasarDesa.findMany.data = res.data.data ?? [];
- pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
- } else {
- pasarDesa.findMany.data = [];
- pasarDesa.findMany.totalPages = 1;
- }
- } catch (err) {
- console.error("Gagal fetch keamanan lingkungan paginated:", err);
- pasarDesa.findMany.data = [];
- pasarDesa.findMany.totalPages = 1;
- } finally {
- pasarDesa.findMany.loading = false;
- }
- },
- },
- findUnique: {
- data: null as Prisma.PasarDesaGetPayload<{
- include: {
- image: true;
- KategoriToPasar: {
- include: {
- kategori: true;
- };
- };
- };
- }> | null,
- async load(id: string) {
- try {
- const res = await fetch(`/api/ekonomi/pasardesa/${id}`);
- if (res.ok) {
- const data = await res.json();
- pasarDesa.findUnique.data = data.data ?? null;
- } else {
- console.error("Failed to fetch data", res.status, res.statusText);
- pasarDesa.findUnique.data = null;
- }
- } catch (error) {
- console.error("Error fetching data:", error);
- pasarDesa.findUnique.data = null;
- }
- },
- },
- delete: {
- loading: false,
- async byId(id: string) {
- if (!id) return toast.warn("ID tidak valid");
-
- try {
- pasarDesa.delete.loading = true;
-
- const response = await fetch(`/api/ekonomi/pasardesa/del/${id}`, {
- method: "DELETE",
- headers: {
- "Content-Type": "application/json",
- },
- });
-
- const result = await response.json();
-
- if (response.ok && result?.success) {
- toast.success(result.message || "Pasar desa berhasil dihapus");
- await pasarDesa.findMany.load(); // refresh list
- } else {
- toast.error(result?.message || "Gagal menghapus pasar desa");
- }
- } catch (error) {
- console.error("Gagal delete:", error);
- toast.error("Terjadi kesalahan saat menghapus pasar desa");
- } finally {
- pasarDesa.delete.loading = false;
- }
- },
- },
- edit: {
- id: "",
- form: { ...defaultPasarDesaForm },
- loading: false,
-
- async load(id: string) {
- if (!id) {
- toast.warn("ID tidak valid");
- return null;
- }
-
- try {
- const response = await fetch(`/api/ekonomi/pasardesa/${id}`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const result = await response.json();
- if (result?.success) {
- const data = result.data;
- this.id = data.id;
- this.form = {
- nama: data.nama,
- harga: data.harga,
- alamatUsaha: data.alamatUsaha,
- imageId: data.imageId,
- rating: data.rating,
- kategoriId: data.kategoriId,
- kontak: data.kontak,
- deskripsi: data.deskripsi
- };
- return data;
- } else {
- throw new Error(result?.message || "Gagal memuat data");
- }
- } catch (error) {
- console.error("Error loading pasar desa:", error);
- toast.error(
- error instanceof Error ? error.message : "Gagal memuat data"
- );
- return null;
- }
- },
-
- async update() {
- const cek = templatePasarDesaForm.safeParse(pasarDesa.edit.form);
- if (!cek.success) {
- const err = `[${cek.error.issues
- .map((v) => `${v.path.join(".")}`)
- .join("\n")}] required`;
- return toast.error(err);
- }
-
- try {
- pasarDesa.edit.loading = true;
- const response = await fetch(`/api/ekonomi/pasardesa/${this.id}`, {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- nama: this.form.nama,
- harga: this.form.harga,
- alamatUsaha: this.form.alamatUsaha,
- imageId: this.form.imageId,
- rating: this.form.rating,
- kategoriId: this.form.kategoriId,
- kontak: this.form.kontak,
- deskripsi: this.form.deskripsi
- }),
- });
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(
- errorData.message || `HTTP error! status: ${response.status}`
- );
- }
- const result = await response.json();
- if (result.success) {
- toast.success("Berhasil update pasar desa");
- await pasarDesa.findMany.load(); // refresh list
- return true;
- } else {
- throw new Error(result.message || "Gagal mengupdate pasar desa");
- }
- } catch (error) {
- console.error("Error updating pasar desa:", error);
- toast.error(
- error instanceof Error ? error.message : "Gagal mengupdate pasar desa"
- );
- return false;
- } finally {
- pasarDesa.edit.loading = false;
- }
- },
- reset() {
- pasarDesa.edit.id = "";
- pasarDesa.edit.form = { ...defaultPasarDesaForm };
- },
- },
-});
-
-// ========================================= KATEGORI PRODUK ========================================= //
-const kategoriProdukForm = z.object({
- nama: z.string().min(1, "Nama minimal 1 karakter"),
-});
-
-const kategoriProdukDefaultForm = {
- nama: "",
-};
-
-const kategoriProduk = proxy({
- create: {
- form: { ...kategoriProdukDefaultForm },
- loading: false,
- async create() {
- const cek = kategoriProdukForm.safeParse(kategoriProduk.create.form);
- if (!cek.success) {
- const err = `[${cek.error.issues
- .map((v) => `${v.path.join(".")}`)
- .join("\n")}] required`;
- return toast.error(err);
- }
- try {
- kategoriProduk.create.loading = true;
- const res = await ApiFetch.api.ekonomi.kategoriproduk["create"].post(
- kategoriProduk.create.form
- );
- if (res.status === 200) {
- kategoriProduk.findMany.load();
- return toast.success("Data berhasil ditambahkan");
- }
- return toast.error("Gagal menambahkan data");
- } catch (error) {
- console.log(error);
- toast.error("Gagal menambahkan data");
- } finally {
- kategoriProduk.create.loading = false;
- }
- },
- },
- findMany: {
- data: null as
- | Prisma.KategoriProdukGetPayload<{
- omit: {
- isActive: true;
- };
- }>[]
- | null,
- page: 1,
- totalPages: 1,
- loading: false,
- search: "",
- load: async (page = 1, limit = 10, search = "") => {
- kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
- kategoriProduk.findMany.page = page;
- kategoriProduk.findMany.search = search;
-
- try {
- const query: any = { page, limit };
- if (search) query.search = search;
-
- const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
-
- if (res.status === 200 && res.data?.success) {
- kategoriProduk.findMany.data = res.data.data ?? [];
- kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
- } else {
- kategoriProduk.findMany.data = [];
- kategoriProduk.findMany.totalPages = 1;
- }
- } catch (err) {
- console.error("Gagal fetch kategori produk paginated:", err);
- kategoriProduk.findMany.data = [];
- kategoriProduk.findMany.totalPages = 1;
- } finally {
- kategoriProduk.findMany.loading = false;
- }
- },
- },
- // ✅ Versi findManyAll (ambil semua tanpa pagination)
- findManyAll: {
- data: null as
- | Prisma.KategoriProdukGetPayload<{
- omit: { isActive: true };
- }>[]
- | null,
- loading: false,
- search: "",
- load: async (search = "") => {
- kategoriProduk.findManyAll.loading = true;
- kategoriProduk.findManyAll.search = search;
-
- try {
- const query: any = {};
- if (search) query.search = search;
-
- const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many-all"].get({
- query,
- });
-
- if (res.status === 200 && res.data?.success) {
- kategoriProduk.findManyAll.data = res.data.data ?? [];
- } else {
- kategoriProduk.findManyAll.data = [];
- }
- } catch (err) {
- console.error("Gagal fetch kategori produk (all):", err);
- kategoriProduk.findManyAll.data = [];
- } finally {
- kategoriProduk.findManyAll.loading = false;
- }
- },
- },
- findUnique: {
- data: null as Prisma.KategoriProdukGetPayload<{
- omit: { isActive: true };
- }> | null,
- async load(id: string) {
- try {
- const res = await fetch(`/api/ekonomi/kategoriproduk/${id}`);
- if (res.ok) {
- const data = await res.json();
- kategoriProduk.findUnique.data = data.data ?? null;
- } else {
- console.error("Failed to fetch data", res.status, res.statusText);
- kategoriProduk.findUnique.data = null;
- }
- } catch (error) {
- console.error("Error fetching data:", error);
- kategoriProduk.findUnique.data = null;
- }
- },
- },
- delete: {
- loading: false,
- async byId(id: string) {
- if (!id) return toast.warn("ID tidak valid");
-
- try {
- kategoriProduk.delete.loading = true;
-
- const response = await fetch(`/api/ekonomi/kategoriproduk/del/${id}`, {
- method: "DELETE",
- headers: {
- "Content-Type": "application/json",
- },
- });
-
- const result = await response.json();
-
- if (response.ok && result?.success) {
- toast.success(result.message || "Kategori produk berhasil dihapus");
- await kategoriProduk.findMany.load(); // refresh list
- } else {
- toast.error(result?.message || "Gagal menghapus kategori produk");
- }
- } catch (error) {
- console.error("Gagal delete:", error);
- toast.error("Terjadi kesalahan saat menghapus kategori produk");
- } finally {
- kategoriProduk.delete.loading = false;
- }
- },
- },
- edit: {
- id: "",
- form: { ...kategoriProdukDefaultForm },
- loading: false,
-
- async load(id: string) {
- if (!id) {
- toast.warn("ID tidak valid");
- return null;
- }
-
- try {
- const response = await fetch(`/api/ekonomi/kategoriproduk/${id}`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const result = await response.json();
- if (result?.success) {
- const data = result.data;
- this.id = data.id;
- this.form = {
- nama: data.nama,
- };
- return data;
- } else {
- throw new Error(result?.message || "Gagal memuat data");
- }
- } catch (error) {
- console.error("Error loading kategori produk:", error);
- toast.error(
- error instanceof Error ? error.message : "Gagal memuat data"
- );
- return null;
- }
- },
-
- async update() {
- const cek = kategoriProdukForm.safeParse(kategoriProduk.edit.form);
- if (!cek.success) {
- const err = `[${cek.error.issues
- .map((v) => `${v.path.join(".")}`)
- .join("\n")}] required`;
- toast.error(err);
- return false;
- }
-
- try {
- kategoriProduk.edit.loading = true;
- const response = await fetch(
- `/api/ekonomi/kategoriproduk/${kategoriProduk.edit.id}`,
- {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- nama: kategoriProduk.edit.form.nama,
- }),
- }
- );
-
- // Clone the response to avoid 'body already read' error
- const responseClone = response.clone();
-
- try {
- const result = await response.json();
-
- if (!response.ok) {
- console.error(
- "Update failed with status:",
- response.status,
- "Response:",
- result
- );
- throw new Error(
- result?.message ||
- `Gagal mengupdate kategori produk (${response.status})`
- );
- }
-
- if (result.success) {
- toast.success(
- result.message || "Berhasil memperbarui kategori produk"
- );
- await kategoriProduk.findMany.load(); // refresh list
- return true;
- } else {
- throw new Error(
- result.message || "Gagal mengupdate kategori produk"
- );
- }
- } catch (error) {
- // If JSON parsing fails, try to get the response text for better error messages
- try {
- const text = await responseClone.text();
- console.error("Error response text:", text);
- throw new Error(`Gagal memproses respons dari server: ${text}`);
- } catch (textError) {
- console.error("Error parsing response as text:", textError);
- console.error("Original error:", error);
- throw new Error("Gagal memproses respons dari server");
- }
- }
- } catch (error) {
- console.error("Error updating kategori produk:", error);
- toast.error(
- error instanceof Error
- ? error.message
- : "Gagal mengupdate kategori produk"
- );
- return false;
- } finally {
- kategoriProduk.edit.loading = false;
- }
- },
- reset() {
- kategoriProduk.edit.id = "";
- kategoriProduk.edit.form = { ...kategoriProdukDefaultForm };
- },
- },
-});
-
-const pasarDesaState = proxy({
- pasarDesa,
- kategoriProduk,
-});
-export default pasarDesaState;
diff --git a/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts b/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts
index 584920d7..349308cf 100644
--- a/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts
+++ b/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts
@@ -167,6 +167,20 @@ export const umkmState = proxy({
} catch (e) { console.error(e); } finally { this.loading = false; }
}
},
+ findUnique: {
+ data: null as any,
+ loading: false,
+ async load(id: string) {
+ this.loading = true;
+ try {
+ const res = await fetch(`/api/ekonomi/umkm/produk/${id}`);
+ const result = await res.json();
+ if (result.success) {
+ this.data = result.data;
+ }
+ } catch (e) { console.error(e); } finally { this.loading = false; }
+ }
+ },
create: {
form: { ...defaultProdukForm },
loading: false,
diff --git a/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts b/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts
index d966a861..3080ff9b 100644
--- a/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts
+++ b/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts
@@ -155,11 +155,11 @@ const kegiatanDesa = proxy({
toast.success(result.message || "kegiatan desa berhasil dihapus");
await kegiatanDesa.findMany.load(); // refresh list
} else {
- toast.error(result?.message || "Gagal menghapus pasar desa");
+ toast.error(result?.message || "Gagal menghapus gotong royong");
}
} catch (error) {
console.error("Gagal delete:", error);
- toast.error("Terjadi kesalahan saat menghapus pasar desa");
+ toast.error("Terjadi kesalahan saat menghapus gotong royong");
} finally {
kegiatanDesa.delete.loading = false;
}
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/_lib/layoutTabs.tsx
deleted file mode 100644
index e82db659..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/_lib/layoutTabs.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-/* eslint-disable react-hooks/exhaustive-deps */
-'use client'
-import colors from '@/con/colors';
-import {
- Box,
- ScrollArea,
- Stack,
- Tabs,
- TabsList,
- TabsPanel,
- TabsTab,
- Title
-} from '@mantine/core';
-import { IconCategory, IconShoppingBag } from '@tabler/icons-react';
-import { usePathname, useRouter } from 'next/navigation';
-import React, { useEffect, useState } from 'react';
-
-function LayoutTabs({ children }: { children: React.ReactNode }) {
- const router = useRouter();
- const pathname = usePathname();
-
- const tabs = [
- {
- label: "Produk Pasar Desa",
- value: "produkpasardesa",
- href: "/admin/ekonomi/pasar-desa/produk-pasar-desa",
- icon:
- },
- {
- label: "Kategori Produk",
- value: "kategoriproduk",
- href: "/admin/ekonomi/pasar-desa/kategori-produk",
- icon:
- },
- ];
-
- const currentTab = tabs.find((tab) => tab.href === pathname);
- const [activeTab, setActiveTab] = useState(
- currentTab?.value || tabs[0].value
- );
-
- const handleTabChange = (value: string | null) => {
- const tab = tabs.find((t) => t.value === value);
- if (tab) {
- router.push(tab.href);
- }
- setActiveTab(value);
- };
-
- useEffect(() => {
- const match = tabs.find((tab) => tab.href === pathname);
- if (match) {
- setActiveTab(match.value);
- }
- }, [pathname]);
-
- return (
-
-
- Pasar Desa
-
-
-
- {/* ✅ Scroll horizontal wrapper */}
-
-
-
- {tabs.map((tab, i) => (
-
- {tab.label}
-
- ))}
-
-
-
-
-
-
-
-
- {tabs.map((tab, i) => (
-
- {tab.label}
-
- ))}
-
-
-
-
- {tabs.map((tab, i) => (
-
- {children}
-
- ))}
-
-
- );
-}
-
-export default LayoutTabs;
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/[id]/page.tsx
deleted file mode 100644
index c180ad4a..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/[id]/page.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-/* eslint-disable react-hooks/exhaustive-deps */
-'use client'
-import colors from '@/con/colors';
-import {
- Box,
- Button,
- Group,
- Loader,
- Paper,
- Stack,
- Text,
- TextInput,
- Title
-} from '@mantine/core';
-import { IconArrowBack } from '@tabler/icons-react';
-import { useParams, useRouter } from 'next/navigation';
-import React, { useEffect, useState } from 'react';
-import { toast } from 'react-toastify';
-import { useProxy } from 'valtio/utils';
-import pasarDesaState from '../../../../_state/ekonomi/pasar-desa/pasar-desa';
-
-function EditKategoriProduk() {
- const router = useRouter();
- const params = useParams();
- const id = params?.id as string;
- const statePasar = useProxy(pasarDesaState.kategoriProduk);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [formData, setFormData] = useState({ nama: '' });
- const [originalData, setOriginalData] = useState({ nama: '' });
-
- // Check if form is valid
- const isFormValid = () => {
- return formData.nama?.trim() !== '';
- };
-
- useEffect(() => {
- const loadKategoriProduk = async () => {
- if (!id) return;
-
- try {
- const data = await statePasar.edit.load(id);
-
- if (data) {
- // simpan id ke state global hanya untuk referensi
- statePasar.edit.id = id;
-
- // simpan data ke state lokal
- setFormData({ nama: data.nama || '' });
- setOriginalData({ nama: data.nama || '' });
- }
- } catch (error) {
- console.error('Error loading kategori produk:', error);
- toast.error('Gagal memuat data kategori produk');
- }
- };
-
- loadKategoriProduk();
- }, [id]);
-
- const handleChange = (e: React.ChangeEvent) => {
- setFormData((prev) => ({
- ...prev,
- [e.target.name]: e.target.value,
- }));
- };
-
- const handleResetForm = () => {
- setFormData({
- nama: originalData.nama,
- });
- toast.info('Form dikembalikan ke data awal');
- };
-
- const handleSubmit = async () => {
- try {
- setIsSubmitting(true);
- if (!formData.nama.trim()) {
- toast.error('Nama kategori produk tidak boleh kosong');
- return;
- }
-
- // update global state hanya saat submit
- statePasar.edit.form = { nama: formData.nama.trim() };
- if (!statePasar.edit.id) {
- statePasar.edit.id = id; // fallback
- }
-
- const success = await statePasar.edit.update();
-
- if (success) {
- toast.success('Kategori produk berhasil diperbarui!');
- router.push('/admin/ekonomi/pasar-desa/kategori-produk');
- }
- } catch (error) {
- console.error('Error updating kategori produk:', error);
- toast.error('Terjadi kesalahan saat memperbarui kategori produk');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- {/* Header dengan tombol back */}
-
-
-
- Edit Kategori Produk
-
-
-
- {/* Card form */}
-
-
- Nama Kategori Produk}
- placeholder="Masukkan nama kategori produk"
- value={formData.nama}
- onChange={handleChange}
- required
- />
-
-
-
-
- {/* Tombol Simpan */}
-
-
-
-
-
- );
-}
-
-export default EditKategoriProduk;
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/create/page.tsx
deleted file mode 100644
index 5873d9ae..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/create/page.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/* eslint-disable react-hooks/exhaustive-deps */
-'use client';
-import colors from '@/con/colors';
-import {
- Box,
- Button,
- Group,
- Loader,
- Paper,
- Stack,
- TextInput,
- Title
-} from '@mantine/core';
-import { IconArrowBack } from '@tabler/icons-react';
-import { useRouter } from 'next/navigation';
-import { useEffect, useState } from 'react';
-import { toast } from 'react-toastify';
-import { useProxy } from 'valtio/utils';
-import pasarDesaState from '../../../../_state/ekonomi/pasar-desa/pasar-desa';
-
-function CreateKategoriProduk() {
- const router = useRouter();
- const statePasar = useProxy(pasarDesaState.kategoriProduk);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- // Check if form is valid
- const isFormValid = () => {
- return statePasar.create.form.nama?.trim() !== '';
- };
-
- useEffect(() => {
- statePasar.findMany.load();
- }, []);
-
- const resetForm = () => {
- statePasar.create.form = {
- nama: '',
- };
- };
-
- const handleSubmit = async () => {
- try {
- if (!statePasar.create.form.nama) {
- return toast.warn('Nama kategori produk wajib diisi');
- }
- setIsSubmitting(true);
- await statePasar.create.create();
- resetForm();
- router.push('/admin/ekonomi/pasar-desa/kategori-produk');
- } catch (error) {
- console.error(error)
- toast.error('Gagal menambahkan kategori produk');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- {/* Header dengan tombol kembali */}
-
-
-
- Tambah Kategori Produk
-
-
-
- {/* Card form */}
-
-
- (statePasar.create.form.nama = e.target.value)}
- required
- />
-
-
-
-
- {/* Tombol Simpan */}
-
-
-
-
-
- );
-}
-
-export default CreateKategoriProduk;
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/page.tsx
deleted file mode 100644
index 2f1a972a..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/page.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-'use client'
-import colors from '@/con/colors';
-import {
- Box,
- Button,
- Center,
- Group,
- Pagination,
- Paper,
- Skeleton,
- Stack,
- Table,
- TableTbody,
- TableTd,
- TableTh,
- TableThead,
- TableTr,
- Text,
- Title,
-} from '@mantine/core';
-import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
-import { IconEdit, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
-import { useRouter } from 'next/navigation';
-import { useState } from 'react';
-import { useProxy } from 'valtio/utils';
-import HeaderSearch from '../../../_com/header';
-import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
-import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
-
-function KategoriProduk() {
- const [search, setSearch] = useState('');
- return (
-
- }
- value={search}
- onChange={(e) => setSearch(e.currentTarget.value)}
- />
-
-
- );
-}
-
-function ListKategoriProduk({ search }: { search: string }) {
- const statePasar = useProxy(pasarDesaState.kategoriProduk);
- const [modalHapus, setModalHapus] = useState(false);
- const [selectedId, setSelectedId] = useState(null);
- const router = useRouter();
- const [debouncedSearch] = useDebouncedValue(search, 1000);
-
- const { data, page, totalPages, loading, load } = statePasar.findMany;
-
- useShallowEffect(() => {
- load(page, 10, debouncedSearch);
- }, [page, debouncedSearch]);
-
- const handleHapus = () => {
- if (selectedId) {
- statePasar.delete.byId(selectedId);
- setModalHapus(false);
- setSelectedId(null);
- }
- };
-
- const filteredData = data || [];
-
- if (loading || !data) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
-
- Daftar Kategori Produk
-
- }
- color="blue"
- variant="light"
- onClick={() =>
- router.push('/admin/ekonomi/pasar-desa/kategori-produk/create')
- }
- >
- Tambah Baru
-
-
-
- {/* Desktop Table */}
-
-
-
-
-
-
- Nama Kategori
-
-
-
-
- Edit
-
-
-
-
- Delete
-
-
-
-
-
- {filteredData.length > 0 ? (
- filteredData.map((item) => (
-
-
-
- {item.nama}
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))
- ) : (
-
-
-
-
- Tidak ada data kategori produk yang cocok
-
-
-
-
- )}
-
-
-
-
- {/* Mobile Card */}
-
- {filteredData.length > 0 ? (
-
- {filteredData.map((item) => (
-
-
-
- Nama Kategori
-
-
- {item.nama}
-
-
-
-
-
-
-
- ))}
-
- ) : (
-
-
- Tidak ada data kategori produk yang cocok
-
-
- )}
-
-
-
-
- {
- load(newPage, 10);
- window.scrollTo({ top: 0, behavior: 'smooth' });
- }}
- total={totalPages}
- mt="md"
- mb="md"
- color="blue"
- radius="md"
- />
-
-
- {/* Modal Konfirmasi Hapus */}
- setModalHapus(false)}
- onConfirm={handleHapus}
- text='Apakah anda yakin ingin menghapus kategori produk ini?'
- />
-
- );
-}
-
-export default KategoriProduk;
\ No newline at end of file
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/layout.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/layout.tsx
deleted file mode 100644
index db66e722..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/layout.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-'use client'
-
-import { usePathname } from "next/navigation";
-import LayoutTabs from "./_lib/layoutTabs"
-import { Box } from "@mantine/core";
-
-
-export default function Layout({ children }: { children: React.ReactNode }) {
- const pathname = usePathname();
-
- // Contoh path:
- // - /darmasaba/desa/berita/semua → panjang 5 → list
- // - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
- // - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
-
- const segments = pathname.split('/').filter(Boolean);
- const isDetailPage = segments.length >= 5;
-
- if (isDetailPage) {
- // Tampilkan tanpa tab menu
- return (
-
- {children}
-
- );
- }
- return (
-
- {children}
-
- )
-}
\ No newline at end of file
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/[id]/edit/page.tsx
deleted file mode 100644
index 0fc729ac..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/[id]/edit/page.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-/* eslint-disable react-hooks/exhaustive-deps */
-/* eslint-disable @typescript-eslint/no-explicit-any */
-'use client';
-import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
-import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
-import colors from '@/con/colors';
-import ApiFetch from '@/lib/api-fetch';
-import {
- ActionIcon,
- Box,
- Button,
- Group,
- Image,
- Loader,
- MultiSelect,
- Paper,
- Stack,
- Text,
- TextInput,
- Title
-} from '@mantine/core';
-import { Dropzone } 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';
-
-type FormData = {
- nama: string;
- harga: number;
- alamatUsaha: string;
- imageId: string;
- rating: number;
- kategoriId: string[];
- kontak: string;
- deskripsi: string;
-};
-
-function EditPasarDesa() {
- const pasarState = useProxy(pasarDesaState);
- const router = useRouter();
- const params = useParams();
-
- const [previewImage, setPreviewImage] = useState(null);
- const [file, setFile] = useState(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [formData, setFormData] = useState({
- nama: '',
- harga: 0,
- alamatUsaha: '',
- imageId: '',
- rating: 0,
- kategoriId: [],
- kontak: '',
- deskripsi: ''
- });
-
- const [originalData, setOriginalData] = useState({
- nama: '',
- harga: 0,
- alamatUsaha: '',
- imageId: '',
- imageUrl: "",
- rating: 0,
- kategoriId: [],
- kontak: '',
- deskripsi: ''
- });
-
- // Helper function to check if HTML content is empty
- const isHtmlEmpty = (html: string) => {
- // Remove all HTML tags and check if there's any text content
- const textContent = html.replace(/<[^>]*>/g, '').trim();
- return textContent === '';
- };
-
- // Check if form is valid
- const isFormValid = () => {
- return (
- formData.nama?.trim() !== '' &&
- formData.harga !== null &&
- formData.harga > 0 &&
- !isHtmlEmpty(formData.deskripsi)
- );
- };
-
- // load data awal
- useEffect(() => {
- pasarState.kategoriProduk.findManyAll.load();
-
- const loadPasarDesa = async () => {
- const id = params?.id as string;
- if (!id) return;
-
- try {
- const data = await pasarState.pasarDesa.edit.load(id);
- if (data) {
- setFormData({
- nama: data.nama || '',
- harga: data.harga || 0,
- alamatUsaha: data.alamatUsaha || '',
- imageId: data.imageId || '',
- rating: data.rating || 0,
- kategoriId: data.KategoriToPasar?.map((k: any) => k.kategoriId) || [],
- kontak: data.kontak || '',
- deskripsi: data.deskripsi || ''
- });
- setOriginalData({
- nama: data.nama || '',
- harga: data.harga || 0,
- alamatUsaha: data.alamatUsaha || '',
- imageId: data.imageId || '',
- imageUrl: data.image?.link || "",
- rating: data.rating || 0,
- kategoriId: data.KategoriToPasar?.map((k: any) => k.kategoriId) || [],
- kontak: data.kontak || '',
- deskripsi: data.deskripsi || ''
- });
- if (data.image?.link) setPreviewImage(data.image.link);
- }
- } catch (error) {
- console.error('Error loading pasar desa:', error);
- toast.error(
- error instanceof Error ? error.message : 'Gagal mengambil data pasar desa'
- );
- }
- };
-
- loadPasarDesa();
- }, [params?.id]);
-
- const handleChange = (key: keyof FormData, value: any) => {
- setFormData((prev) => ({ ...prev, [key]: value }));
- };
-
- const handleResetForm = () => {
- setFormData({
- nama: originalData.nama,
- harga: originalData.harga,
- alamatUsaha: originalData.alamatUsaha,
- imageId: originalData.imageId,
- rating: originalData.rating,
- kategoriId: (originalData as any)?.KategoriToPasar?.map((k: any) => k.kategoriId) || [],
- kontak: originalData.kontak,
- deskripsi: originalData.deskripsi
- });
- setPreviewImage(originalData.imageUrl || null);
- setFile(null);
- toast.info("Form dikembalikan ke data awal");
- };
-
-
- const handleSubmit = async () => {
- try {
- setIsSubmitting(true);
- // upload image kalau ada file baru
- let imageId = formData.imageId;
- if (file) {
- const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
- const uploaded = res.data?.data;
- if (!uploaded?.id) return toast.error('Gagal upload gambar');
- imageId = uploaded.id;
- }
-
- // update global state hanya saat submit
- pasarState.pasarDesa.edit.form = {
- ...formData,
- imageId,
- };
-
- await pasarState.pasarDesa.edit.update();
- toast.success('Pasar desa berhasil diperbarui!');
- router.push('/admin/ekonomi/pasar-desa/produk-pasar-desa');
- } catch (error) {
- console.error('Error updating pasar desa:', error);
- toast.error('Terjadi kesalahan saat memperbarui pasar desa');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
-
-
-
- Edit Pasar Desa
-
-
-
-
-
- {/* Dropzone upload */}
-
-
- Gambar 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/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
- radius="md"
- p="xl"
- >
-
-
-
-
-
-
-
-
-
-
-
-
- Seret gambar atau klik untuk memilih file
-
-
- Maksimal 5MB, format gambar .png, .jpg, .jpeg, webp
-
-
-
-
-
- {previewImage && (
-
-
-
- {/* Tombol hapus (pojok kanan atas) */}
- {
- setPreviewImage(null);
- setFile(null);
- }}
- style={{
- boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
- }}
- >
-
-
-
- )}
-
-
- {/* Controlled Inputs */}
- handleChange('nama', e.target.value)}
- required
- />
-
- handleChange('harga', Number(e.target.value))}
- required
- />
-
- handleChange('rating', Number(e.target.value))}
- required
- />
-
- handleChange('alamatUsaha', e.target.value)}
- required
- />
-
- handleChange('kontak', e.target.value)}
- required
- />
-
- handleChange('kategoriId', val)}
- data={
- pasarState.kategoriProduk.findManyAll.data?.map((v) => ({
- value: v.id,
- label: v.nama,
- })) || []
- }
- clearable
- searchable
- required
- error={!formData.kategoriId.length ? 'Pilih minimal satu kategori' : undefined}
- />
-
- {/* Input Deskripsi */}
-
-
- Deskripsi
-
-
- setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
- }
- />
-
-
-
-
-
- {/* Tombol Simpan */}
-
-
-
-
-
- );
-}
-
-export default EditPasarDesa;
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/[id]/page.tsx
deleted file mode 100644
index d31ce111..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/[id]/page.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-'use client'
-import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
-import colors from '@/con/colors';
-import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
-import { useShallowEffect } from '@mantine/hooks';
-import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
-import { useParams, useRouter } from 'next/navigation';
-import { useState } from 'react';
-import { useProxy } from 'valtio/utils';
-import pasarDesaState from '../../../../_state/ekonomi/pasar-desa/pasar-desa';
-
-function DetailPasarDesa() {
- const statePasar = useProxy(pasarDesaState);
- const [modalHapus, setModalHapus] = useState(false);
- const [selectedId, setSelectedId] = useState(null);
- const params = useParams();
- const router = useRouter();
-
- useShallowEffect(() => {
- statePasar.pasarDesa.findUnique.load(params?.id as string);
- }, []);
-
- const handleHapus = () => {
- if (selectedId) {
- statePasar.pasarDesa.delete.byId(selectedId);
- setModalHapus(false);
- setSelectedId(null);
- router.push("/admin/ekonomi/pasar-desa/produk-pasar-desa");
- }
- };
-
- if (!statePasar.pasarDesa.findUnique.data) {
- return (
-
-
-
- );
- }
-
- const data = statePasar.pasarDesa.findUnique.data;
-
- return (
-
-
-
-
-
-
- Detail Pasar Desa
-
-
-
-
-
- Nama Produk
- {data.nama || '-'}
-
-
-
- Harga Produk
- Rp. {data.harga || '-'}
-
-
-
- Rating Produk
- {data.rating || '-'}
-
-
-
- Alamat Usaha
- {data.alamatUsaha || '-'}
-
-
-
- Kontak
- {data.kontak || '-'}
-
-
-
- Gambar
- {data.image?.link ? (
-
- ) : (
- Tidak ada gambar
- )}
-
-
-
- Kategori
-
- {data.KategoriToPasar && data.KategoriToPasar.length > 0 ? (
- data.KategoriToPasar.map((kategori) => (
-
- • {kategori.kategori.nama}
-
- ))
- ) : (
- Tidak ada kategori
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- setModalHapus(false)}
- onConfirm={handleHapus}
- text="Apakah Anda yakin ingin menghapus produk ini?"
- />
-
- );
-}
-
-export default DetailPasarDesa;
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/create/page.tsx
deleted file mode 100644
index 0fc7fc03..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/create/page.tsx
+++ /dev/null
@@ -1,302 +0,0 @@
-/* eslint-disable react-hooks/exhaustive-deps */
-'use client';
-import colors from '@/con/colors';
-import ApiFetch from '@/lib/api-fetch';
-import {
- ActionIcon,
- Box,
- Button,
- Group,
- Image,
- Loader,
- MultiSelect,
- Paper,
- Stack,
- Text,
- TextInput,
- Title
-} from '@mantine/core';
-import { Dropzone } from '@mantine/dropzone';
-import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
-import { useRouter } from 'next/navigation';
-import { useEffect, useState } from 'react';
-import { toast } from 'react-toastify';
-import { useProxy } from 'valtio/utils';
-import pasarDesaState from '../../../../_state/ekonomi/pasar-desa/pasar-desa';
-import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
-
-export default function CreatePasarDesa() {
- const router = useRouter();
- const statePasar = useProxy(pasarDesaState);
- const [previewImage, setPreviewImage] = useState(null);
- const [file, setFile] = useState(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- // Helper function to check if HTML content is empty
- const isHtmlEmpty = (html: string) => {
- // Remove all HTML tags and check if there's any text content
- const textContent = html.replace(/<[^>]*>/g, '').trim();
- return textContent === '';
- };
-
- // Check if form is valid
- const isFormValid = () => {
- return (
- statePasar.pasarDesa.create.form.nama?.trim() !== '' &&
- statePasar.pasarDesa.create.form.harga !== null &&
- statePasar.pasarDesa.create.form.harga > 0 &&
- !isHtmlEmpty(statePasar.pasarDesa.create.form.deskripsi) &&
- file !== null
- );
- };
-
- useEffect(() => {
- statePasar.kategoriProduk.findManyAll.load();
- }, []);
-
- const resetForm = () => {
- statePasar.pasarDesa.create.form = {
- nama: '',
- harga: 0,
- alamatUsaha: '',
- imageId: '',
- rating: 0,
- kategoriId: [],
- kontak: '',
- deskripsi: ''
- };
- setPreviewImage(null);
- setFile(null);
- };
-
- const handleSubmit = async () => {
- try {
- setIsSubmitting(true);
- if (!file) {
- return toast.warn('Silakan pilih file gambar terlebih dahulu');
- }
-
- const res = await ApiFetch.api.fileStorage.create.post({
- file,
- name: file.name,
- });
-
- const uploaded = res.data?.data;
- if (!uploaded?.id) {
- return toast.error('Gagal mengunggah gambar, silakan coba lagi');
- }
-
- statePasar.pasarDesa.create.form.imageId = uploaded.id;
- await statePasar.pasarDesa.create.create();
-
- resetForm();
- router.push('/admin/ekonomi/pasar-desa/produk-pasar-desa');
- } catch (error) {
- console.error('Error creating kategori produk:', error);
- toast.error('Gagal membuat kategori produk');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- {/* Header dengan tombol kembali */}
-
-
-
- Tambah Produk Pasar Desa
-
-
-
- {/* Card Form */}
-
-
- {/* Upload Gambar */}
-
-
- Gambar 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/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
- radius="md"
- p="xl"
- >
-
-
-
-
-
-
-
-
-
-
-
-
- Seret gambar atau klik untuk memilih file (maks 5MB)
-
-
-
- {previewImage && (
-
-
- {
- setPreviewImage(null);
- setFile(null);
- }}
- style={{
- boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
- }}
- >
-
-
-
- )}
-
-
- {/* Nama Produk */}
- (statePasar.pasarDesa.create.form.nama = e.target.value)}
- required
- />
-
- {/* Harga Produk */}
- (statePasar.pasarDesa.create.form.harga = Number(e.target.value))}
- required
- />
-
- {/* Rating */}
- {
- const value = Number(e.target.value);
- if (value >= 0 && value <= 5) {
- statePasar.pasarDesa.create.form.rating = value;
- }
- }}
- />
-
- {/* Alamat Usaha */}
- (statePasar.pasarDesa.create.form.alamatUsaha = e.target.value)}
- />
-
- {/* Kontak */}
- (statePasar.pasarDesa.create.form.kontak = e.target.value)}
- />
-
- {/* Kategori Produk */}
- (statePasar.pasarDesa.create.form.kategoriId = val)}
- data={
- statePasar.kategoriProduk.findManyAll.data?.map((v) => ({
- value: v.id,
- label: v.nama,
- })) || []
- }
- />
-
-
-
- Deskripsi Produk
-
- {
- statePasar.pasarDesa.create.form.deskripsi = val;
- }}
- />
-
-
- {/* Tombol Submit */}
-
-
-
- {/* Tombol Simpan */}
-
-
-
-
-
- );
-}
diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/page.tsx
deleted file mode 100644
index 8a83c0c8..00000000
--- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/produk-pasar-desa/page.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-'use client'
-import colors from '@/con/colors';
-import {
- Box,
- Button,
- Center,
- Group,
- Pagination,
- Paper,
- Skeleton,
- Stack,
- Table,
- TableTbody,
- TableTd,
- TableTh,
- TableThead,
- TableTr,
- Text,
- Title
-} from '@mantine/core';
-import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
-import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
-import { useRouter } from 'next/navigation';
-import { useState } from 'react';
-import { useProxy } from 'valtio/utils';
-import HeaderSearch from '../../../_com/header';
-import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
-
-function PasarDesa() {
- const [search, setSearch] = useState("");
- return (
-
- }
- value={search}
- onChange={(e) => setSearch(e.currentTarget.value)}
- />
-
-
- );
-}
-
-function ListPasarDesa({ search }: { search: string }) {
- const statePasar = useProxy(pasarDesaState.pasarDesa);
- const router = useRouter();
- const [debouncedSearch] = useDebouncedValue(search, 1000);
-
- const { data, page, totalPages, loading, load } = statePasar.findMany;
-
- useShallowEffect(() => {
- load(page, 10, debouncedSearch);
- }, [page, debouncedSearch]);
-
- const filteredData = data || [];
-
- if (loading || !data) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- Daftar Produk Pasar Desa
- }
- color="blue"
- variant="light"
- onClick={() =>
- router.push('/admin/ekonomi/pasar-desa/produk-pasar-desa/create')
- }
- >
- Tambah Baru
-
-
-
- {/* Desktop Table */}
-
-
-
-
- Nama Produk
- Harga Produk
- Rating
- Alamat Usaha
- Aksi
-
-
-
- {filteredData.length > 0 ? (
- filteredData.map((item) => (
-
-
-
- {item.nama}
-
-
-
- Rp.{item.harga}
-
-
- {item.rating || '-'}
-
-
-
- {item.alamatUsaha || '-'}
-
-
-
-
-
-
- ))
- ) : (
-
-
-
-
- Tidak ada produk pasar desa yang cocok
-
-
-
-
- )}
-
-
-
-
- {/* Mobile Cards */}
-
- {filteredData.length > 0 ? (
- filteredData.map((item) => (
-
-
-
- Nama Produk
- {item.nama}
-
-
- Harga Produk
- Rp.{item.harga}
-
-
- Rating
- {item.rating || '-'}
-
-
- Alamat Usaha
-
- {item.alamatUsaha || '-'}
-
-
-
-
-
-
-
- ))
- ) : (
-
-
- Tidak ada produk pasar desa yang cocok
-
-
- )}
-
-
-
-
- {
- load(newPage, 10);
- window.scrollTo({ top: 0, behavior: 'smooth' });
- }}
- total={totalPages}
- mt="md"
- mb="md"
- color="blue"
- radius="md"
- />
-
-
- );
-}
-
-export default PasarDesa;
\ No newline at end of file
diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx
index 88779c9e..75e4acd4 100644
--- a/src/app/admin/_com/list_PageAdmin.tsx
+++ b/src/app/admin/_com/list_PageAdmin.tsx
@@ -208,29 +208,9 @@ export const devBar = [
children: [
{
id: "Ekonomi_UMKM_1",
- name: "UMKM - Dashboard",
+ name: "UMKM",
path: "/admin/ekonomi/umkm/dashboard"
},
- {
- id: "Ekonomi_UMKM_2",
- name: "UMKM - Data UMKM",
- path: "/admin/ekonomi/umkm/data-umkm"
- },
- {
- id: "Ekonomi_UMKM_3",
- name: "UMKM - Produk",
- path: "/admin/ekonomi/umkm/produk"
- },
- {
- id: "Ekonomi_UMKM_4",
- name: "UMKM - Penjualan",
- path: "/admin/ekonomi/umkm/penjualan"
- },
- {
- id: "Ekonomi_1",
- name: "Pasar Desa",
- path: "/admin/ekonomi/pasar-desa/produk-pasar-desa"
- },
{
id: "Ekonomi_2",
name: "Lowongan Kerja Lokal",
@@ -677,11 +657,6 @@ export const navBar = [
name: "UMKM - Penjualan",
path: "/admin/ekonomi/umkm/penjualan"
},
- {
- id: "Ekonomi_1",
- name: "Pasar Desa",
- path: "/admin/ekonomi/pasar-desa/produk-pasar-desa"
- },
{
id: "Ekonomi_2",
name: "Lowongan Kerja Lokal",
@@ -1086,11 +1061,6 @@ export const role1 = [
name: "UMKM - Penjualan",
path: "/admin/ekonomi/umkm/penjualan"
},
- {
- id: "Ekonomi_1",
- name: "Pasar Desa",
- path: "/admin/ekonomi/pasar-desa/produk-pasar-desa"
- },
{
id: "Ekonomi_2",
name: "Lowongan Kerja Lokal",
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts
index 39481558..dbb9c22c 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts
@@ -1,8 +1,6 @@
import Elysia from "elysia";
-import PasarDesa from "./pasar-desa";
import LowonganKerja from "./lowongan-kerja";
import ProgramKemiskinan from "./program-kemiskinan";
-import KategoriProduk from "./pasar-desa/kategori-produk";
import GrafikUsiaKerjaYangMenganggur from "./usia-kerja-yang-menganggur";
import GrafikMenganggurBerdasarkanPendidikan from "./usia-kerja-yang-menganggur/pengangguran-berdasrkan-pendidikan";
import JumlahPendudukMiskin from "./jumlah-penduduk-miskin";
@@ -20,8 +18,6 @@ const Ekonomi = new Elysia({
prefix: "/ekonomi",
tags: ["Ekonomi"],
})
-.use(PasarDesa)
-.use(KategoriProduk)
.use(LowonganKerja)
.use(ProgramKemiskinan)
.use(StrukturOrganisasi)
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/create.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/create.ts
deleted file mode 100644
index 62ad27b1..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/create.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-type FormCreate = {
- nama: string;
- harga: number;
- alamatUsaha: string;
- imageId: string;
- rating: number;
- kategoriId: string[];
- kontak: string;
- deskripsi: string;
- // Array of KategoriProduk IDs
-};
-
-export default async function pasarDesaCreate(context: Context) {
- const body = context.body as FormCreate;
-
- if (!body.kategoriId || body.kategoriId.length === 0) {
- throw new Error("At least one kategoriId is required");
- }
-
- try {
- // Start a transaction to ensure data consistency
- const result = await prisma.$transaction(async (prisma) => {
- // 1. Create PasarDesa with the first kategoriId as the main category
- const pasarDesa = await prisma.pasarDesa.create({
- data: {
- nama: body.nama,
- harga: Number(body.harga),
- alamatUsaha: body.alamatUsaha,
- imageId: body.imageId,
- rating: Number(body.rating),
- kategoriProdukId: body.kategoriId[0],
- kontak: body.kontak,
- deskripsi: body.deskripsi,
- // Use the first category as the main one
- },
- });
-
- // 2. Create category relationships in KategoriToPasar for all categories
- await prisma.kategoriToPasar.createMany({
- data: body.kategoriId.map((kategoriId) => ({
- pasarDesaId: pasarDesa.id,
- kategoriId: kategoriId, // Note: The field is 'kategoriId' in the schema, not 'kategoriProdukId'
- })),
- });
-
- // 3. Get the complete data with relationships
- return await prisma.pasarDesa.findUnique({
- where: { id: pasarDesa.id },
- include: {
- image: true,
- kategoriProduk: true,
- KategoriToPasar: {
- include: {
- kategori: true,
- },
- },
- },
- });
- });
-
- return {
- success: true,
- message: "Sukses menambahkan pasar desa",
- data: result,
- };
- } catch (error) {
- console.error("Error creating PasarDesa:", error);
- throw new Error("Failed to create PasarDesa: " + (error as Error).message);
- }
-}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/del.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/del.ts
deleted file mode 100644
index 5ad6a8e1..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/del.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-export default async function pasarDesaDelete(context: Context) {
- const { params } = context;
- const id = params?.id as string;
-
- if (!id) {
- throw new Error("ID tidak ditemukan dalam parameter");
- }
-
- // 1. Hapus relasi dari pivot
- await prisma.kategoriToPasar.deleteMany({
- where: { pasarDesaId: id },
- });
-
- // 2. Hapus pasar desa utama
- const deleted = await prisma.pasarDesa.delete({
- where: { id },
- });
-
- return {
- success: true,
- message: "Berhasil menghapus pasar desa",
- data: deleted,
- };
-}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts
deleted file mode 100644
index cce98fd2..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-// /api/berita/findManyPaginated.ts
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-async function pasarDesaFindMany(context: Context) {
- // Ambil parameter dari query
- const page = Number(context.query.page) || 1;
- const limit = Number(context.query.limit) || 10;
- const search = (context.query.search as string) || '';
- const categoryId = context.query.categoryId as string | undefined;
- const skip = (page - 1) * limit;
-
- // Buat where clause: Tampilkan hanya yang TIDAK punya umkmId (Produk Pasar Murni)
- const where: any = {
- isActive: true,
- deletedAt: null,
- umkmId: null
- };
-
- // Tambahkan filter kategori (jika ada)
- if (categoryId) {
- where.KategoriToPasar = {
- some: {
- kategoriId: categoryId
- }
- };
- }
-
- // Tambahkan pencarian (jika ada)
- if (search) {
- where.AND = where.AND || [];
- where.AND.push({
- OR: [
- { nama: { contains: search, mode: 'insensitive' } },
- { alamatUsaha: { contains: search, mode: 'insensitive' } },
- {
- KategoriToPasar: {
- some: {
- kategori: {
- nama: { contains: search, mode: 'insensitive' }
- }
- }
- }
- }
- ]
- });
- }
-
- try {
- // Ambil data dan total count secara paralel
- const [data, total] = await Promise.all([
- prisma.pasarDesa.findMany({
- where,
- include: {
- image: true,
- KategoriToPasar: {
- include: {
- kategori: true
- }
- }
- },
- skip,
- take: limit,
- orderBy: { createdAt: 'desc' },
- }),
- prisma.pasarDesa.count({ where }),
- ]);
-
- return {
- success: true,
- message: "Berhasil ambil pasar desa dengan pagination (Non-UMKM)",
- data,
- page,
- limit,
- total,
- totalPages: Math.ceil(total / limit),
- };
- } catch (e) {
- console.error("Error di findMany paginated:", e);
- return {
- success: false,
- message: "Gagal mengambil data pasar desa",
- };
- }
-}
-
-export default pasarDesaFindMany;
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findUnique.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findUnique.ts
deleted file mode 100644
index 5c15b153..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findUnique.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-export default async function pasarDesaFindUnique(context: Context) {
- const { params } = context;
- const id = params?.id as string;
-
- if (!id) {
- throw new Error("ID tidak ditemukan dalam parameter");
- }
-
- const data = await prisma.pasarDesa.findUnique({
- where: { id },
- include: {
- image: true,
- KategoriToPasar: {
- include: {
- kategori: true,
- },
- },
- },
- });
-
- if (!data) {
- throw new Error("Pasar desa tidak ditemukan");
- }
-
- return {
- success: true,
- message: "Data pasar desa ditemukan",
- data,
- };
-}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/index.ts
deleted file mode 100644
index 5d310992..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/index.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import Elysia, { t } from "elysia";
-import pasarDesaCreate from "./create";
-import pasarDesaDelete from "./del";
-import pasarDesaFindMany from "./findMany";
-import pasarDesaUpdate from "./updt";
-import pasarDesaFindUnique from "./findUnique";
-
-const PasarDesa = new Elysia({
- prefix: "/pasardesa",
- tags: ["Ekonomi/Pasar Desa"],
-})
- // GET all
- .get("/find-many", pasarDesaFindMany)
-
- // GET by ID
- .get(
- "/:id",
- async (context) => {
- return await pasarDesaFindUnique(context);
- },
- {
- params: t.Object({
- id: t.String(),
- }),
- }
- )
-
- // POST create
- .post(
- "/create",
- pasarDesaCreate,
- {
- body: t.Object({
- nama: t.String(),
- harga: t.Number(),
- alamatUsaha: t.String(),
- imageId: t.String(),
- rating: t.Number(),
- kategoriId: t.Array(t.String()),
- kontak: t.String(),
- deskripsi: t.String(),
- }),
- }
- )
-
- // DELETE
- .delete(
- "/del/:id",
- pasarDesaDelete,
- {
- params: t.Object({
- id: t.String(),
- }),
- }
- )
-
- // PUT update
- .put(
- "/:id",
- async (context) => {
- const body = context.body;
- const id = context.params.id;
-
- // Gabungkan id ke body
- return await pasarDesaUpdate({
- ...context,
- body: {
- ...body,
- id,
- },
- });
- },
- {
- params: t.Object({
- id: t.String(),
- }),
- body: t.Object({
- nama: t.String(),
- harga: t.Number(),
- alamatUsaha: t.String(),
- imageId: t.String(),
- rating: t.Number(),
- kategoriId: t.Array(t.String()),
- kontak: t.String(),
- deskripsi: t.String(),
- }),
- }
- );
-
-export default PasarDesa;
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/create.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/create.ts
deleted file mode 100644
index fd92ea8b..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/create.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-
-export default async function kategoriProdukCreate(context: Context) {
- const body = context.body as {nama: string};
-
- if (!body.nama) {
- return {
- success: false,
- message: "Nama is required",
- };
- }
-
- const kategoriProduk = await prisma.kategoriProduk.create({
- data: {
- nama: body.nama,
- },
- });
- return {
- success: true,
- message: "Sukses menambahkan kategori produk",
- data: kategoriProduk
- };
-}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/del.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/del.ts
deleted file mode 100644
index 317ea36e..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/del.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-const kategoriProdukDelete = async (context: Context) => {
- const id = context.params.id;
- if (!id) {
- return {
- success: false,
- message: "ID is required",
- }
- }
-
- const kategoriProduk = await prisma.kategoriProduk.delete({
- where: {
- id: id,
- },
- })
-
- if(!kategoriProduk) {
- return {
- success: false,
- message: "Kategori Produk tidak ditemukan",
- }
- }
-
- return {
- success: true,
- message: "Sukses Menghapus kategori produk",
- data: kategoriProduk,
- }
-}
-
-export default kategoriProdukDelete
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts
deleted file mode 100644
index 8f50c579..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-// /api/berita/findManyPaginated.ts
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-async function kategoriProdukFindMany(context: Context) {
- // Ambil parameter dari query
- const page = Number(context.query.page) || 1;
- const limit = Number(context.query.limit) || 10;
- const search = (context.query.search as string) || '';
- const skip = (page - 1) * limit;
-
- // Buat where clause
- const where: any = { isActive: true };
-
- // Tambahkan pencarian (jika ada)
- if (search) {
- where.OR = [
- { nama: { contains: search, mode: 'insensitive' } },
- {KategoriToPasar : {
- some: {
- kategori: {
- nama: { contains: search, mode: 'insensitive' }
- }
- }
- }}
- ];
- }
-
- try {
- // Ambil data dan total count secara paralel
- const [data, total] = await Promise.all([
- prisma.kategoriProduk.findMany({
- where,
- skip,
- take: limit,
- orderBy: { createdAt: 'desc' },
- }),
- prisma.kategoriProduk.count({ where }),
- ]);
-
- return {
- success: true,
- message: "Berhasil ambil kategori produk dengan pagination",
- data,
- page,
- limit,
- total,
- totalPages: Math.ceil(total / limit),
- };
- } catch (e) {
- console.error("Error di findMany paginated:", e);
- return {
- success: false,
- message: "Gagal mengambil data kategori produk",
- };
- }
-}
-
-export default kategoriProdukFindMany;
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findManyAll.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findManyAll.ts
deleted file mode 100644
index e364ecf2..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findManyAll.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-// /api/berita/findManyAll.ts
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-async function kategoriProdukFindManyAll(context: Context) {
- // Ambil query search (opsional)
- const search = (context.query.search as string) || "";
-
- // Buat where clause
- const where: any = { isActive: true };
-
- if (search) {
- where.OR = [
- { nama: { contains: search, mode: "insensitive" } },
- {
- KategoriToPasar: {
- some: {
- kategori: {
- nama: { contains: search, mode: "insensitive" },
- },
- },
- },
- },
- ];
- }
-
- try {
- const data = await prisma.kategoriProduk.findMany({
- where,
- orderBy: { createdAt: "desc" },
- });
-
- return {
- success: true,
- message: "Berhasil ambil semua kategori produk",
- data,
- total: data.length,
- };
- } catch (e) {
- console.error("Error di findManyAll:", e);
- return {
- success: false,
- message: "Gagal mengambil data kategori produk",
- };
- }
-}
-
-export default kategoriProdukFindManyAll;
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findUnique.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findUnique.ts
deleted file mode 100644
index b9b0b8fe..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findUnique.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Context } from "elysia";
-import prisma from "@/lib/prisma";
-
-export default async function kategoriProdukFindUnique(context: Context) {
- const url = new URL(context.request.url);
- const pathSegments = url.pathname.split('/');
- const id = pathSegments[pathSegments.length - 1];
-
- if (!id) {
- return {
- success: false,
- message: "ID is required",
- }
- }
-
- try {
- if (typeof id !== 'string') {
- return {
- success: false,
- message: "ID is required",
- }
- }
-
- const data = await prisma.kategoriProduk.findUnique({
- where: { id },
- });
-
- if (!data) {
- return {
- success: false,
- message: "Kategori makanan tidak ditemukan",
- }
- }
-
- return {
- success: true,
- message: "Success find kategori makanan",
- data,
- }
- } catch (error) {
- console.error("Find by ID error:", error);
- return {
- success: false,
- message: "Gagal mengambil kategori makanan: " + (error instanceof Error ? error.message : 'Unknown error'),
- }
- }
-}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/index.ts
deleted file mode 100644
index 5777098b..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/index.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import Elysia from "elysia";
-import kategoriProdukFindMany from "./findMany";
-import kategoriProdukFindUnique from "./findUnique";
-import kategoriProdukDelete from "./del";
-import kategoriProdukCreate from "./create";
-import kategoriProdukUpdate from "./updt";
-import { t } from "elysia";
-import kategoriProdukFindManyAll from "./findManyAll";
-
-const KategoriProduk = new Elysia({
- prefix: "/kategoriproduk",
- tags: ["Ekonomi/Kategori Produk"],
-})
- .get("/find-many", kategoriProdukFindMany)
- .get("/find-many-all", kategoriProdukFindManyAll)
- .get("/:id", async (context) => {
- const response = await kategoriProdukFindUnique(context);
- return response;
- })
- .delete("/del/:id", kategoriProdukDelete)
- .post("/create", kategoriProdukCreate, {
- body: t.Object({
- nama: t.String(),
- }),
- })
- .put("/:id", kategoriProdukUpdate, {
- body: t.Object({
- nama: t.String(),
- }),
- });
-
-export default KategoriProduk;
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/updt.ts
deleted file mode 100644
index 18dc0837..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/updt.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-export default async function kategoriProdukUpdate(context: Context) {
- const body = context.body as { nama: string };
- const id = context.params?.id as string;
-
- // Validasi ID dan nama
- if (!id) {
- return {
- success: false,
- message: "ID is required",
- };
- }
-
- if (!body.nama) {
- return {
- success: false,
- message: "Nama is required",
- };
- }
-
- try {
- const kategoriProduk = await prisma.kategoriProduk.update({
- where: { id },
- data: {
- nama: body.nama,
- },
- });
-
- return {
- success: true,
- message: "Success update kategori produk",
- data: kategoriProduk,
- };
- } catch (error) {
- console.error("Update error:", error);
- return {
- success: false,
- message: "Gagal update kategori produk",
- error: error instanceof Error ? error.message : String(error),
- };
- }
-}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/updt.ts
deleted file mode 100644
index 98d80704..00000000
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/updt.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import prisma from "@/lib/prisma";
-import { Context } from "elysia";
-
-type FormUpdate = {
- id: string;
- nama: string;
- harga: number;
- alamatUsaha: string;
- imageId: string;
- rating: number;
- kategoriId: string[]; // Array of KategoriProduk IDs
- kontak: string;
- deskripsi: string;
-};
-
-export default async function pasarDesaUpdate(context: Context) {
- const body = context.body as FormUpdate;
-
- if (!body.id) {
- throw new Error("ID pasar desa tidak boleh kosong");
- }
-
- if (!body.kategoriId || body.kategoriId.length === 0) {
- throw new Error("Minimal 1 kategori harus dipilih");
- }
-
- // 1. Update data utama pasar desa
- await prisma.pasarDesa.update({
- where: { id: body.id },
- data: {
- nama: body.nama,
- harga: Number(body.harga),
- alamatUsaha: body.alamatUsaha,
- imageId: body.imageId,
- rating: Number(body.rating),
- kontak: body.kontak,
- deskripsi: body.deskripsi
- },
- });
-
- // 2. Hapus semua relasi kategori lama
- await prisma.kategoriToPasar.deleteMany({
- where: { pasarDesaId: body.id },
- });
-
- // 3. Tambah relasi kategori yang baru
- await prisma.kategoriToPasar.createMany({
- data: body.kategoriId.map((kategoriProdukId) => ({
- pasarDesaId: body.id,
- kategoriId: kategoriProdukId,
- })),
- });
-
- // 4. Ambil data lengkap setelah update
- const updated = await prisma.pasarDesa.findUnique({
- where: { id: body.id },
- include: {
- image: true,
- KategoriToPasar: {
- include: {
- kategori: true,
- },
- },
- },
- });
-
- return {
- success: true,
- message: "Success update pasar desa",
- data: updated,
- };
-}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/detailPenjualan.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/detailPenjualan.ts
index 43597f4e..9abfce97 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/detailPenjualan.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/detailPenjualan.ts
@@ -22,9 +22,9 @@ async function umkmDashboardDetailPenjualan(context: Context) {
where: { periode: periodeLalu, deletedAt: null },
_sum: { totalNilai: true }
}),
- // Use PasarDesa with umkmId filter
+ // Use PasarDesa
prisma.pasarDesa.findMany({
- where: { deletedAt: null, umkmId: { not: null } },
+ where: { deletedAt: null },
select: { id: true, nama: true, stok: true }
})
]);
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/ringSummary.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/ringSummary.ts
index 1c4dfd2a..f6fe7425 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/ringSummary.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/ringSummary.ts
@@ -20,9 +20,9 @@ async function umkmDashboardRingSummary(context: Context) {
where: { periode: periodeLalu, deletedAt: null },
_sum: { totalNilai: true }
}),
- // Count from PasarDesa with umkmId filter
- prisma.pasarDesa.count({
- where: { isActive: true, deletedAt: null, umkmId: { not: null } }
+ // Count from PasarDesa
+ prisma.pasarDesa.count({
+ where: { isActive: true, deletedAt: null }
}),
prisma.penjualanProduk.count({ where: { periode, deletedAt: null } })
]);
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/findMany.ts
index 93e9ec65..1a1df2ce 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/findMany.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/findMany.ts
@@ -10,13 +10,11 @@ async function produkUmkmFindMany(context: Context) {
const kategoriId = context.query.kategoriId as string | undefined;
const skip = (page - 1) * limit;
- // Filter: ONLY products that belong to an UMKM
- const where: any = {
+ // Filter: ONLY active products
+ const where: any = {
deletedAt: null,
isActive: true,
- umkmId: { not: null }
};
-
if (umkmId) {
where.umkmId = umkmId;
}
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/index.ts
index c87a92c1..a735ac6c 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/index.ts
@@ -20,6 +20,7 @@ const ProdukUmkm = new Elysia({
nama: t.String(),
harga: t.Number(),
umkmId: t.String(),
+ kategoriId: t.String(), // Added validation
stok: t.Optional(t.Number()),
deskripsi: t.Optional(t.String()),
imageId: t.Optional(t.String()),
@@ -34,6 +35,7 @@ const ProdukUmkm = new Elysia({
nama: t.String(),
harga: t.Number(),
umkmId: t.String(),
+ kategoriId: t.String(), // Added validation
stok: t.Number(),
deskripsi: t.Optional(t.String()),
imageId: t.Optional(t.String()),
diff --git a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx
deleted file mode 100644
index 579c8286..00000000
--- a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-'use client'
-import colors from '@/con/colors';
-import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider, Title } from '@mantine/core';
-import { IconArrowBack, IconBrandWhatsapp, IconMapPin, IconPhone, IconStar } from '@tabler/icons-react';
-import { useRouter, useParams } from 'next/navigation';
-import React from 'react';
-import { useProxy } from 'valtio/utils';
-import { useShallowEffect } from '@mantine/hooks';
-import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
-
-function DetailProdukPasarUser() {
- const router = useRouter();
- const params = useParams();
- const statePasar = useProxy(pasarDesaState);
-
- useShallowEffect(() => {
- statePasar.pasarDesa.findUnique.load(params?.id as string);
- }, []);
-
- const data = statePasar.pasarDesa.findUnique.data;
-
- if (!data) {
- return (
-
-
-
- );
- }
-
- return (
-
- {/* Tombol kembali */}
-
-
-
-
-
-
- {/* Gambar Produk */}
- {data.image?.link ? (
-
- ) : (
-
-
- Tidak ada gambar
-
-
- )}
-
- {/* Detail Produk */}
-
-
- {data.nama || 'Produk Tanpa Nama'}
-
-
-
-
- Rp {data.harga?.toLocaleString('id-ID')}
-
-
- {data.rating && (
-
-
-
- {data.rating}
-
-
- )}
-
-
-
-
-
- {/* Info Tambahan */}
-
-
-
- Kategori
-
-
- {data.KategoriToPasar && data.KategoriToPasar.length > 0 ? (
- data.KategoriToPasar.map((kategori) => (
-
- {kategori.kategori.nama}
-
- ))
- ) : (
-
- Tidak ada kategori
-
- )}
-
-
-
- {data.alamatUsaha && (
-
-
-
- {data.alamatUsaha}
-
-
- )}
-
- {data.kontak && (
-
-
-
- {data.kontak}
-
-
- )}
-
-
-
-
- {/* Deskripsi */}
-
-
- Deskripsi Produk
-
-
- Tidak ada deskripsi.
-
-
-
- {/* Tombol Aksi User */}
- {data.kontak && (
-
- )}
-
-
-
- );
-}
-
-export default DetailProdukPasarUser;
\ No newline at end of file
diff --git a/src/app/darmasaba/(pages)/ekonomi/umkm/[id]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/umkm/[id]/page.tsx
index 27b8f8a8..b6e431a1 100644
--- a/src/app/darmasaba/(pages)/ekonomi/umkm/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/ekonomi/umkm/[id]/page.tsx
@@ -1,14 +1,15 @@
'use client'
import umkmState from '@/app/admin/(dashboard)/_state/ekonomi/umkm/umkm';
import colors from '@/con/colors';
-import { Box, Card, Flex, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title, Badge, SimpleGrid, Group, Divider, Button, Center } from '@mantine/core';
+import { Box, Card, Flex, Grid, GridCol, Image, Skeleton, Stack, Text, Title, Badge, SimpleGrid, Group, Divider, Button, Center } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
-import { IconBrandWhatsapp, IconMapPinFilled, IconPackage, IconUser } from '@tabler/icons-react';
-import { useParams } from 'next/navigation';
+import { IconBrandWhatsapp, IconMapPinFilled, IconUser } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import BackButton from '../../../desa/layanan/_com/BackButto';
function Page() {
+ const router = useRouter();
const params = useParams();
const id = params.id as string;
const state = useProxy(umkmState.umkm.findUnique);
@@ -83,7 +84,14 @@ function Page() {
Katalog Produk
{u.produk?.map((p: any, k: number) => (
-
+ router.push(`/darmasaba/ekonomi/umkm/produk/${p.id}`)}
+ >
('produk-pasar');
+ const [activeTab, setActiveTab] = useState('produk-umkm');
return (
@@ -31,7 +30,7 @@ function Page() {
- Pasar Desa & UMKM Darmasaba
+ UMKM & Produk Desa Darmasaba
Pusat informasi produk lokal dan direktori usaha warga Desa Darmasaba.
@@ -40,21 +39,14 @@ function Page() {
- }>
- Produk Pasar Desa
-
}>
- Produk UMKM
+ Katalog Produk
}>
- Direktori UMKM
+ Direktori Bisnis
-
-
-
-
@@ -70,96 +62,6 @@ function Page() {
// --- TAB COMPONENTS ---
-function TabProdukPasar({ router }: { router: any }) {
- const state = useProxy(pasarDesaState.pasarDesa);
- const [search, setSearch] = useState('');
- const [debouncedSearch] = useDebouncedValue(search, 1000);
- const [selectedCategory, setSelectedCategory] = useState(null);
-
- const { data, page, loading, totalPages } = state.findMany;
-
- useShallowEffect(() => {
- pasarDesaState.kategoriProduk.findManyAll.load();
- }, []);
-
- useShallowEffect(() => {
- pasarDesaState.pasarDesa.findMany.load(page, 8, debouncedSearch, selectedCategory || undefined);
- }, [page, debouncedSearch, selectedCategory]);
-
- return (
-
-
-
- setSearch(e.currentTarget.value)}
- leftSection={}
- radius="md"
- />
-
-
-
-
-
- {loading ? (
-
- ) : (
- <>
-
- {data?.map((v, k) => (
- router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
- whileHover={{ scale: 1.03 }}
- >
-
-
- {v.nama}
- Rp {v.harga.toLocaleString('id-ID')}
-
-
- {v.rating}
-
-
-
-
- {v.alamatUsaha}
-
-
-
-
-
- ))}
-
-
- pasarDesaState.pasarDesa.findMany.load(p)} radius="md" />
-
- >
- )}
-
- );
-}
-
function TabProdukUmkm({ router }: { router: any }) {
const state = useProxy(umkmState.produk);
const [search, setSearch] = useState('');
@@ -210,7 +112,12 @@ function TabProdukUmkm({ router }: { router: any }) {
<>
{data?.map((v, k) => (
-
+ router.push(`/darmasaba/ekonomi/umkm/produk/${v.id}`)}
+ style={{ cursor: 'pointer' }}
+ >
0 ? 'Tersedia' : 'Habis'}
{v.nama}
- router.push(`/darmasaba/ekonomi/umkm/${v.umkmId}`)}>
+ {
+ e.stopPropagation();
+ router.push(`/darmasaba/ekonomi/umkm/${v.umkmId}`);
+ }}>
{v.umkm?.nama}
Rp {v.harga.toLocaleString('id-ID')}
diff --git a/src/app/darmasaba/(pages)/ekonomi/umkm/produk/[id]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/umkm/produk/[id]/page.tsx
new file mode 100644
index 00000000..93cfec9c
--- /dev/null
+++ b/src/app/darmasaba/(pages)/ekonomi/umkm/produk/[id]/page.tsx
@@ -0,0 +1,134 @@
+'use client'
+import colors from '@/con/colors';
+import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider, Title } from '@mantine/core';
+import { IconArrowBack, IconBrandWhatsapp, IconMapPin, IconUser } from '@tabler/icons-react';
+import { useRouter, useParams } from 'next/navigation';
+import React from 'react';
+import { useProxy } from 'valtio/utils';
+import { useShallowEffect } from '@mantine/hooks';
+import umkmState from '@/app/admin/(dashboard)/_state/ekonomi/umkm/umkm';
+
+function DetailProdukPasarUser() {
+ const router = useRouter();
+ const params = useParams();
+ const state = useProxy(umkmState.produk.findUnique);
+
+ useShallowEffect(() => {
+ state.load(params?.id as string);
+ }, []);
+
+ const data = state.data;
+
+ if (state.loading || !data) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Tombol kembali */}
+
+
+
+
+
+
+ {/* Gambar Produk */}
+
+
+ {/* Detail Produk */}
+
+
+
+ {data.kategoriProduk?.nama}
+
+ {data.nama}
+
+
+ 0 ? 'green' : 'red'} size="lg">
+ {data.stok > 0 ? `Stok: ${data.stok}` : 'Stok Habis'}
+
+
+
+
+ Rp {data.harga?.toLocaleString('id-ID')}
+
+
+
+
+
+
+ Informasi Penjual
+
+
+
+ router.push(`/darmasaba/ekonomi/umkm/${data.umkmId}`)}>
+ {data.umkm?.nama}
+
+
+
+ {data.umkm?.pemilik}
+
+
+
+ {data.umkm?.alamat || 'Darmasaba'}
+
+
+ {data.umkm?.kontak && (
+
+ )}
+
+
+
+
+
+ Deskripsi Produk
+
+ {data.deskripsi || 'Tidak ada deskripsi tersedia untuk produk ini.'}
+
+
+
+
+
+
+
+ );
+}
+
+export default DetailProdukPasarUser;
\ No newline at end of file
diff --git a/src/con/navbar-list-menu.ts b/src/con/navbar-list-menu.ts
index fb14726a..f0a8e5f6 100644
--- a/src/con/navbar-list-menu.ts
+++ b/src/con/navbar-list-menu.ts
@@ -172,8 +172,8 @@ const navbarListMenu = [
children: [
{
id: "5.1",
- name: "Pasar Desa",
- href: "/darmasaba/ekonomi/pasar-desa"
+ name: "UMKM",
+ href: "/darmasaba/ekonomi/umkm"
},
{
id: "5.2",