API & UI Menu Inovasi & Submenu Layan Online Desa 2 tabs

This commit is contained in:
2025-07-16 00:20:55 +08:00
parent e8ad74d118
commit 55433128a9
45 changed files with 2566 additions and 399 deletions

View File

@@ -275,8 +275,7 @@ const kategoriProduk = proxy({
data: null as Array<{
id: string;
nama: string;
}>
| null,
}> | null,
async load() {
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
if (res.status === 200) {
@@ -335,125 +334,135 @@ const kategoriProduk = proxy({
}
},
},
edit: {
id: "",
form: { ...kategoriProdukDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
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}`);
}
try {
const response = await fetch(`/api/ekonomi/kategoriproduk/${id}`, {
method: "GET",
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",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
body: JSON.stringify({
nama: kategoriProduk.edit.form.nama,
}),
}
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;
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
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');
}
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) {
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;
// 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");
}
}
},
reset() {
kategoriProduk.edit.id = "";
kategoriProduk.edit.form = { ...kategoriProdukDefaultForm };
},
} 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
kategoriProduk,
});
export default pasarDesaState;

View File

@@ -0,0 +1,123 @@
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({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
alamat: z.string().min(1).max(5000),
namaIde: z.string().min(1).max(5000),
masalah: z.string().min(1).max(5000),
benefit: z.string().min(1).max(5000),
});
const defaultForm = {
name: "",
deskripsi: "",
alamat: "",
namaIde: "",
masalah: "",
benefit: "",
};
const ajukanIdeInovatifState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(ajukanIdeInovatifState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
ajukanIdeInovatifState.create.loading = true;
const res = await ApiFetch.api.inovasi.ajukanideinovatif["create"].post(
ajukanIdeInovatifState.create.form
);
if (res.status === 200) {
ajukanIdeInovatifState.findMany.load();
return toast.success("Ajukan Ide Inovatif berhasil di kirim");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
ajukanIdeInovatifState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.AjukanIdeInovatifGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.ajukanideinovatif["find-many"].get();
if (res.status === 200) {
ajukanIdeInovatifState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.AjukanIdeInovatifGetPayload<{
omit: {
isActive: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/ajukanideinovatif/${id}`);
if (res.ok) {
const data = await res.json();
ajukanIdeInovatifState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
ajukanIdeInovatifState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading ajukan ide inovatif:", error);
ajukanIdeInovatifState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
ajukanIdeInovatifState.delete.loading = true;
const response = await fetch(`/api/inovasi/ajukanideinovatif/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Ajukan Ide Inovatif berhasil dihapus");
await ajukanIdeInovatifState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus ajukan ide inovatif");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus ajukan ide inovatif");
} finally {
ajukanIdeInovatifState.delete.loading = false;
}
},
},
});
export default ajukanIdeInovatifState;

View File

@@ -0,0 +1,403 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= ADMINISTRASI ONLINE ========================================= //
const templateAdministrasiOnlineForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor telepon minimal 1 karakter"),
jenisLayananId: z.string().min(1, "Jenis layanan minimal 1 karakter"),
});
const defaultAdministrasiOnlineForm = {
name: "",
alamat: "",
nomorTelepon: "",
jenisLayananId: "",
};
const administrasiOnline = proxy({
create: {
form: { ...defaultAdministrasiOnlineForm },
loading: false,
async create() {
const cek = templateAdministrasiOnlineForm.safeParse(
administrasiOnline.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
administrasiOnline.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
"create"
].post(administrasiOnline.create.form);
if (res.status === 200) {
administrasiOnline.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 {
administrasiOnline.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.AdministrasiOnlineGetPayload<{
include: {
jenisLayanan: true;
};
}>
> | null,
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
administrasiOnline.findMany.loading = true;
administrasiOnline.findMany.page = page;
try {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
"find-many"
].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
administrasiOnline.findMany.data = res.data.data ?? [];
administrasiOnline.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch administrasi online paginated:", err);
} finally {
administrasiOnline.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.AdministrasiOnlineGetPayload<{
include: {
jenisLayanan: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/${id}`
);
if (res.ok) {
const data = await res.json();
administrasiOnline.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch administrasi online:", res.statusText);
administrasiOnline.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching administrasi online:", error);
administrasiOnline.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
administrasiOnline.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Administrasi online berhasil dihapus"
);
await administrasiOnline.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus administrasi online");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus administrasi online");
} finally {
administrasiOnline.delete.loading = false;
}
},
},
});
// ========================================= JENIS LAYANAN ========================================= //
const templateJenisLayananForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
});
const defaultJenisLayananForm = {
nama: "",
deskripsi: "",
};
const jenisLayanan = proxy({
create: {
form: { ...defaultJenisLayananForm },
loading: false,
async create() {
const cek = templateJenisLayananForm.safeParse(jenisLayanan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jenisLayanan.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"create"
].post(jenisLayanan.create.form);
if (res.status === 200) {
jenisLayanan.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 {
jenisLayanan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
deskripsi: string;
}> | null,
async load() {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"find-many"
].get();
if (res.status === 200) {
jenisLayanan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.JenisLayananGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${id}`
);
if (res.ok) {
const data = await res.json();
jenisLayanan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jenisLayanan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
jenisLayanan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jenisLayanan.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Jenis layanan berhasil dihapus");
await jenisLayanan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus jenis layanan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jenis layanan");
} finally {
jenisLayanan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultJenisLayananForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${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,
deskripsi: data.deskripsi,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading jenis layanan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateJenisLayananForm.safeParse(jenisLayanan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
jenisLayanan.edit.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${jenisLayanan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: jenisLayanan.edit.form.nama,
deskripsi: jenisLayanan.edit.form.deskripsi,
}),
}
);
// 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 jenis layanan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui jenis layanan"
);
await jenisLayanan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate jenis layanan");
}
} 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 jenis layanan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate jenis layanan"
);
return false;
} finally {
jenisLayanan.edit.loading = false;
}
},
reset() {
jenisLayanan.edit.id = "";
jenisLayanan.edit.form = { ...defaultJenisLayananForm };
},
},
});
const layananonlineDesa = proxy({
administrasiOnline,
jenisLayanan,
});
export default layananonlineDesa;

View File

@@ -0,0 +1,108 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import ajukanIdeInovatifState from '../../../_state/inovasi/ajukan-ide-inovatif';
function DetailAjukanIdeInofativDesa() {
const state = useProxy(ajukanIdeInovatifState)
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter()
const params = useParams()
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
const handleHapus = () => {
if (selectedId) {
state.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/inovasi/ajukan-ide-inovatif")
}
}
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Flex justify="space-between" gap={"xs"}>
<Text fz={"xl"} fw={"bold"}>Detail Ajukan Ide Inovatif Desa</Text>
<Button
onClick={() => {
if (state.findUnique.data) {
setSelectedId(state.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={state.delete.loading || !state.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
</Flex>
{state.findUnique.data ? (
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Nama</Text>
<Text fz={"lg"}>{state.findUnique.data?.name}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.alamat }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Nama Ide Inovatif</Text>
<Text fz={"lg"}>{state.findUnique.data?.namaIde}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Masalah</Text>
<Text fz={"lg"}>{state.findUnique.data?.masalah}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Benefit</Text>
<Text fz={"lg"}>{state.findUnique.data?.benefit}</Text>
</Box>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus ajukan ide inovatif ini?'
/>
</Box>
);
}
export default DetailAjukanIdeInofativDesa;

View File

@@ -1,59 +1,91 @@
'use client'
import colors from '@/con/colors';
import { Box, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react';
import React from 'react';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, 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 ajukanIdeInovatifState from '../../_state/inovasi/ajukan-ide-inovatif';
function AjukanIdeInofativ() {
function AjukanIdeInovatif() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Ajukan Ide Inovatif'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListAjukanIdeInovatif />
<ListAjukanIdeInovatif search={search} />
</Box>
);
}
function ListAjukanIdeInovatif() {
function ListAjukanIdeInovatif({ search }: { search: string }) {
const state = useProxy(ajukanIdeInovatifState)
const router = useRouter()
useShallowEffect(() => {
state.findMany.load()
}, [])
const filteredData = (state.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword) ||
item.namaIde.toLowerCase().includes(keyword) ||
item.masalah.toLowerCase().includes(keyword) ||
item.benefit.toLowerCase().includes(keyword)
);
});
if (!state.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>List Ajukan Ide Inovatif</Title>
<Box>
<Table striped withRowBorders withColumnBorders withTableBorder>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Nama Ide Inovatif</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Masalah yang ingin diatasi</TableTh>
<TableTh>Benefit</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>1</TableTd>
<TableTd>nama</TableTd>
<TableTd>alamat</TableTd>
<TableTd>ide inovatif</TableTd>
<TableTd>deskripsi</TableTd>
<TableTd>masalah</TableTd>
<TableTd>benefit</TableTd>
</TableTr>
</TableTbody>
</Table> </Box>
</Stack>
<Title mb={10} order={3}>List Ajukan Ide Inovatif</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Nama Ide Inovatif</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.alamat }} />
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.namaIde }} />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/inovasi/ajukan-ide-inovatif/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Box>
)
);
}
export default AjukanIdeInofativ;
export default AjukanIdeInovatif;

View File

@@ -1,42 +0,0 @@
'use client'
import colors from "@/con/colors";
import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from "@mantine/core";
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
// import { KeamananEditor } from "../../../keamanan/_com/keamananEditor";
export default function EditLayananOnlineDesa() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Edit Layanan Online Desa</Title>
<Box>
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
<IconImageInPicture size={50} />
</Box>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Layanan Online Desa</Text>}
placeholder='Masukkan nama LayananOnlineDesa'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Layanan Online Desa</Text>
{/* <KeamananEditor
showSubmit={false}
/> */}
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,66 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailLayananOnlineDesa() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Layanan Online Desa</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
<Image src={"/"} alt="gambar" />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Nama Layanan Online Desa</Text>
<Text fz={"lg"}>Test Judul</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
<Text fz={"lg"}>Test Deskripsi</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Konten</Text>
<Text fz={"lg"} >Test Konten</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/inovasi/layanan-online-desa/edit')} color="green">
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus potensi ini?"
/> */}
</Box>
);
}
export default DetailLayananOnlineDesa;

View File

@@ -0,0 +1,73 @@
/* 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 LayoutTabsLayananOnlineDesa({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Administrasi Online",
value: "administrasionline",
href: "/admin/inovasi/layanan-online-desa/administrasi-online"
},
{
label: "Jenis Layanan",
value: "jenislayanan",
href: "/admin/inovasi/layanan-online-desa/jenis-layanan"
},
{
label: "Pengaduan Masyarakat",
value: "pengaduanmasyarakat",
href: "/admin/inovasi/layanan-online-desa/pengaduan-masyarakat"
},
{
label: "Informasi Desa",
value: "informasidesa",
href: "/admin/inovasi/layanan-online-desa/informasi-desa"
}
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(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 (
<Stack>
<Title order={3}>Layanan Online Desa</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabsLayananOnlineDesa;

View File

@@ -0,0 +1,104 @@
'use client'
import { useProxy } from 'valtio/utils';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconTrash } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
import colors from '@/con/colors';
function DetailAdministrasiOnline() {
const beritaState = useProxy(layananonlineDesa)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
useShallowEffect(() => {
beritaState.administrasiOnline.findUnique.load(params?.id as string)
}, [])
const handleHapus = () => {
if (selectedId) {
beritaState.administrasiOnline.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/inovasi/layanan-online-desa/administrasi-online")
}
}
if (!beritaState.administrasiOnline.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={40} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Flex gap={"xs"} justify={"space-between"} mt={10}>
<Text fz={"xl"} fw={"bold"}>Detail Administrasi Online</Text>
<Button
onClick={() => {
if (beritaState.administrasiOnline.findUnique.data) {
setSelectedId(beritaState.administrasiOnline.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={beritaState.administrasiOnline.delete.loading || !beritaState.administrasiOnline.findUnique.data}
color={"red"}
>
<IconTrash size={20} />
</Button>
</Flex>
{beritaState.administrasiOnline.findUnique.data ? (
<Paper key={beritaState.administrasiOnline.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Nama</Text>
<Text fz={"lg"}>{beritaState.administrasiOnline.findUnique.data?.name}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
<Text fz={"lg"}>{beritaState.administrasiOnline.findUnique.data?.alamat}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Nomor Telepon</Text>
<Text fz={"lg"} >{beritaState.administrasiOnline.findUnique.data?.nomorTelepon}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Jenis Layanan</Text>
<Text fz={"lg"} >{beritaState.administrasiOnline.findUnique.data?.jenisLayanan?.nama}</Text>
</Box>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus administrasi online ini?'
/>
</Box>
);
}
export default DetailAdministrasiOnline;

View File

@@ -0,0 +1,99 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import layananonlineDesa from '../../../_state/inovasi/layanan-online-desa';
function AdministrasiOnline() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Administrasi Online'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListAdministrasiOnline search={search} />
</Box>
);
}
function ListAdministrasiOnline({ search }: { search: string }) {
const listState = useProxy(layananonlineDesa.administrasiOnline)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany;
useShallowEffect(() => {
load(page, 10);
}, [page]);
const filteredData = (data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword) ||
item.nomorTelepon.toLowerCase().includes(keyword)
);
});
if (loading || !data) {
return <Skeleton h={500} />;
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={3} mb={10}>List Administrasi Online</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Layanan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Nomor Telepon</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody style={{ overflowX: "auto" }}>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>{item.nomorTelepon}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin//inovasi/layanan-online-desa/administrasi-online/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}
export default AdministrasiOnline;

View File

@@ -1,44 +0,0 @@
'use client'
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';
function CreateLayananOnlineDesa() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Layanan Online Desa</Title>
<Box>
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
<IconImageInPicture size={50} />
</Box>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Layanan Online Desa</Text>}
placeholder='Masukkan nama LayananOnlineDesa'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Layanan Online Desa</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateLayananOnlineDesa;

View File

@@ -0,0 +1,92 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } 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';
function EditJenisLayanan() {
const state = useProxy(layananonlineDesa.jenisLayanan)
const router = useRouter()
const params = useParams()
const [formData, setFormData] = useState({
nama: state.edit.form.nama,
deskripsi: state.edit.form.deskripsi,
})
useEffect(() => {
const loadJenisLayanan = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await state.edit.load(id);
if (data) {
setFormData({
nama: data.nama,
deskripsi: data.deskripsi,
});
}
} catch (error) {
console.error("Error loading jenis layanan:", error);
toast.error("Gagal memuat data jenis layanan");
}
};
loadJenisLayanan();
}, [params?.id]);
const handleSubmit = async () => {
try {
state.edit.form = {
...state.edit.form,
nama: formData.nama,
deskripsi: formData.deskripsi,
}
await state.edit.update()
toast.success("Jenis layanan berhasil diperbarui!")
router.push("/admin/inovasi/layanan-online-desa/jenis-layanan")
} catch (error) {
console.error("Error updating jenis layanan:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis layanan");
}
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit Jenis Layanan</Title>
<TextInput
value={formData.nama}
onChange={(val) => {
setFormData({ ...formData, nama: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Nama Jenis Layanan</Text>}
placeholder="masukkan nama jenis layanan"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData({ ...formData, deskripsi: htmlContent });
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditJenisLayanan;

View File

@@ -0,0 +1,103 @@
'use client'
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function DetailJenisLayanan() {
const state = useProxy(layananonlineDesa.jenisLayanan)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
const handleHapus = () => {
if (selectedId) {
state.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/inovasi/layanan-online-desa/jenis-layanan")
}
}
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Jenis Layanan</Text>
{state.findUnique.data ? (
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Nama</Text>
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi }}></Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (state.findUnique.data) {
setSelectedId(state.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={state.delete.loading || !state.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (state.findUnique.data) {
router.push(`/admin/inovasi/layanan-online-desa/jenis-layanan/${state.findUnique.data.id}/edit`);
}
}}
disabled={!state.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus jenis layanan ini?'
/>
</Box>
);
}
export default DetailJenisLayanan;

View File

@@ -0,0 +1,70 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function CreateJenisLayanan() {
const router = useRouter();
const statePasar = useProxy(layananonlineDesa.jenisLayanan)
useEffect(() => {
statePasar.findMany.load();
}, []);
const resetForm = () => {
statePasar.create.form = {
nama: "",
deskripsi: "",
};
}
const handleSubmit = async () => {
await statePasar.create.create();
resetForm();
router.push("/admin/inovasi/layanan-online-desa/jenis-layanan")
}
return (
<Box>
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Jenis Layanan</Title>
<TextInput
value={statePasar.create.form.nama}
onChange={(val) => {
statePasar.create.form.nama = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Layanan</Text>}
placeholder='Masukkan nama jenis layanan'
/>
<TextInput
value={statePasar.create.form.deskripsi}
onChange={(val) => {
statePasar.create.form.deskripsi = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>}
placeholder='Masukkan deskripsi'
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
</Box>
);
}
export default CreateJenisLayanan;

View File

@@ -0,0 +1,88 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, 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 JudulList from '../../../_com/judulList';
import layananonlineDesa from '../../../_state/inovasi/layanan-online-desa';
function JenisLayanan() {
const [search, setSearch] = useState("")
return (
<Box>
<HeaderSearch
title='Jenis Layanan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListJenisLayanan search={search} />
</Box>
);
}
function ListJenisLayanan({ search }: { search: string }) {
const stateList = useProxy(layananonlineDesa.jenisLayanan)
const router = useRouter()
useShallowEffect(() => {
stateList.findMany.load()
}, [])
const filteredData = (stateList.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
if (!stateList.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Jenis Layanan'
href='/admin/inovasi/layanan-online-desa/jenis-layanan/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Jenis Layanan</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.nama}</TableTd>
<TableTd>{item.deskripsi}</TableTd>
<TableTd>
<Button color="green" onClick={() => router.push(`/admin/inovasi/layanan-online-desa/jenis-layanan/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default JenisLayanan;

View File

@@ -0,0 +1,12 @@
'use client'
import LayoutTabsLayananOnlineDesa from "./_lib/layoutTabs";
function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsLayananOnlineDesa>
{children}
</LayoutTabsLayananOnlineDesa>
);
}
export default Layout;

View File

@@ -1,56 +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 LayananOnlineDesa() {
return (
<Box>
<HeaderSearch
title='Layanan Online Desa'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
/>
<ListLayananOnlineDesa/>
</Box>
);
}
function ListLayananOnlineDesa() {
const router = useRouter();
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Layanan Online Desa'
href='/admin/inovasi/layanan-online-desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Layanan</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Layanan Online Desa 1</TableTd>
<TableTd>Deskripsi Layanan Online Desa 1</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/inovasi/layanan-online-desa/detail')}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default LayananOnlineDesa;

View File

@@ -276,7 +276,7 @@ export const navBar = [
{
id: "Inovasi_2",
name: "Layanan Online Desa",
path: "/admin/inovasi/layanan-online-desa"
path: "/admin/inovasi/layanan-online-desa/administrasi-online"
},
{
id: "Inovasi_3",