Sinkronisasi UI & API Admin - User Submenu Tips Keamanan, Menu Keamanan
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";
|
||||||
@@ -53,15 +54,39 @@ const tipsKeamananState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.MenuTipsKeamananGetPayload<{
|
| Prisma.MenuTipsKeamananGetPayload<{
|
||||||
include: { image: true };
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.menutipskeamanan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
tipsKeamananState.findMany.data = res.data?.data ?? [];
|
tipsKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
tipsKeamananState.findMany.page = page;
|
||||||
|
tipsKeamananState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.menutipskeamanan["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
tipsKeamananState.findMany.data = res.data.data ?? [];
|
||||||
|
tipsKeamananState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
tipsKeamananState.findMany.data = [];
|
||||||
|
tipsKeamananState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch menu tips keamanan paginated:", err);
|
||||||
|
tipsKeamananState.findMany.data = [];
|
||||||
|
tipsKeamananState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
tipsKeamananState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'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, Text } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,19 +30,21 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
const stateKeamanan = useProxy(tipsKeamananState)
|
const stateKeamanan = useProxy(tipsKeamananState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateKeamanan.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateKeamanan.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (stateKeamanan.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateKeamanan.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -67,9 +69,15 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.judul}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz={"xs"} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
|
<Box w={150}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.judul}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={250}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
|
||||||
@@ -81,6 +89,14 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,57 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
// /api/berita/findManyPaginated.ts
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function menuTipsKeamananFindMany() {
|
async function tipsKeamananFindMany(context: Context) {
|
||||||
try {
|
// Ambil parameter dari query
|
||||||
const data = await prisma.menuTipsKeamanan.findMany({
|
const page = Number(context.query.page) || 1;
|
||||||
where: { isActive: true },
|
const limit = Number(context.query.limit) || 10;
|
||||||
include: {
|
const search = (context.query.search as string) || '';
|
||||||
image: true,
|
const skip = (page - 1) * limit;
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
// Buat where clause
|
||||||
success: true,
|
const where: any = { isActive: true };
|
||||||
message: "Success fetch menu tips keamanan",
|
|
||||||
data,
|
// Tambahkan pencarian (jika ada)
|
||||||
};
|
if (search) {
|
||||||
} catch (e) {
|
where.OR = [
|
||||||
console.error("Find many error:", e);
|
{ judul: { contains: search, mode: 'insensitive' } },
|
||||||
return {
|
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||||
success: false,
|
];
|
||||||
message: "Failed fetch menu tips keamanan",
|
}
|
||||||
};
|
|
||||||
}
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.menuTipsKeamanan.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
image: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
}),
|
||||||
|
prisma.menuTipsKeamanan.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil menu tips keamanan dengan pagination",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findMany paginated:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data tips keamanan",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default tipsKeamananFindMany;
|
||||||
@@ -1,83 +1,62 @@
|
|||||||
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Image, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
|
|
||||||
const data1 = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
judul: 'Keamanan Rumah',
|
|
||||||
image: '/api/img/kemanan.png',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Pastikan pintu dan jendela selalu terkunci saat meninggalkan rumah.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Pasang lampu penerangan di halaman dan area sekitar rumah untuk mencegah tindak kejahatan.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Jangan mudah memberikan akses masuk ke orang yang tidak dikenal.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
judul: 'Keamanan di Jalan',
|
|
||||||
image: '/api/img/keamananjalan.png',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Hindari berjalan sendirian di tempat sepi, terutama pada malam hari.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Simpan barang berharga di tempat yang aman saat bepergian.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Gunakan jalur yang ramai dan terang saat pulang malam.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
judul: 'Keamanan Kendaraan',
|
|
||||||
image: '/api/img/keamanankendaraan.png',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Gunakan kunci ganda saat memarkir kendaraan, terutama di tempat umum.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Parkir di tempat yang terang dan mudah diawasi.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Jangan meninggalkan barang berharga di dalam kendaraan.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
judul: 'Keamanan Sosial',
|
|
||||||
image: '/api/img/mencurigakan.png',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Laporkan kejadian mencurigakan kepada Pecalang atau perangkat desa.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Jangan mudah percaya terhadap informasi yang belum jelas sumbernya.</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Ikuti program sosialisasi keamanan yang diadakan oleh desa.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
judul: 'Sistem Laporan Kejadian',
|
|
||||||
image: '/api/img/securitydigital.png',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem>Jangan mudah membagikan informasi pribadi di media sosial.</ListItem>
|
|
||||||
<ListItem>Waspada terhadap penipuan online dan telepon yang mengatasnamakan instansi resmi.</ListItem>
|
|
||||||
<ListItem>Gunakan kata sandi yang kuat untuk akun digital dan ganti secara berkala.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
judul: 'Nomor Darurat yang Bisa Dihubungi',
|
|
||||||
image: '/api/img/kontakpecalang.png',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Pecalang: 08125651052</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Ambulans: 08125651052</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Pemadam Kebakaran: 113</ListItem>
|
|
||||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Polisi: 110</ListItem>
|
|
||||||
</List>
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(tipsKeamananState)
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 3, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
||||||
Tips Keamanan
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
</Text>
|
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||||
<Text px={{ base: 20, md: 150 }} ta={"center"} fz={{ base: "h4", md: "h3" }} >
|
Tips Keamanan
|
||||||
Desa Darmasaba berkomitmen untuk menjaga keamanan dan kenyamanan seluruh warganya. Berikut beberapa tips yang dapat membantu meningkatkan keamanan di lingkungan desa.
|
</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
|
<TextInput
|
||||||
|
radius={"lg"}
|
||||||
|
placeholder='Cari Tips'
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
leftSection={<IconSearch size={20} />}
|
||||||
|
w={{ base: "50%", md: "100%" }}
|
||||||
|
/>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz={{ base: "h4", md: "h3" }} >
|
||||||
|
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal). Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
@@ -88,21 +67,21 @@ function Page() {
|
|||||||
base: 1,
|
base: 1,
|
||||||
md: 3,
|
md: 3,
|
||||||
}}>
|
}}>
|
||||||
{data1.map((v, k) => {
|
{data.map((v, k) => {
|
||||||
return (
|
return (
|
||||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
||||||
<Stack gap={'xs'}>
|
<Stack gap={'xs'}>
|
||||||
<Center p={10}>
|
<Center p={10}>
|
||||||
<Image src={v.image} radius={10}
|
<Image src={v.image?.link} radius={10}
|
||||||
alt='' />
|
alt='' />
|
||||||
</Center>
|
</Center>
|
||||||
<Box px={'xl'}>
|
<Box px={'xl'}>
|
||||||
<Box pb={20}>
|
<Box pb={20}>
|
||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
||||||
{v.judul}
|
{v.judul}
|
||||||
</Text>
|
</Text>
|
||||||
<Box pr={10}>
|
<Box>
|
||||||
{v.deskripsi}
|
<Text pb={10} fz={"md"} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -113,6 +92,14 @@ function Page() {
|
|||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user