Fix Tampilab DesaAntiKorupsi Landing Page Mobile
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -54,19 +55,20 @@ const administrasiOnline = proxy({
|
|||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<
|
data: null as Array<
|
||||||
Prisma.AdministrasiOnlineGetPayload<{
|
Prisma.AdministrasiOnlineGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
jenisLayanan: true;
|
jenisLayanan: true;
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
page: 1,
|
page: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
search: '',
|
||||||
async load(page = 1, limit = 10) {
|
async load(page = 1, limit = 10, search = '') {
|
||||||
administrasiOnline.findMany.loading = true;
|
administrasiOnline.findMany.loading = true;
|
||||||
administrasiOnline.findMany.page = page;
|
administrasiOnline.findMany.page = page;
|
||||||
|
administrasiOnline.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
const res =
|
const res =
|
||||||
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
|
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
|
||||||
@@ -75,6 +77,7 @@ const administrasiOnline = proxy({
|
|||||||
query: {
|
query: {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
|
search,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,10 +94,10 @@ const administrasiOnline = proxy({
|
|||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.AdministrasiOnlineGetPayload<{
|
data: null as Prisma.AdministrasiOnlineGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
jenisLayanan: true;
|
jenisLayanan: true;
|
||||||
};
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
@@ -199,13 +202,37 @@ const jenisLayanan = proxy({
|
|||||||
nama: string;
|
nama: string;
|
||||||
deskripsi: string;
|
deskripsi: string;
|
||||||
}> | null,
|
}> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res =
|
totalPages: 1,
|
||||||
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
jenisLayanan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
jenisLayanan.findMany.page = page;
|
||||||
|
jenisLayanan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({ query });
|
||||||
if (res.status === 200) {
|
|
||||||
jenisLayanan.findMany.data = res.data?.data ?? [];
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
jenisLayanan.findMany.data = res.data.data ?? [];
|
||||||
|
jenisLayanan.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
jenisLayanan.findMany.data = [];
|
||||||
|
jenisLayanan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch jenis layanan paginated:", err);
|
||||||
|
jenisLayanan.findMany.data = [];
|
||||||
|
jenisLayanan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
jenisLayanan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -403,7 +430,9 @@ const templatePengaduanMasyarakatForm = z.object({
|
|||||||
nik: z.string().min(1, "NIK minimal 1 karakter"),
|
nik: z.string().min(1, "NIK minimal 1 karakter"),
|
||||||
judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"),
|
judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"),
|
||||||
lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"),
|
lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"),
|
||||||
deskripsiPengaduan: z.string().min(1, "Deskripsi pengaduan minimal 1 karakter"),
|
deskripsiPengaduan: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Deskripsi pengaduan minimal 1 karakter"),
|
||||||
jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"),
|
jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"),
|
||||||
imageId: z.string().min(1, "Image minimal 1 karakter"),
|
imageId: z.string().min(1, "Image minimal 1 karakter"),
|
||||||
});
|
});
|
||||||
@@ -455,13 +484,13 @@ const pengaduanMasyarakat = proxy({
|
|||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<
|
data: null as Array<
|
||||||
Prisma.PengaduanMasyarakatGetPayload<{
|
Prisma.PengaduanMasyarakatGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
jenisPengaduan: true;
|
jenisPengaduan: true;
|
||||||
image: true;
|
image: true;
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
page: 1,
|
page: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -493,11 +522,11 @@ const pengaduanMasyarakat = proxy({
|
|||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.PengaduanMasyarakatGetPayload<{
|
data: null as Prisma.PengaduanMasyarakatGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
jenisPengaduan: true;
|
jenisPengaduan: true;
|
||||||
image: true;
|
image: true;
|
||||||
};
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
@@ -507,7 +536,10 @@ const pengaduanMasyarakat = proxy({
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
pengaduanMasyarakat.findUnique.data = data.data ?? null;
|
pengaduanMasyarakat.findUnique.data = data.data ?? null;
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to fetch pengaduan masyarakat:", res.statusText);
|
console.error(
|
||||||
|
"Failed to fetch pengaduan masyarakat:",
|
||||||
|
res.statusText
|
||||||
|
);
|
||||||
pengaduanMasyarakat.findUnique.data = null;
|
pengaduanMasyarakat.findUnique.data = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -542,7 +574,9 @@ const pengaduanMasyarakat = proxy({
|
|||||||
);
|
);
|
||||||
await pengaduanMasyarakat.findMany.load(); // refresh list
|
await pengaduanMasyarakat.findMany.load(); // refresh list
|
||||||
} else {
|
} else {
|
||||||
toast.error(result?.message || "Gagal menghapus pengaduan masyarakat");
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus pengaduan masyarakat"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal delete:", error);
|
console.error("Gagal delete:", error);
|
||||||
@@ -567,7 +601,9 @@ const jenisPengaduan = proxy({
|
|||||||
form: { ...defaultJenisPengaduanForm },
|
form: { ...defaultJenisPengaduanForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.create.form);
|
const cek = templateJenisPengaduanForm.safeParse(
|
||||||
|
jenisPengaduan.create.form
|
||||||
|
);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -693,7 +729,7 @@ const jenisPengaduan = proxy({
|
|||||||
const data = result.data;
|
const data = result.data;
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
this.form = {
|
this.form = {
|
||||||
nama: data.nama
|
nama: data.nama,
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@@ -709,7 +745,9 @@ const jenisPengaduan = proxy({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.edit.form);
|
const cek = templateJenisPengaduanForm.safeParse(
|
||||||
|
jenisPengaduan.edit.form
|
||||||
|
);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -759,7 +797,9 @@ const jenisPengaduan = proxy({
|
|||||||
await jenisPengaduan.findMany.load(); // refresh list
|
await jenisPengaduan.findMany.load(); // refresh list
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || "Gagal mengupdate jenis pengaduan");
|
throw new Error(
|
||||||
|
result.message || "Gagal mengupdate jenis pengaduan"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If JSON parsing fails, try to get the response text for better error messages
|
// If JSON parsing fails, try to get the response text for better error messages
|
||||||
@@ -792,7 +832,6 @@ const jenisPengaduan = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const layananonlineDesa = proxy({
|
const layananonlineDesa = proxy({
|
||||||
administrasiOnline,
|
administrasiOnline,
|
||||||
jenisLayanan,
|
jenisLayanan,
|
||||||
|
|||||||
@@ -1,72 +1,148 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import {
|
||||||
|
ScrollArea,
|
||||||
|
Stack,
|
||||||
|
Tabs,
|
||||||
|
TabsList,
|
||||||
|
TabsPanel,
|
||||||
|
TabsTab,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
IconFileText,
|
||||||
|
IconListDetails,
|
||||||
|
IconMessage,
|
||||||
|
IconAlertCircle
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabsLayananOnlineDesa({ children }: { children: React.ReactNode }) {
|
function LayoutTabsLayananOnlineDesa({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
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: "Jenis Pengaduan",
|
|
||||||
value: "jenispengaduan",
|
|
||||||
href: "/admin/inovasi/layanan-online-desa/jenis-pengaduan"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
// ✅ Tambahin icon + tooltip biar konsisten sama versi berita
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tabs = [
|
||||||
if (tab) {
|
{
|
||||||
router.push(tab.href)
|
label: "Administrasi Online",
|
||||||
}
|
value: "administrasionline",
|
||||||
setActiveTab(value)
|
href: "/admin/inovasi/layanan-online-desa/administrasi-online",
|
||||||
|
icon: <IconFileText size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola administrasi online desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Jenis Layanan",
|
||||||
|
value: "jenislayanan",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/jenis-layanan",
|
||||||
|
icon: <IconListDetails size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Daftar jenis layanan desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pengaduan Masyarakat",
|
||||||
|
value: "pengaduanmasyarakat",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/pengaduan-masyarakat",
|
||||||
|
icon: <IconMessage size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Laporan pengaduan masyarakat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Jenis Pengaduan",
|
||||||
|
value: "jenispengaduan",
|
||||||
|
href: "/admin/inovasi/layanan-online-desa/jenis-pengaduan",
|
||||||
|
icon: <IconAlertCircle size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kategori/jenis pengaduan masyarakat"
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
if (match) {
|
|
||||||
setActiveTab(match.value)
|
|
||||||
}
|
|
||||||
}, [pathname])
|
|
||||||
|
|
||||||
return (
|
const handleTabChange = (value: string | null) => {
|
||||||
<Stack>
|
const tab = tabs.find(t => t.value === value);
|
||||||
<Title order={3}>Layanan Online Desa</Title>
|
if (tab) {
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
router.push(tab.href);
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
}
|
||||||
{tabs.map((e, i) => (
|
setActiveTab(value);
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
};
|
||||||
))}
|
|
||||||
</TabsList>
|
useEffect(() => {
|
||||||
{tabs.map((e, i) => (
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
<TabsPanel key={i} value={e.value}>
|
if (match) {
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
setActiveTab(match.value);
|
||||||
<></>
|
}
|
||||||
</TabsPanel>
|
}, [pathname]);
|
||||||
))}
|
|
||||||
</Tabs>
|
return (
|
||||||
{children}
|
<Stack gap="lg">
|
||||||
</Stack>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
);
|
Layanan Online Desa
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
color={colors['blue-button']}
|
||||||
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
{/* ✅ Scroll horizontal biar gak overflow */}
|
||||||
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip
|
||||||
|
key={i}
|
||||||
|
label={tab.tooltip}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
|
>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<>{children}</>
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabsLayananOnlineDesa;
|
export default LayoutTabsLayananOnlineDesa;
|
||||||
|
|||||||
@@ -1,92 +1,111 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconTrash } from '@tabler/icons-react';
|
import { IconArrowBack, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
import layananonlineDesa from '@/app/admin/(dashboard)/_state/inovasi/layanan-online-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
function DetailAdministrasiOnline() {
|
function DetailAdministrasiOnline() {
|
||||||
const beritaState = useProxy(layananonlineDesa)
|
const stateAdminOnline = useProxy(layananonlineDesa.administrasiOnline);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
beritaState.administrasiOnline.findUnique.load(params?.id as string)
|
stateAdminOnline.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
beritaState.administrasiOnline.delete.byId(selectedId)
|
stateAdminOnline.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/inovasi/layanan-online-desa/administrasi-online")
|
router.push("/admin/inovasi/layanan-online-desa/administrasi-online");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!beritaState.administrasiOnline.findUnique.data) {
|
if (!stateAdminOnline.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateAdminOnline.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Group justify='space-between' align='center' w={{ base: "100%", md: "50%" }} mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
{/* Tombol Kembali */}
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
<Stack>
|
<Button
|
||||||
<Flex gap={"xs"} justify={"space-between"} mt={10}>
|
color="red"
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Administrasi Online</Text>
|
onClick={() => {
|
||||||
<Button
|
setSelectedId(data.id);
|
||||||
onClick={() => {
|
setModalHapus(true);
|
||||||
if (beritaState.administrasiOnline.findUnique.data) {
|
}}
|
||||||
setSelectedId(beritaState.administrasiOnline.findUnique.data.id);
|
variant="light"
|
||||||
setModalHapus(true);
|
radius="md"
|
||||||
}
|
size="md"
|
||||||
}}
|
>
|
||||||
disabled={beritaState.administrasiOnline.delete.loading || !beritaState.administrasiOnline.findUnique.data}
|
<IconTrash size={20} />
|
||||||
color={"red"}
|
</Button>
|
||||||
>
|
</Tooltip>
|
||||||
<IconTrash size={20} />
|
</Group>
|
||||||
</Button>
|
{/* Konten Detail */}
|
||||||
</Flex>
|
<Paper
|
||||||
{beritaState.administrasiOnline.findUnique.data ? (
|
withBorder
|
||||||
<Paper key={beritaState.administrasiOnline.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Stack gap={"xs"}>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
radius="md"
|
||||||
<Text fz={"lg"}>{beritaState.administrasiOnline.findUnique.data?.name}</Text>
|
shadow="sm"
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Stack gap="md">
|
||||||
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"lg"}>{beritaState.administrasiOnline.findUnique.data?.alamat}</Text>
|
Detail Administrasi Online
|
||||||
</Box>
|
</Text>
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Nomor Telepon</Text>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Text fz={"lg"} >{beritaState.administrasiOnline.findUnique.data?.nomorTelepon}</Text>
|
<Stack gap="sm">
|
||||||
</Box>
|
<Box>
|
||||||
<Box>
|
<Text fz="lg" fw="bold">Nama</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Jenis Layanan</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
<Text fz={"lg"} >{beritaState.administrasiOnline.findUnique.data?.jenisLayanan?.nama}</Text>
|
</Box>
|
||||||
</Box>
|
|
||||||
</Stack>
|
<Box>
|
||||||
</Paper>
|
<Text fz="lg" fw="bold">Alamat</Text>
|
||||||
) : null}
|
<Text fz="md" c="dimmed">{data.alamat || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nomor Telepon</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.nomorTelepon || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Jenis Layanan</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.jenisLayanan?.nama || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -95,10 +114,10 @@ function DetailAdministrasiOnline() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus administrasi online ini?'
|
text="Apakah Anda yakin ingin menghapus administrasi online ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailAdministrasiOnline;
|
export default DetailAdministrasiOnline;
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -15,8 +32,8 @@ function AdministrasiOnline() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Administrasi Online'
|
title="Administrasi Online"
|
||||||
placeholder='pencarian'
|
placeholder="Cari nama layanan, alamat, atau nomor telepon..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,69 +44,117 @@ function AdministrasiOnline() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListAdministrasiOnline({ search }: { search: string }) {
|
function ListAdministrasiOnline({ search }: { search: string }) {
|
||||||
const listState = useProxy(layananonlineDesa.administrasiOnline)
|
const state = useProxy(layananonlineDesa.administrasiOnline);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {
|
|
||||||
data,
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = listState.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10);
|
load(page, 10, search);
|
||||||
}, [page]);
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.alamat.toLowerCase().includes(keyword) ||
|
|
||||||
item.nomorTelepon.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return <Skeleton h={500} />;
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={600} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Title order={3} mb={10}>List Administrasi Online</Title>
|
<Group justify="space-between" mb="md">
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Title order={4}>Daftar Administrasi Online</Title>
|
||||||
<TableThead>
|
<Tooltip label="Tambah Layanan Baru" withArrow>
|
||||||
<TableTr>
|
<Button
|
||||||
<TableTh>Nama Layanan</TableTh>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableTh>Alamat</TableTh>
|
color="blue"
|
||||||
<TableTh>Nomor Telepon</TableTh>
|
variant="light"
|
||||||
<TableTh>Detail</TableTh>
|
onClick={() =>
|
||||||
</TableTr>
|
router.push('/admin/inovasi/layanan-online-desa/administrasi-online/create')
|
||||||
</TableThead>
|
}
|
||||||
<TableTbody style={{ overflowX: "auto" }}>
|
>
|
||||||
{filteredData.map((item) => (
|
Tambah Baru
|
||||||
<TableTr key={item.id}>
|
</Button>
|
||||||
<TableTd>{item.name}</TableTd>
|
</Tooltip>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
</Group>
|
||||||
<TableTd>{item.nomorTelepon}</TableTd>
|
|
||||||
<TableTd>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Button onClick={() => router.push(`/admin//inovasi/layanan-online-desa/administrasi-online/${item.id}`)}>
|
<Table highlightOnHover>
|
||||||
<IconDeviceImac size={20} />
|
<TableThead>
|
||||||
</Button>
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '25%' }}>Nama Layanan</TableTh>
|
||||||
|
<TableTh style={{ width: '25%' }}>Alamat</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>Nomor Telepon</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={1}>
|
||||||
|
{item.alamat}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={1}>
|
||||||
|
{item.nomorTelepon || '-'}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImac size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/inovasi/layanan-online-desa/administrasi-online/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data administrasi online yang cocok</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,23 +1,39 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import layananonlineDesa from '../../../_state/inovasi/layanan-online-desa';
|
import layananonlineDesa from '../../../_state/inovasi/layanan-online-desa';
|
||||||
|
|
||||||
|
|
||||||
function JenisLayanan() {
|
function JenisLayanan() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Jenis Layanan'
|
title="Jenis Layanan"
|
||||||
placeholder='pencarian'
|
placeholder="Cari jenis layanan..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -28,59 +44,115 @@ function JenisLayanan() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListJenisLayanan({ search }: { search: string }) {
|
function ListJenisLayanan({ search }: { search: string }) {
|
||||||
const stateList = useProxy(layananonlineDesa.jenisLayanan)
|
const stateList = useProxy(layananonlineDesa.jenisLayanan);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { data, page, totalPages, loading, load } = stateList.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateList.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
const filteredData = (stateList.findMany.data || []).filter(item => {
|
if (loading || !data) {
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateList.findMany.data) {
|
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Jenis Layanan'
|
<Title order={4}>Daftar Jenis Layanan</Title>
|
||||||
href='/admin/inovasi/layanan-online-desa/jenis-layanan/create'
|
<Tooltip label="Tambah Jenis Layanan" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama Jenis Layanan</TableTh>
|
onClick={() =>
|
||||||
<TableTh>Deskripsi</TableTh>
|
router.push(
|
||||||
<TableTh>Detail</TableTh>
|
'/admin/inovasi/layanan-online-desa/jenis-layanan/create'
|
||||||
</TableTr>
|
)
|
||||||
</TableThead>
|
}
|
||||||
<TableTbody>
|
>
|
||||||
{filteredData.map((item) => (
|
Tambah Baru
|
||||||
<TableTr key={item.id}>
|
</Button>
|
||||||
<TableTd>{item.nama}</TableTd>
|
</Tooltip>
|
||||||
<TableTd>{item.deskripsi}</TableTd>
|
</Group>
|
||||||
<TableTd>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Button color="green" onClick={() => router.push(`/admin/inovasi/layanan-online-desa/jenis-layanan/${item.id}`)}>
|
<Table highlightOnHover>
|
||||||
<IconDeviceImac size={20} />
|
<TableThead>
|
||||||
</Button>
|
<TableTr>
|
||||||
</TableTd>
|
<TableTh style={{ width: '30%' }}>Nama Jenis Layanan</TableTh>
|
||||||
|
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd style={{ width: '30%' }}>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ width: '40%' }}>
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={2}>
|
||||||
|
{item.deskripsi || '-'}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ width: '15%' }}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImac size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/inovasi/layanan-online-desa/jenis-layanan/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={3}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada jenis layanan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
header={{ height: 64 }}
|
header={{ height: 64 }}
|
||||||
navbar={{
|
navbar={{
|
||||||
width: 300,
|
width: { base: 260, sm: 280, lg: 300 },
|
||||||
breakpoint: "sm",
|
breakpoint: 'sm',
|
||||||
collapsed: {
|
collapsed: {
|
||||||
mobile: !opened,
|
mobile: !opened,
|
||||||
desktop: !desktopOpened,
|
desktop: !desktopOpened,
|
||||||
@@ -52,23 +52,29 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
style={{
|
style={{
|
||||||
background: "linear-gradient(90deg, #ffffff, #f9fbff)",
|
background: "linear-gradient(90deg, #ffffff, #f9fbff)",
|
||||||
borderBottom: `1px solid ${colors["blue-button"]}20`,
|
borderBottom: `1px solid ${colors["blue-button"]}20`,
|
||||||
|
padding: '0 16px',
|
||||||
}}
|
}}
|
||||||
|
px={{ base: 'sm', sm: 'md' }}
|
||||||
|
py={{ base: 'xs', sm: 'sm' }}
|
||||||
>
|
>
|
||||||
<Group px="md" h="100%" justify="space-between">
|
<Group w="100%" h="100%" justify="space-between" wrap="nowrap">
|
||||||
<Flex align="center" gap="sm">
|
<Flex align="center" gap="sm">
|
||||||
<Image
|
<Image
|
||||||
src="/assets/images/darmasaba-icon.png"
|
src="/assets/images/darmasaba-icon.png"
|
||||||
alt="Logo Darmasaba"
|
alt="Logo Darmasaba"
|
||||||
width={46}
|
w={{ base: 32, sm: 40 }}
|
||||||
height={46}
|
h={{ base: 32, sm: 40 }}
|
||||||
radius="md"
|
radius="md"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
style={{
|
||||||
|
minWidth: '32px',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
fw={700}
|
fw={700}
|
||||||
c={colors["blue-button"]}
|
c={colors["blue-button"]}
|
||||||
fz="lg"
|
fz={{ base: 'md', sm: 'xl' }}
|
||||||
style={{ letterSpacing: rem(0.3) }}
|
|
||||||
>
|
>
|
||||||
Admin Darmasaba
|
Admin Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
@@ -93,8 +99,9 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
opened={opened}
|
opened={opened}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
hiddenFrom="sm"
|
hiddenFrom="sm"
|
||||||
size="sm"
|
size="md"
|
||||||
color={colors["blue-button"]}
|
color={colors["blue-button"]}
|
||||||
|
mr="xs"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip label="Kembali ke Website Desa" position="bottom" withArrow>
|
<Tooltip label="Kembali ke Website Desa" position="bottom" withArrow>
|
||||||
@@ -108,7 +115,18 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||||
>
|
>
|
||||||
<Image src="/assets/images/darmasaba-icon.png" alt="Logo Darmasaba" w={25} h={25} radius="md" loading="lazy" />
|
<Image
|
||||||
|
src="/assets/images/darmasaba-icon.png"
|
||||||
|
alt="Logo Darmasaba"
|
||||||
|
w={20}
|
||||||
|
h={20}
|
||||||
|
radius="md"
|
||||||
|
loading="lazy"
|
||||||
|
style={{
|
||||||
|
minWidth: '20px',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Keluar" position="bottom" withArrow>
|
<Tooltip label="Keluar" position="bottom" withArrow>
|
||||||
@@ -135,6 +153,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
background: "#ffffff",
|
background: "#ffffff",
|
||||||
borderRight: `1px solid ${colors["blue-button"]}20`,
|
borderRight: `1px solid ${colors["blue-button"]}20`,
|
||||||
}}
|
}}
|
||||||
|
p={{ base: 'xs', sm: 'sm' }}
|
||||||
>
|
>
|
||||||
<AppShell.Section p="sm">
|
<AppShell.Section p="sm">
|
||||||
{navBar.map((v, k) => {
|
{navBar.map((v, k) => {
|
||||||
@@ -155,6 +174,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
marginBottom: rem(4),
|
marginBottom: rem(4),
|
||||||
transition: "background 150ms ease",
|
transition: "background 150ms ease",
|
||||||
}}
|
}}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(25, 113, 194, 0.05)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
variant="light"
|
variant="light"
|
||||||
active={isParentActive}
|
active={isParentActive}
|
||||||
>
|
>
|
||||||
@@ -173,10 +199,19 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
{child.name}
|
{child.name}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
style={{
|
styles={{
|
||||||
borderRadius: rem(8),
|
root: {
|
||||||
marginBottom: rem(2),
|
borderRadius: rem(8),
|
||||||
transition: "background 150ms ease",
|
marginBottom: rem(2),
|
||||||
|
transition: 'background 150ms ease',
|
||||||
|
padding: '6px 12px',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)',
|
||||||
|
},
|
||||||
|
...(isChildActive && {
|
||||||
|
backgroundColor: 'rgba(25, 113, 194, 0.1)',
|
||||||
|
}),
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
active={isChildActive}
|
active={isChildActive}
|
||||||
component={Link}
|
component={Link}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
// /api/berita/findManyPaginated.ts
|
// /api/berita/findManyPaginated.ts
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
@@ -5,8 +6,20 @@ import { Context } from "elysia";
|
|||||||
async function administrasiOnlineFindMany(context: Context) {
|
async function administrasiOnlineFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Buat where clause
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ name: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ alamat: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.administrasiOnline.findMany({
|
prisma.administrasiOnline.findMany({
|
||||||
@@ -28,6 +41,7 @@ async function administrasiOnlineFindMany(context: Context) {
|
|||||||
message: "Success fetch administrasi online with pagination",
|
message: "Success fetch administrasi online with pagination",
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
limit,
|
||||||
totalPages: Math.ceil(total / limit),
|
totalPages: Math.ceil(total / limit),
|
||||||
total,
|
total,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,37 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function jenisLayananFindMany() {
|
export default async function jenisLayananFindMany(context: Context) {
|
||||||
const data = await prisma.jenisLayanan.findMany();
|
// Ambil parameter dari query
|
||||||
return {
|
const page = Number(context.query.page) || 1;
|
||||||
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Buat where clause
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ nama: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.jenisLayanan.findMany({
|
||||||
|
where,
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
}),
|
||||||
|
prisma.jenisLayanan.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: data.map((item: any) => {
|
data: data.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
@@ -12,5 +40,16 @@ export default async function jenisLayananFindMany() {
|
|||||||
deskripsi: item.deskripsi,
|
deskripsi: item.deskripsi,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findMany paginated:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data jenis layanan",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,63 +1,58 @@
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Container, Box, List, ListItem, Text, Image } from '@mantine/core';
|
import { Stack, Box, Container, Text, Image, ListItem, List } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BackButton from '../darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '../darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
||||||
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
|
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
|
||||||
<Container w={{ base: "100%", md: "50%" }} >
|
<Container w={{ base: "100%", md: "50%" }} >
|
||||||
<Box pb={20}>
|
<Box pb={20}>
|
||||||
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||||
IKM Berbasis Pengolahan Pangan
|
Taman Beji Cengana
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
ta={"center"}
|
ta={"center"}
|
||||||
fw={"bold"}
|
fw={"bold"}
|
||||||
fz={"1.4rem"}
|
fz={"1.5rem"}
|
||||||
>
|
>
|
||||||
Informasi dan Pelayanan Administrasi Digital
|
Informasi dan Pelayanan Administrasi Digital
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
|
||||||
<Image src="/api/img/ikm.png" alt='' w={"100%"} loading="lazy"/>
|
|
||||||
</Container>
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam Industri Kecil dan Menengah (IKM) berbasis pengolahan pangan. Dengan sumber daya alam yang melimpah dan warisan kuliner khas Bali, Darmasaba dapat mengembangkan sektor ini untuk meningkatkan kesejahteraan masyarakat dan menciptakan lapangan kerja baru.
|
|
||||||
</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
Potensi dan Peran IKM Berbasis Pengolahan Pangan:
|
|
||||||
</Text>
|
|
||||||
<List py={20} type='ordered'>
|
|
||||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Produk Unggulan Pengolahan Pangan</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>Beberapa produk olahan pangan yang potensial dikembangkan di Darmasaba meliputi:</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}> - Keripik dan Snack Tradisional : Seperti keripik pisang, keripik singkong, dan rengginang.</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}> - Sambal Khas Bali : Seperti sambal matah dan sambal embe yang banyak diminati pasar lokal dan nasional.</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}> - Minuman Herbal dan Jamu : Berbasis rempah seperti kunyit asam, beras kencur, dan wedang jahe.</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}> - Olahan Makanan Berbasis Kelapa : Seperti virgin coconut oil (VCO), serundeng, dan gula aren.</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}> - Kue Tradisional Bali : Seperti jaje laklak, jaje uli, dan klepon yang dapat dikemas secara modern.</Text>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Peluang Ekonomi dan Pemberdayaan UMKM:</Text>IKM berbasis pengolahan pangan dapat membuka peluang bagi masyarakat, terutama ibu rumah tangga dan pemuda desa, untuk berwirausaha. Dengan dukungan modal dan pelatihan dari pemerintah desa atau BUMDes Pudak Mesari, usaha kecil ini dapat berkembang menjadi industri yang lebih besar.
|
|
||||||
</ListItem>
|
|
||||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Digitalisasi dan Pemasaran Online:</Text>Darmasaba dapat mengembangkan kawasan sentra IKM sebagai pusat produksi, pelatihan, dan pemasaran produk olahan pangan. Dengan adanya fasilitas ini, para pelaku usaha dapat lebih mudah berkolaborasi, meningkatkan kualitas produk, serta mendapatkan akses ke permodalan dan distribusi yang lebih luas.
|
|
||||||
</ListItem>
|
|
||||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengembangan Kawasan Sentra IKM:</Text>Dengan berkembangnya sektor kuliner, banyak pelaku UMKM di Darmasaba mulai merintis usaha makanan, baik dalam bentuk warung makan, katering, hingga produksi makanan ringan seperti keripik, sambal, dan minuman tradisional. Potensi ini dapat terus dikembangkan dengan dukungan pemerintah desa dan promosi melalui media sosial.
|
|
||||||
</ListItem>
|
|
||||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Sinergi dengan Pariwisata dan Agrowisata:</Text>Dengan berkembangnya sektor wisata di Darmasaba, produk olahan pangan dapat dijadikan suvenir khas desa. Pengunjung dapat membeli oleh-oleh seperti sambal kemasan, jajanan khas, atau minuman herbal sebagai bagian dari pengalaman wisata mereka.
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
|
||||||
IKM berbasis pengolahan pangan memiliki potensi besar untuk menjadi sektor unggulan di Desa Darmasaba. Dengan inovasi, dukungan teknologi, serta pemasaran yang baik, produk-produk lokal dapat bersaing di pasar yang lebih luas, meningkatkan kesejahteraan masyarakat, dan menjadikan Darmasaba sebagai pusat industri pangan kreatif di Kabupaten Badung.
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
<Image src="/api/img/taman-beji.jpg" alt='' w={"100%"} h={400} />
|
||||||
|
</Container>
|
||||||
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
|
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
Taman Beji Cengana, terletak di Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali, adalah situs suci yang memiliki nilai spiritual dan sejarah yang tinggi. Tempat ini dikenal sebagai lokasi untuk ritual pembersihan diri (melukat) dan peribadatan oleh umat Hindu Bali. Keberadaan mata air suci (Tirta Klebutan) di Taman Beji Cengana dipercaya memberikan berkah dan penyucian bagi mereka yang datang untuk berdoa dan melakukan ritual.
|
||||||
|
</Text>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
Potensi Desa melalui Taman Beji Cengana:
|
||||||
|
</Text>
|
||||||
|
<List py={20} type='ordered'>
|
||||||
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengembangan Pariwisata Spiritual:</Text> Taman Beji Cengana memiliki potensi besar sebagai destinasi wisata spiritual. Wisatawan yang mencari pengalaman spiritual dan ketenangan batin dapat tertarik untuk mengunjungi tempat ini, mengikuti ritual melukat, dan merasakan suasana sakral yang ditawarkan.
|
||||||
|
</ListItem>
|
||||||
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pelestarian Budaya dan Tradisi:</Text>Dengan mempromosikan Taman Beji Cengana sebagai pusat kegiatan budaya dan ritual tradisional, desa dapat memastikan bahwa warisan budaya dan tradisi lokal tetap lestari.
|
||||||
|
</ListItem>
|
||||||
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pendidikan dan Penelitian:</Text>Taman Beji Cengana dapat dijadikan sebagai pusat pendidikan dan penelitian bagi akademisi, peneliti, dan pelajar yang tertarik mempelajari budaya, agama, dan sejarah Bali.
|
||||||
|
</ListItem>
|
||||||
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengembangan Ekonomi Kreatif:</Text>Dengan meningkatnya jumlah pengunjung ke Taman Beji Cengana, peluang bagi pengembangan ekonomi kreatif juga terbuka lebar. Masyarakat lokal dapat mengembangkan produk kerajinan tangan, kuliner khas, dan suvenir yang mencerminkan budaya dan tradisi desa.
|
||||||
|
</ListItem>
|
||||||
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Konservasi Lingkungan:</Text>Sebagai situs suci dengan mata air alami, Taman Beji Cengana memiliki peran penting dalam konservasi lingkungan. Upaya menjaga kebersihan dan kelestarian mata air serta lingkungan sekitarnya dapat menjadi contoh praktik konservasi yang baik.
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
|
Dengan memanfaatkan potensi yang dimiliki Taman Beji Cengana, Desa Darmasaba dapat mengembangkan sektor pariwisata, budaya, pendidikan, ekonomi, dan lingkungan secara berkelanjutan, yang pada gilirannya akan meningkatkan kesejahteraan masyarakat dan pelestarian warisan budaya.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/c
|
|||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../../layanan/_com/BackButto';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +49,6 @@ function Page() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
||||||
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
|
|
||||||
<Container w={{ base: "100%", md: "50%" }} >
|
<Container w={{ base: "100%", md: "50%" }} >
|
||||||
<Box pb={20}>
|
<Box pb={20}>
|
||||||
<Text ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
import { Box, Container, Grid, GridCol, ScrollArea, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -22,15 +22,15 @@ function LayoutTabsBerita({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Get active tab from URL path
|
// Get active tab from URL path
|
||||||
const activeTab = pathname.split('/').pop() || 'semua';
|
const activeTab = pathname.split('/').pop() || 'semua';
|
||||||
|
|
||||||
// Get initial search value from URL
|
// Get initial search value from URL
|
||||||
const initialSearch = searchParams.get('search') || '';
|
const initialSearch = searchParams.get('search') || '';
|
||||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||||
|
|
||||||
// Update active tab state when pathname changes
|
// Update active tab state when pathname changes
|
||||||
const [activeTabState, setActiveTabState] = useState(activeTab);
|
const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,28 +50,28 @@ function LayoutTabsBerita({
|
|||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
setSearchValue(value);
|
setSearchValue(value);
|
||||||
|
|
||||||
// Clear previous timeout
|
// Clear previous timeout
|
||||||
if (searchTimeout !== null) {
|
if (searchTimeout !== null) {
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new timeout
|
// Set new timeout
|
||||||
const newTimeout = window.setTimeout(() => {
|
const newTimeout = window.setTimeout(() => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
params.set('search', value);
|
params.set('search', value);
|
||||||
} else {
|
} else {
|
||||||
params.delete('search');
|
params.delete('search');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update URL if the search value has actually changed
|
// Only update URL if the search value has actually changed
|
||||||
if (params.toString() !== searchParams.toString()) {
|
if (params.toString() !== searchParams.toString()) {
|
||||||
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||||
}
|
}
|
||||||
}, 500); // 500ms debounce delay
|
}, 500); // 500ms debounce delay
|
||||||
|
|
||||||
setSearchTimeout(newTimeout);
|
setSearchTimeout(newTimeout);
|
||||||
};
|
};
|
||||||
const tabs = [
|
const tabs = [
|
||||||
@@ -147,17 +147,19 @@ function LayoutTabsBerita({
|
|||||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||||
<TabsList>
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
{tabs.map((tab, index) => (
|
<TabsList>
|
||||||
<TabsTab
|
{tabs.map((tab, index) => (
|
||||||
key={index}
|
<TabsTab
|
||||||
value={tab.value}
|
key={index}
|
||||||
onClick={() => router.push(tab.href)}
|
value={tab.value}
|
||||||
>
|
onClick={() => router.push(tab.href)}
|
||||||
{tab.label}
|
>
|
||||||
</TabsTab>
|
{tab.label}
|
||||||
))}
|
</TabsTab>
|
||||||
</TabsList>
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ function Apbdes() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
||||||
<Box w={{ base: '100%', sm: '70%' }}>
|
<Box>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fz={{ base: '2.4rem', sm: '4rem' }} fw="bold" lh={1.2}>
|
<Text ta={"center"} fz={{ base: '2.4rem', sm: '4rem' }} fw="bold" lh={1.2}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: '1rem', sm: '1.3rem' }} c="dimmed">
|
<Text ta={"center"} fz={{ base: '1rem', sm: '1.3rem' }} c="dimmed">
|
||||||
{textHeading.des}
|
{textHeading.des}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
|
import korupsiState from "@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { Box, Button, Center, Container, Flex, Paper, SimpleGrid, Stack, Text, useMantineTheme } from "@mantine/core";
|
import { Button, Center, Container, Flex, Paper, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||||
import { useMediaQuery } from "@mantine/hooks";
|
|
||||||
import { IconClipboardText } from "@tabler/icons-react";
|
import { IconClipboardText } from "@tabler/icons-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import korupsiState from "@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi";
|
|
||||||
import { useProxy } from "valtio/utils";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
function DesaAntiKorupsi() {
|
function DesaAntiKorupsi() {
|
||||||
const state = useProxy(korupsiState);
|
const state = useProxy(korupsiState);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const theme = useMantineTheme();
|
|
||||||
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -31,7 +29,7 @@ function DesaAntiKorupsi() {
|
|||||||
|
|
||||||
const data = (state.desaAntikorupsi.findMany.data || []).slice(0, 6);
|
const data = (state.desaAntikorupsi.findMany.data || []).slice(0, 6);
|
||||||
return (
|
return (
|
||||||
<Stack gap={"0"} bg={colors.Bg} p={"sm"} h={mobile ? 2000 : 1050}>
|
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||||
<Center>
|
<Center>
|
||||||
<Text fz={{ base: "2.4rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
<Text fz={{ base: "2.4rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
||||||
@@ -41,51 +39,60 @@ function DesaAntiKorupsi() {
|
|||||||
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desa-anti-korupsi/detail"}>Selengkapnya</Button>
|
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desa-anti-korupsi/detail"}>Selengkapnya</Button>
|
||||||
</Center>
|
</Center>
|
||||||
</Container>
|
</Container>
|
||||||
<SimpleGrid
|
<Container w="100%" maw="80rem" px="md">
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
sm: 2,
|
|
||||||
|
|
||||||
}}>
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Center>
|
<Center mih={200}>
|
||||||
<Text fz={"2.4rem"}>Memuat Data...</Text>
|
<Text fz="lg">Memuat Data...</Text>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
data.map((v, k) => {
|
<SimpleGrid
|
||||||
return (
|
cols={{ base: 1, sm: 2, md: 3 }}
|
||||||
<Box
|
spacing="lg"
|
||||||
|
mt="lg"
|
||||||
|
>
|
||||||
|
{data.map((v, k) => (
|
||||||
|
<Paper
|
||||||
key={k}
|
key={k}
|
||||||
|
p="md"
|
||||||
|
withBorder
|
||||||
|
shadow="sm"
|
||||||
|
radius="md"
|
||||||
|
h="100%"
|
||||||
>
|
>
|
||||||
<Paper
|
<Flex gap="md" align="flex-start">
|
||||||
p={"lg"}
|
<IconClipboardText
|
||||||
withBorder
|
color={colors["blue-button"]}
|
||||||
shadow="sm"
|
size={40}
|
||||||
h={{ base: 250, md: 210 }}
|
style={{ flexShrink: 0 }} // biar icon nggak ketekan
|
||||||
>
|
/>
|
||||||
<Flex gap={"lg"} align={"center"}>
|
<Stack gap={2} style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Box>
|
<Text
|
||||||
<Flex justify={"center"} align={"center"}>
|
fz={{ base: "sm", sm: "md", md: "lg", lg: "xl" }} // lebih besar di desktop
|
||||||
<Box>
|
c={colors["blue-button"]}
|
||||||
<IconClipboardText color={colors["blue-button"]} size={50} />
|
fw={600}
|
||||||
</Box>
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
<Box px={20} >
|
>
|
||||||
<Stack gap={"xs"}>
|
{v.kategori?.name || "Kategori"}
|
||||||
<Text fz={{ base: "1.2rem", md: "lg" }} ta={"center"} c={colors["blue-button"]} fw={500}>
|
</Text>
|
||||||
{v.kategori?.name || v.name || 'Kategori'}
|
<Text
|
||||||
</Text>
|
dangerouslySetInnerHTML={{
|
||||||
<Text dangerouslySetInnerHTML={{ __html: v.name || 'Name' }} fz={{ base: "1rem", md: "lg" }} ta={"center"} c={colors["blue-button"]} fw={500} />
|
__html: v.name || "Name",
|
||||||
</Stack>
|
}}
|
||||||
</Box>
|
fz={{ base: "sm", sm: "md", md: "lg", lg: "xl" }} // sama, scaling responsif
|
||||||
</Flex>
|
c="dark"
|
||||||
</Box>
|
style={{
|
||||||
</Flex>
|
wordBreak: "break-word",
|
||||||
</Paper>
|
whiteSpace: "normal",
|
||||||
</Box>
|
}}
|
||||||
)
|
/>
|
||||||
})
|
</Stack>
|
||||||
|
</Flex>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</SimpleGrid>
|
</Container>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function ModuleView() {
|
|||||||
<ScrollArea h={280} // ✅ tinggi fixed, bisa disesuaikan
|
<ScrollArea h={280} // ✅ tinggi fixed, bisa disesuaikan
|
||||||
scrollbarSize={8}
|
scrollbarSize={8}
|
||||||
offsetScrollbars
|
offsetScrollbars
|
||||||
type="auto"
|
type="never"
|
||||||
styles={{
|
styles={{
|
||||||
viewport: { paddingRight: 8 }, // kasih jarak biar scroll nggak dempet
|
viewport: { paddingRight: 8 }, // kasih jarak biar scroll nggak dempet
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ function Potensi() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="sm" gap="4rem">
|
<Stack p="sm" gap="4rem">
|
||||||
<Box w={{ base: "100%", sm: "60%" }}>
|
<Box>
|
||||||
<Text fz="4.4rem" fw={700} c={colors["blue-button"]}>
|
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }} fw={700} c={colors["blue-button"]}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="1.4rem" c="black">
|
<Text ta={"center"} fz={{ base: "1.4rem", md: "1.6rem" }} c="black">
|
||||||
{textHeading.des}
|
{textHeading.des}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user