feat(umkm): implement full CRUD for product categories
- added CRUD endpoints for KategoriProduk in Elysia API - updated umkmState with category management logic - added 'Kategori Produk' tab in admin dashboard - created list, create, and edit pages for category management - bumped version to 0.1.32
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "desa-darmasaba",
|
"name": "desa-darmasaba",
|
||||||
"version": "0.1.31",
|
"version": "0.1.32",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -63,6 +63,15 @@ const defaultPenjualanForm = {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Kategori Produk Form Validation
|
||||||
|
const kategoriProdukFormSchema = z.object({
|
||||||
|
nama: z.string().min(1, "Nama kategori wajib diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultKategoriProdukForm = {
|
||||||
|
nama: "",
|
||||||
|
};
|
||||||
|
|
||||||
export const umkmState = proxy({
|
export const umkmState = proxy({
|
||||||
// UMKM Module
|
// UMKM Module
|
||||||
umkm: {
|
umkm: {
|
||||||
@@ -101,7 +110,7 @@ export const umkmState = proxy({
|
|||||||
loading: false,
|
loading: false,
|
||||||
async submit() {
|
async submit() {
|
||||||
const cek = umkmFormSchema.safeParse(this.form);
|
const cek = umkmFormSchema.safeParse(this.form);
|
||||||
if (!cek.success) return toast.error("Cek kembali form anda");
|
if (!cek.success) { toast.error("Cek kembali form anda"); return false; }
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/ekonomi/umkm/create", {
|
const res = await fetch("/api/ekonomi/umkm/create", {
|
||||||
@@ -129,7 +138,7 @@ export const umkmState = proxy({
|
|||||||
loading: false,
|
loading: false,
|
||||||
async submit(id: string) {
|
async submit(id: string) {
|
||||||
const cek = umkmFormSchema.safeParse(this.form);
|
const cek = umkmFormSchema.safeParse(this.form);
|
||||||
if (!cek.success) return toast.error("Cek kembali form anda");
|
if (!cek.success) { toast.error("Cek kembali form anda"); return false; }
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/ekonomi/umkm/${id}`, {
|
const res = await fetch(`/api/ekonomi/umkm/${id}`, {
|
||||||
@@ -237,7 +246,7 @@ export const umkmState = proxy({
|
|||||||
loading: false,
|
loading: false,
|
||||||
async submit() {
|
async submit() {
|
||||||
const cek = produkFormSchema.safeParse(this.form);
|
const cek = produkFormSchema.safeParse(this.form);
|
||||||
if (!cek.success) return toast.error("Cek kembali form anda");
|
if (!cek.success) { toast.error("Cek kembali form anda"); return false; }
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/ekonomi/umkm/produk/create", {
|
const res = await fetch("/api/ekonomi/umkm/produk/create", {
|
||||||
@@ -260,7 +269,7 @@ export const umkmState = proxy({
|
|||||||
loading: false,
|
loading: false,
|
||||||
async submit(id: string) {
|
async submit(id: string) {
|
||||||
const cek = produkFormSchema.safeParse(this.form);
|
const cek = produkFormSchema.safeParse(this.form);
|
||||||
if (!cek.success) return toast.error("Cek kembali form anda");
|
if (!cek.success) { toast.error("Cek kembali form anda"); return false; }
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/ekonomi/umkm/produk/${id}`, {
|
const res = await fetch(`/api/ekonomi/umkm/produk/${id}`, {
|
||||||
@@ -323,7 +332,7 @@ export const umkmState = proxy({
|
|||||||
loading: false,
|
loading: false,
|
||||||
async submit() {
|
async submit() {
|
||||||
const cek = penjualanFormSchema.safeParse(this.form);
|
const cek = penjualanFormSchema.safeParse(this.form);
|
||||||
if (!cek.success) return toast.error("Cek kembali form anda");
|
if (!cek.success) { toast.error("Cek kembali form anda"); return false; }
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/ekonomi/umkm/penjualan/create", {
|
const res = await fetch("/api/ekonomi/umkm/penjualan/create", {
|
||||||
@@ -368,6 +377,31 @@ export const umkmState = proxy({
|
|||||||
|
|
||||||
// Kategori Produk (Share with Pasar Desa)
|
// Kategori Produk (Share with Pasar Desa)
|
||||||
kategoriProduk: {
|
kategoriProduk: {
|
||||||
|
findMany: {
|
||||||
|
data: [] as any[],
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
async load(page = 1, limit = 10, search = "") {
|
||||||
|
this.loading = true;
|
||||||
|
this.page = page;
|
||||||
|
this.search = search;
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: page.toString(),
|
||||||
|
limit: limit.toString(),
|
||||||
|
search
|
||||||
|
});
|
||||||
|
const res = await fetch(`/api/ekonomi/kategoriproduk/find-many?${params}`);
|
||||||
|
const result = await res.json();
|
||||||
|
if (result.success) {
|
||||||
|
this.data = result.data;
|
||||||
|
this.totalPages = result.totalPages;
|
||||||
|
}
|
||||||
|
} catch (e) { console.error(e); } finally { this.loading = false; }
|
||||||
|
}
|
||||||
|
},
|
||||||
findManyAll: {
|
findManyAll: {
|
||||||
data: [] as any[],
|
data: [] as any[],
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -381,6 +415,75 @@ export const umkmState = proxy({
|
|||||||
}
|
}
|
||||||
} catch (e) { console.error(e); } finally { this.loading = false; }
|
} catch (e) { console.error(e); } finally { this.loading = false; }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
form: { ...defaultKategoriProdukForm },
|
||||||
|
loading: false,
|
||||||
|
async submit() {
|
||||||
|
const cek = kategoriProdukFormSchema.safeParse(this.form);
|
||||||
|
if (!cek.success) { toast.error("Nama kategori wajib diisi"); return false; }
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/ekonomi/kategoriproduk/create", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(this.form)
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Kategori berhasil dibuat");
|
||||||
|
umkmState.kategoriProduk.findMany.load();
|
||||||
|
umkmState.kategoriProduk.findManyAll.load();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
toast.error(result.message || "Gagal membuat kategori");
|
||||||
|
} catch (e) { toast.error("Gagal membuat kategori"); } finally { this.loading = false; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
form: { ...defaultKategoriProdukForm },
|
||||||
|
loading: false,
|
||||||
|
async submit(id: string) {
|
||||||
|
const cek = kategoriProdukFormSchema.safeParse(this.form);
|
||||||
|
if (!cek.success) { toast.error("Nama kategori wajib diisi"); return false; }
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/ekonomi/kategoriproduk/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(this.form)
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Kategori berhasil diperbarui");
|
||||||
|
umkmState.kategoriProduk.findMany.load();
|
||||||
|
umkmState.kategoriProduk.findManyAll.load();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
toast.error(result.message || "Gagal memperbarui kategori");
|
||||||
|
} catch (e) { toast.error("Gagal memperbarui kategori"); } finally { this.loading = false; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
del: {
|
||||||
|
loading: false,
|
||||||
|
async submit(id: string) {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/ekonomi/kategoriproduk/del/${id}`, {
|
||||||
|
method: "DELETE"
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Kategori berhasil dihapus");
|
||||||
|
umkmState.kategoriProduk.findMany.load();
|
||||||
|
umkmState.kategoriProduk.findManyAll.load();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) { toast.error("Gagal menghapus kategori"); } finally { this.loading = false; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
TabsTab,
|
TabsTab,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconDashboard, IconBuildingStore, IconPackage, IconShoppingCart } from '@tabler/icons-react';
|
import { IconDashboard, IconBuildingStore, IconPackage, IconShoppingCart, IconTag } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
@@ -44,6 +44,12 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
href: "/admin/ekonomi/umkm/penjualan",
|
href: "/admin/ekonomi/umkm/penjualan",
|
||||||
icon: <IconShoppingCart size={18} stroke={1.8} />
|
icon: <IconShoppingCart size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Kategori Produk",
|
||||||
|
value: "kategori-produk",
|
||||||
|
href: "/admin/ekonomi/umkm/kategori-produk",
|
||||||
|
icon: <IconTag size={18} stroke={1.8} />
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const currentTab = tabs.find((tab) => pathname.startsWith(tab.href));
|
const currentTab = tabs.find((tab) => pathname.startsWith(tab.href));
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
'use client';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Center,
|
||||||
|
Loader
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import umkmState from '../../../../../_state/ekonomi/umkm/umkm';
|
||||||
|
|
||||||
|
export default function EditKategoriProduk() {
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const id = params.id as string;
|
||||||
|
const state = useProxy(umkmState.kategoriProduk.findMany);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [nama, setNama] = useState("");
|
||||||
|
const [originalNama, setOriginalNama] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const init = async () => {
|
||||||
|
// Find the item from the existing list or load it if not available
|
||||||
|
if (state.data.length === 0) {
|
||||||
|
await state.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = state.data.find((v: any) => v.id === id);
|
||||||
|
if (item) {
|
||||||
|
setNama(item.nama);
|
||||||
|
setOriginalNama(item.nama);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
}, [id, state]);
|
||||||
|
|
||||||
|
const handleResetForm = () => {
|
||||||
|
setNama(originalNama);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
umkmState.kategoriProduk.update.form.nama = nama;
|
||||||
|
const success = await umkmState.kategoriProduk.update.submit(id);
|
||||||
|
if (success) {
|
||||||
|
router.push('/admin/ekonomi/umkm/kategori-produk');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Center h={400}>
|
||||||
|
<Loader size="lg" />
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Group mb="lg">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={20} />}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
<Title order={3}>Edit Kategori Produk</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper withBorder p="xl" radius="md" shadow="sm" maw={600}>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Kategori"
|
||||||
|
placeholder="Contoh: Makanan, Minuman, Kerajinan"
|
||||||
|
required
|
||||||
|
value={nama}
|
||||||
|
onChange={(e) => setNama(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="flex-end" mt="xl">
|
||||||
|
<Button variant="outline" color="gray" onClick={handleResetForm}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="blue"
|
||||||
|
onClick={handleUpdate}
|
||||||
|
loading={isSubmitting}
|
||||||
|
disabled={!nama.trim()}
|
||||||
|
>
|
||||||
|
Simpan Perubahan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
'use client';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import umkmState from '../../../../_state/ekonomi/umkm/umkm';
|
||||||
|
|
||||||
|
export default function CreateKategoriProduk() {
|
||||||
|
const router = useRouter();
|
||||||
|
const state = useProxy(umkmState.kategoriProduk.create);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const handleResetForm = () => {
|
||||||
|
state.form = {
|
||||||
|
nama: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = async () => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
const success = await umkmState.kategoriProduk.create.submit();
|
||||||
|
if (success) {
|
||||||
|
handleResetForm();
|
||||||
|
router.push('/admin/ekonomi/umkm/kategori-produk');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Group mb="lg">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={20} />}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
<Title order={3}>Tambah Kategori Produk Baru</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper withBorder p="xl" radius="md" shadow="sm" maw={600}>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Kategori"
|
||||||
|
placeholder="Contoh: Makanan, Minuman, Kerajinan"
|
||||||
|
required
|
||||||
|
value={state.form.nama}
|
||||||
|
onChange={(e) => (state.form.nama = e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="flex-end" mt="xl">
|
||||||
|
<Button variant="outline" color="gray" onClick={handleResetForm}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="blue"
|
||||||
|
onClick={handleCreate}
|
||||||
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
Simpan Kategori
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
144
src/app/admin/(dashboard)/ekonomi/umkm/kategori-produk/page.tsx
Normal file
144
src/app/admin/(dashboard)/ekonomi/umkm/kategori-produk/page.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
'use client'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Title,
|
||||||
|
TextInput,
|
||||||
|
Badge
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconPlus, IconSearch, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import umkmState from '../../../_state/ekonomi/umkm/umkm';
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function KategoriProdukPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const state = useProxy(umkmState.kategoriProduk.findMany);
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.load(state.page, 10, debouncedSearch);
|
||||||
|
}, [state.page, debouncedSearch]);
|
||||||
|
|
||||||
|
const handleHapus = async () => {
|
||||||
|
if (selectedId) {
|
||||||
|
const success = await umkmState.kategoriProduk.del.submit(selectedId);
|
||||||
|
if (success) {
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Title order={3}>Kategori Produk</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/umkm/kategori-produk/create')}
|
||||||
|
>
|
||||||
|
Tambah Kategori
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper withBorder p="md" radius="md">
|
||||||
|
<TextInput
|
||||||
|
placeholder="Cari kategori..."
|
||||||
|
leftSection={<IconSearch size={18} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{state.loading ? (
|
||||||
|
<Skeleton height={400} />
|
||||||
|
) : (
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Kategori</TableTh>
|
||||||
|
<TableTh>Status</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{state.data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd fw={500}>{item.nama}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Badge color={item.isActive ? "green" : "red"}>
|
||||||
|
{item.isActive ? "Aktif" : "Nonaktif"}
|
||||||
|
</Badge>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
color="blue"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/umkm/kategori-produk/${item.id}/edit`)}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Center mt="md">
|
||||||
|
<Pagination
|
||||||
|
total={state.totalPages}
|
||||||
|
value={state.page}
|
||||||
|
onChange={(p) => state.load(p, 10, debouncedSearch)}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah Anda yakin ingin menghapus kategori ini? Kategori yang dihapus tidak akan muncul di pilihan kategori produk baru."
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KategoriProdukPage;
|
||||||
@@ -9,7 +9,7 @@ import DemografiPekerjaan from "./demografi-pekerjaan";
|
|||||||
import JumlahPengangguran from "./jumlah-pengangguran";
|
import JumlahPengangguran from "./jumlah-pengangguran";
|
||||||
import PendapatanAsliDesa from "./pendapatan-asli-desa";
|
import PendapatanAsliDesa from "./pendapatan-asli-desa";
|
||||||
import StrukturOrganisasi from "./struktur-bumdes";
|
import StrukturOrganisasi from "./struktur-bumdes";
|
||||||
import KategoriProduk from "./kategori-produk";
|
import KategoriProduk from "./umkm/kategori-produk/kategori-produk";
|
||||||
import Umkm from "./umkm";
|
import Umkm from "./umkm";
|
||||||
import ProdukUmkm from "./umkm/produk";
|
import ProdukUmkm from "./umkm/produk";
|
||||||
import PenjualanProduk from "./umkm/penjualan";
|
import PenjualanProduk from "./umkm/penjualan";
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import prisma from "@/lib/prisma";
|
|
||||||
import Elysia from "elysia";
|
|
||||||
|
|
||||||
const KategoriProduk = new Elysia({
|
|
||||||
prefix: "/kategoriproduk",
|
|
||||||
})
|
|
||||||
.get("/find-many-all", async () => {
|
|
||||||
try {
|
|
||||||
const data = await prisma.kategoriProduk.findMany({
|
|
||||||
where: {
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
orderBy: { nama: 'asc' },
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "Berhasil mengambil semua kategori produk",
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error di KategoriProduk find-many-all:", e);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Gagal mengambil data kategori produk",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default KategoriProduk;
|
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
|
||||||
|
const KategoriProduk = new Elysia({
|
||||||
|
prefix: "/kategoriproduk",
|
||||||
|
})
|
||||||
|
.get("/find-many-all", async () => {
|
||||||
|
try {
|
||||||
|
const data = await prisma.kategoriProduk.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: { nama: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mengambil semua kategori produk",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di KategoriProduk find-many-all:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data kategori produk",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get("/find-many", async ({ query }) => {
|
||||||
|
try {
|
||||||
|
const { page = 1, limit = 10, search = "" } = query;
|
||||||
|
const skip = (Number(page) - 1) * Number(limit);
|
||||||
|
const take = Number(limit);
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
nama: { contains: search, mode: 'insensitive' as const },
|
||||||
|
};
|
||||||
|
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.kategoriProduk.findMany({
|
||||||
|
where,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
}),
|
||||||
|
prisma.kategoriProduk.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mengambil data kategori produk",
|
||||||
|
data,
|
||||||
|
total,
|
||||||
|
page: Number(page),
|
||||||
|
limit: Number(limit),
|
||||||
|
totalPages: Math.ceil(total / take),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di KategoriProduk find-many:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data kategori produk",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
page: t.Optional(t.String()),
|
||||||
|
limit: t.Optional(t.String()),
|
||||||
|
search: t.Optional(t.String()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.post("/create", async ({ body }) => {
|
||||||
|
try {
|
||||||
|
const data = await prisma.kategoriProduk.create({
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil membuat kategori produk",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di KategoriProduk create:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal membuat kategori produk",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.put("/:id", async ({ params, body }) => {
|
||||||
|
try {
|
||||||
|
const data = await prisma.kategoriProduk.update({
|
||||||
|
where: { id: params.id },
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil memperbarui kategori produk",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di KategoriProduk update:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal memperbarui kategori produk",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
params: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
}),
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.delete("/del/:id", async ({ params }) => {
|
||||||
|
try {
|
||||||
|
const data = await prisma.kategoriProduk.update({
|
||||||
|
where: { id: params.id },
|
||||||
|
data: {
|
||||||
|
isActive: false,
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil menghapus kategori produk",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di KategoriProduk delete:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal menghapus kategori produk",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
params: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export default KategoriProduk;
|
||||||
Reference in New Issue
Block a user