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 - - - - - {/* 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 && ( - - Preview Gambar - - {/* 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 ? ( - {data.nama - ) : ( - 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 && ( - - Preview Gambar - { - 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 - - - - {/* 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 ? ( - {data.nama} - ) : ( - - - 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" - /> - - -