diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1126f317..1768f08c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -82,6 +82,8 @@ model FileStorage { MenuTipsKeamanan MenuTipsKeamanan[] Pelapor Pelapor[] + + PasarDesa PasarDesa[] } //========================================= MENU PPID ========================================= // @@ -924,8 +926,6 @@ model LayananPolsek { PolsekTerdekat PolsekTerdekat[] } - - // ========================================= KONTAK DARURAT ========================================= // model KontakDaruratKeamanan { id String @id @default(cuid()) @@ -1031,3 +1031,32 @@ model MenuTipsKeamanan { deletedAt DateTime @default(now()) isActive Boolean @default(true) } + +// ========================================= MENU EKONOMI ========================================= // +// ========================================= PASAR DESA ========================================= // +model PasarDesa { + id String @id @default(uuid()) + nama String // contoh: "Kerupuk Babi" + harga Int // disimpan dalam bentuk angka: 12000 + satuan String // contoh: "pcs", "1 kg" + alamat String // contoh: "Jl. Kenari no.7" + image FileStorage @relation(fields: [imageId], references: [id]) + imageId String + rating Float // contoh: 4.9 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + kategori KategoriMakanan @relation(fields: [kategoriId], references: [id]) + kategoriId String +} + +model KategoriMakanan { + id String @id @default(uuid()) + nama String // contoh: "Makanan", "Bahan Bangunan", dll + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + isActive Boolean @default(true) + PasarDesa PasarDesa[] +} 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 new file mode 100644 index 00000000..6aa21db5 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts @@ -0,0 +1,230 @@ +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 templateForm = z.object({ + nama: z.string().min(3, "Nama minimal 3 karakter"), + harga: z.number().min(1, "Harga minimal 1"), + satuan: z.string().min(3, "Satuan minimal 3 karakter"), + alamat: z.string().min(3, "Alamat minimal 3 karakter"), + imageId: z.string().min(1, "Gambar wajib dipilih"), + rating: z.number().min(1, "Rating minimal 1"), + kategoriId: z.string().min(1, "Kategori wajib dipilih"), +}); + +const defaultForm = { + nama: "", + harga: 0, + satuan: "", + alamat: "", + imageId: "", + rating: 0, + kategoriId: "", +}; + +const pasarDesaState = proxy({ + create: { + form: { ...defaultForm }, + loading: false, + async create() { + const cek = templateForm.safeParse(pasarDesaState.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + pasarDesaState.create.loading = true; + const res = await ApiFetch.api.ekonomi.pasardesa["create"].post( + pasarDesaState.create.form + ); + if (res.status === 200) { + pasarDesaState.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 { + pasarDesaState.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.PasarDesaGetPayload<{ + include: { kategori: true; image: true }; + }>[] + | null, + async load() { + const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get(); + if (res.status === 200) { + pasarDesaState.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.PasarDesaGetPayload<{ + include: { kategori: true; image: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/ekonomi/pasardesa/${id}`); + if (res.ok) { + const data = await res.json(); + pasarDesaState.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + pasarDesaState.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data:", error); + pasarDesaState.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + pasarDesaState.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 pasarDesaState.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 { + pasarDesaState.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...defaultForm }, + 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, + satuan: data.satuan, + alamat: data.alamat, + imageId: data.imageId, + rating: data.rating, + kategoriId: data.kategoriId, + }; + 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 = templateForm.safeParse(pasarDesaState.edit.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + pasarDesaState.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, + satuan: this.form.satuan, + alamat: this.form.alamat, + imageId: this.form.imageId, + rating: this.form.rating, + kategoriId: this.form.kategoriId, + }), + } + ); + 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 pasarDesaState.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 { + pasarDesaState.edit.loading = false; + } + }, + reset() { + pasarDesaState.edit.id = ""; + pasarDesaState.edit.form = { ...defaultForm }; + }, + }, +}); + +export default pasarDesaState; diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/_lib/layoutTabs.tsx new file mode 100644 index 00000000..635aa031 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/_lib/layoutTabs.tsx @@ -0,0 +1,62 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import colors from '@/con/colors'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +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/pasar-desa-ui" + }, + { + label: "Kategori Makanan", + value: "kategorimakanan", + href: "/admin/ekonomi/pasar-desa/kategori-makanan" + }, + ]; + const curentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(curentTab?.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 + + + {tabs.map((e, i) => ( + {e.label} + ))} + + {tabs.map((e, i) => ( + + {/* Konten dummy, bisa diganti tergantung routing */} + <> + + ))} + + {children} + + ); +} + +export default LayoutTabs; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/[id]/edit/page.tsx new file mode 100644 index 00000000..69da2f21 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/[id]/edit/page.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function Page() { + return ( +
+ Page +
+ ); +} + +export default Page; diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/[id]/page.tsx new file mode 100644 index 00000000..69da2f21 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/[id]/page.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function Page() { + return ( +
+ Page +
+ ); +} + +export default Page; diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/create/page.tsx new file mode 100644 index 00000000..69da2f21 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/create/page.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function Page() { + return ( +
+ Page +
+ ); +} + +export default Page; diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/page.tsx new file mode 100644 index 00000000..784c7c45 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-makanan/page.tsx @@ -0,0 +1,79 @@ +'use client' +import colors from '@/con/colors'; +import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; +import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { useShallowEffect } from '@mantine/hooks'; +import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../../_com/header'; +import JudulList from '../../../_com/judulList'; +import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa'; + +function PasarDesa() { + return ( + + } + /> + + + ); +} + +function ListPasarDesa() { + const statePasar = useProxy(pasarDesaState) + const router = useRouter(); + + useShallowEffect(() => { + statePasar.findMany.load() + }, []) + + if (!statePasar.findMany.data) { + return ( + + + + ) + } + + return ( + + + + + + + Nama Produk + Harga Produk + Rating Produk + Alamat Usaha + Detail + + + + {statePasar.findMany.data?.map((item) => ( + + {item.nama} + {item.harga} + {item.rating} + {item.alamat} + + + + + ))} + +
+
+
+ ); +} + +export default PasarDesa; diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/layout.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/layout.tsx new file mode 100644 index 00000000..9ebad0e4 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/layout.tsx @@ -0,0 +1,12 @@ +'use client' + +import LayoutTabs from "./_lib/layoutTabs" + + +export default function Layout({children} : {children: React.ReactNode}) { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/page.tsx deleted file mode 100644 index 185e8f98..00000000 --- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/page.tsx +++ /dev/null @@ -1,60 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; -import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; -import HeaderSearch from '../../_com/header'; -import JudulList from '../../_com/judulList'; -import { useRouter } from 'next/navigation'; - -function PasarDesa() { - return ( - - } - /> - - - ); -} - -function ListPasarDesa() { - const router = useRouter(); - return ( - - - - - - - Nama Produk - Harga Produk - Rating Produk - Alamat Usaha - Detail - - - - - Produk 1 - Harga Rp. 20.000 - Rating 5 - Jalan In Aja - - - - - -
-
-
- ); -} - -export default PasarDesa; diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/create/page.tsx similarity index 95% rename from src/app/admin/(dashboard)/ekonomi/pasar-desa/create/page.tsx rename to src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/create/page.tsx index 6c7a8f14..1fb39a3e 100644 --- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/create/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/create/page.tsx @@ -3,7 +3,7 @@ import colors from '@/con/colors'; import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import { KeamananEditor } from '../../../keamanan/_com/keamananEditor'; +import { KeamananEditor } from '../../../../keamanan/_com/keamananEditor'; function CreatePasarDesa() { const router = useRouter(); diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/detail/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/detail/page.tsx similarity index 100% rename from src/app/admin/(dashboard)/ekonomi/pasar-desa/detail/page.tsx rename to src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/detail/page.tsx diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/edit/page.tsx similarity index 95% rename from src/app/admin/(dashboard)/ekonomi/pasar-desa/edit/page.tsx rename to src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/edit/page.tsx index 92335984..0b47b626 100644 --- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/edit/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/edit/page.tsx @@ -3,7 +3,7 @@ import colors from '@/con/colors'; import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import { KeamananEditor } from '../../../keamanan/_com/keamananEditor'; +import { KeamananEditor } from '../../../../keamanan/_com/keamananEditor'; function EditPasarDesa() { const router = useRouter(); diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/page.tsx new file mode 100644 index 00000000..0906c16a --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/pasar-desa-ui/page.tsx @@ -0,0 +1,79 @@ +'use client' +import colors from '@/con/colors'; +import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; +import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { useShallowEffect } from '@mantine/hooks'; +import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../../_com/header'; +import JudulList from '../../../_com/judulList'; +import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa'; + +function PasarDesa() { + return ( + + } + /> + + + ); +} + +function ListPasarDesa() { + const statePasar = useProxy(pasarDesaState) + const router = useRouter(); + + useShallowEffect(() => { + statePasar.findMany.load() + }, []) + + if (!statePasar.findMany.data) { + return ( + + + + ) + } + + return ( + + + + + + + Nama Produk + Harga Produk + Rating Produk + Alamat Usaha + Detail + + + + {statePasar.findMany.data?.map((item) => ( + + {item.nama} + {item.harga} + {item.rating} + {item.alamat} + + + + + ))} + +
+
+
+ ); +} + +export default PasarDesa; diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index ef9d99ae..71649506 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -215,7 +215,7 @@ export const navBar = [ { id: "Ekonomi_1", name: "Pasar Desa", - path: "/admin/ekonomi/pasar-desa" + path: "/admin/ekonomi/pasar-desa/pasar-desa-ui" }, { id: "Ekonomi_2", diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts new file mode 100644 index 00000000..8e41e3ce --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/index.ts @@ -0,0 +1,12 @@ +import Elysia from "elysia"; +import PasarDesa from "./pasar-desa"; +import KategoriMakanan from "./kategori-makanan"; + +const Ekonomi = new Elysia({ + prefix: "/api/ekonomi", + tags: ["Ekonomi"], +}) +.use(PasarDesa) +.use(KategoriMakanan) + +export default Ekonomi \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/create.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/create.ts new file mode 100644 index 00000000..2559e21c --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/create.ts @@ -0,0 +1,26 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + + +export default async function kategoriMakananCreate(context: Context) { + const body = context.body as {nama: string}; + + if (!body.nama) { + return { + success: false, + message: "Nama is required", + }; + } + + const kategoriMakanan = await prisma.kategoriMakanan.create({ + data: { + nama: body.nama, + deletedAt: null, + }, + }); + return { + success: true, + message: "Success create kategori makanan", + data: kategoriMakanan + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/del.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/del.ts new file mode 100644 index 00000000..c2057010 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/del.ts @@ -0,0 +1,33 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +const kategoriMakananDelete = async (context: Context) => { + const id = context.params.id; + if (!id) { + return { + success: false, + message: "ID is required", + } + } + + const kategoriMakanan = await prisma.kategoriMakanan.delete({ + where: { + id: id, + }, + }) + + if(!kategoriMakanan) { + return { + success: false, + message: "Kategori makanan tidak ditemukan", + } + } + + return { + success: true, + message: "Success delete kategori makanan", + data: kategoriMakanan, + } +} + +export default kategoriMakananDelete diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/findMany.ts new file mode 100644 index 00000000..3b5a29a4 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/findMany.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; + +export default async function kategoriMakananFindMany() { + const data = await prisma.kategoriMakanan.findMany(); + return { + success: true, + data: data.map((item: any) => { + return { + id: item.id, + nama: item.nama, + } + }), + }; +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/findUnique.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/findUnique.ts new file mode 100644 index 00000000..eb56ea69 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/findUnique.ts @@ -0,0 +1,47 @@ +import { Context } from "elysia"; +import prisma from "@/lib/prisma"; + +export default async function kategoriMakananFindUnique(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.kategoriMakanan.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/kategori-makanan/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/index.ts new file mode 100644 index 00000000..b7553fe4 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/index.ts @@ -0,0 +1,30 @@ +import Elysia from "elysia"; +import kategoriMakananFindMany from "./findMany"; +import kategoriMakananFindUnique from "./findUnique"; +import kategoriMakananDelete from "./del"; +import kategoriMakananCreate from "./create"; +import kategoriMakananUpdate from "./updt"; +import { t } from "elysia"; + +const KategoriMakanan = new Elysia({ + prefix: "/kategori-makanan", + tags: ["Ekonomi/Kategori Makanan"], +}) + .get("/find-many", kategoriMakananFindMany) + .get("/:id", async (context) => { + const response = await kategoriMakananFindUnique(context); + return response; + }) + .delete("/del/:id", kategoriMakananDelete) + .post("/create", kategoriMakananCreate, { + body: t.Object({ + nama: t.String(), + }), + }) + .put("/:id", kategoriMakananUpdate, { + body: t.Object({ + nama: t.String(), + }), + }); + +export default KategoriMakanan; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/updt.ts new file mode 100644 index 00000000..4517513d --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/kategori-makanan/updt.ts @@ -0,0 +1,35 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kategoriMakananUpdate(context: Context) { + const body = await context.request.json() + + if (!body.nama) { + return { + success: false, + message: "Nama is required", + } + } + + const kategoriMakanan = await prisma.kategoriMakanan.update({ + where: { + id: body.id, + }, + data: { + nama: body.nama, + }, + }) + + if(!kategoriMakanan) { + return { + success: false, + message: "Kategori makanan tidak ditemukan", + } + } + + return { + success: true, + message: "Success update kategori makanan", + data: kategoriMakanan, + } +} diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/create.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/create.ts new file mode 100644 index 00000000..d1d92f34 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/create.ts @@ -0,0 +1,38 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreate = { + nama: string; + harga: number; + satuan: string; + alamat: string; + imageId: string; + rating: number; + kategoriId: string; // Array of KategoriMakanan IDs +}; +export default async function pasarDesaCreate(context: Context) { + const body = context.body as FormCreate; + + // First, create the PasarDesa record + const pasarDesa = await prisma.pasarDesa.create({ + data: { + nama: body.nama, + harga: Number(body.harga), + satuan: body.satuan, + alamat: body.alamat, + imageId: body.imageId, + rating: Number(body.rating), + kategoriId: body.kategoriId, + }, + include: { + image: true, + kategori: true, + }, + }); + + return { + success: true, + message: "Success create pasar desa", + data: pasarDesa, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/del.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/del.ts new file mode 100644 index 00000000..04bdbec6 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/del.ts @@ -0,0 +1,54 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; +import fs from "fs/promises"; +import path from "path"; + +const pasarDesaDelete = async (context: Context) => { + const id = context.params?.id as string; + + if (!id) { + return { + status: 400, + body: "ID tidak diberikan", + }; + } + + const pasarDesa = await prisma.pasarDesa.findUnique({ + where: { id }, + include: { + image: true, + kategori: true, + }, + }); + + if (!pasarDesa) { + return { + status: 404, + body: "Pasar desa tidak ditemukan", + }; + } + + if (pasarDesa.image) { + try { + const filePath = path.join(pasarDesa.image.path, pasarDesa.image.name); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: pasarDesa.image.id }, + }); + } catch (err) { + console.error("Gagal hapus file image:", err); + } + } + + + await prisma.pasarDesa.delete({ + where: { id }, + }); + + return { + status: 200, + success: true, + message: "Pasar desa berhasil dihapus", + }; +}; +export default pasarDesaDelete; diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts new file mode 100644 index 00000000..f1b2e6f4 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts @@ -0,0 +1,25 @@ +import prisma from "@/lib/prisma"; + +export default async function pasarDesaFindMany() { + try { + const data = await prisma.pasarDesa.findMany({ + where: { isActive: true }, + include: { + image: true, + kategori: true, + }, + }); + + return { + success: true, + message: "Success fetch pasar desa", + data, + }; + } catch (e) { + console.error("Find many error:", e); + return { + success: false, + message: "Failed fetch pasar desa", + }; + } +} \ 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 new file mode 100644 index 00000000..29f90722 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findUnique.ts @@ -0,0 +1,54 @@ +import prisma from "@/lib/prisma"; + +export default async function pasarDesaFindUnique(request: Request){ + const url = new URL(request.url); + const pathSegments = url.pathname.split('/'); + const id = pathSegments[pathSegments.length - 1]; + + if(!id){ + return Response.json({ + success: false, + message: "ID tidak boleh kosong", + }, { status: 400 }); + } + + try { + if (typeof id !== 'string') { + return Response.json({ + success: false, + message: "ID tidak valid", + }, { status: 400 }); + } + + const data = await prisma.pasarDesa.findUnique({ + where: { id }, + include: { + image: true, + kategori: true, + }, + }); + + if (!data) { + return Response.json({ + success: false, + message: "Pasar desa tidak ditemukan", + }, { status: 404 }); + } + + return Response.json({ + success: true, + message: "Success fetch pasar desa by ID", + data, + }, { + status: 200, + }); + } catch (e) { + console.error("Find by ID error:", e); + return Response.json({ + success: false, + message: "Gagal mengambil pasar desa: " + (e instanceof Error ? e.message : 'Unknown error'), + }, { + status: 500, + }); + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/index.ts new file mode 100644 index 00000000..d4a0c262 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/index.ts @@ -0,0 +1,48 @@ +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("/find-many", pasarDesaFindMany) + .get("/:id", async (context) => { + const response = await pasarDesaFindUnique(new Request(context.request)); + return response; + }) + .post("/create", pasarDesaCreate, { + body: t.Object({ + nama: t.String(), + harga: t.Number(), + satuan: t.String(), + alamat: t.String(), + imageId: t.String(), + rating: t.Number(), + kategoriId:t.String(), + }), + }) + .delete("/del/:id", pasarDesaDelete) + .put( + "/:id", + async (context) => { + const response = await pasarDesaUpdate(context); + return response; + }, + { + body: t.Object({ + nama: t.String(), + harga: t.Number(), + satuan: t.String(), + alamat: t.String(), + imageId: t.String(), + rating: t.Number(), + kategoriId: t.String(), + }), + } + ); + +export default PasarDesa; diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/updt.ts new file mode 100644 index 00000000..7862fab2 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/updt.ts @@ -0,0 +1,98 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; +import fs from "fs/promises"; +import path from "path"; + +type FormUpdate = { + nama: string; + harga: number; + satuan: string; + alamat: string; + imageId: string; + rating: number; + kategoriId: string; // Array of KategoriMakanan IDs + }; + +export default async function pasarDesaUpdate(context: Context){ + try { + const id = context.params?.id; + const body = context.body as FormUpdate; + + const { nama, harga, satuan, alamat, imageId, rating, kategoriId } = body; + + if (!id) { + return Response.json({ + success: false, + message: "ID tidak boleh kosong", + }, { status: 400 }); + } + + const existing = await prisma.pasarDesa.findUnique({ + where: { id }, + include: { + image: true, + kategori: true, + }, + }) + + if (!existing) { + return Response.json({ + success: false, + message: "Pasar desa tidak ditemukan", + }, { status: 404 }); + } + + if (existing.imageId && existing.imageId !== imageId) { + const oldImage = existing.image; + if (oldImage) { + try { + const filePath = path.join(oldImage.path, oldImage.name); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: oldImage.id }, + }); + } catch (err) { + console.error("Gagal hapus gambar lama:", err); + } + } + } + + // First, update the main PasarDesa record + await prisma.pasarDesa.update({ + where: { id }, + data: { + nama, + harga, + satuan, + alamat, + imageId, + rating, + kategoriId, + }, + }); + + // Fetch the updated record with all relations + const updated = await prisma.pasarDesa.findUnique({ + where: { id }, + include: { + image: true, + kategori: true, + } + }); + return Response.json({ + success: true, + message: "Success update pasar desa", + data: updated, + }, { + status: 200, + }); + } catch (e) { + console.error("Update error:", e); + return Response.json({ + success: false, + message: "Gagal mengupdate pasar desa: " + (e instanceof Error ? e.message : 'Unknown error'), + }, { + status: 500, + }); + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/route.ts b/src/app/api/[[...slugs]]/route.ts index ae8d1408..6a54aa90 100644 --- a/src/app/api/[[...slugs]]/route.ts +++ b/src/app/api/[[...slugs]]/route.ts @@ -18,6 +18,7 @@ import uplImg from "./_lib/upl-img"; import { uplImgSingle } from "./_lib/upl-img-single"; import FileStorage from "./_lib/fileStorage"; import Keamanan from "./_lib/keamanan"; +import Ekonomi from "./_lib/ekonomi"; const ROOT = process.cwd(); @@ -79,6 +80,7 @@ const ApiServer = new Elysia() .use(Keamanan) .use(Utils) .use(FileStorage) + .use(Ekonomi) .onError(({ code }) => { if (code === "NOT_FOUND") { return {