Compare commits

...

4 Commits

16 changed files with 713 additions and 427 deletions

View File

@@ -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";
@@ -61,13 +62,39 @@ const lowonganKerjaState = proxy({
findMany: { findMany: {
data: null as data: null as
| Prisma.LowonganPekerjaanGetPayload<{ | Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true }; omit: {
isActive: true;
};
}>[] }>[]
| null, | null,
async load() { page: 1,
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get(); totalPages: 1,
if (res.status === 200) { loading: false,
lowonganKerjaState.findMany.data = res.data?.data ?? []; search: "",
load: async (page = 1, limit = 10, search = "") => {
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
lowonganKerjaState.findMany.page = page;
lowonganKerjaState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
lowonganKerjaState.findMany.data = res.data.data ?? [];
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
lowonganKerjaState.findMany.data = [];
lowonganKerjaState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch lowongan kerja paginated:", err);
lowonganKerjaState.findMany.data = [];
lowonganKerjaState.findMany.totalPages = 1;
} finally {
lowonganKerjaState.findMany.loading = false;
} }
}, },
}, },

View File

@@ -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,22 +54,47 @@ const pasarDesa = proxy({
}, },
}, },
findMany: { findMany: {
data: null as Array< data: null as
Prisma.PasarDesaGetPayload<{ | Prisma.PasarDesaGetPayload<{
include: { include: {
image: true; image: true;
KategoriToPasar: { KategoriToPasar: {
include: { include: {
kategori: true; kategori: true;
};
}; };
}; };
}; }>[]
}> | null,
> | null, page: 1,
async load() { totalPages: 1,
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get(); loading: false,
if (res.status === 200) { search: "",
pasarDesa.findMany.data = res.data?.data ?? []; load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
pasarDesa.findMany.loading = true;
pasarDesa.findMany.page = page;
pasarDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
if (categoryId) query.categoryId = categoryId;
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
pasarDesa.findMany.data = res.data.data ?? [];
pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
} else {
pasarDesa.findMany.data = [];
pasarDesa.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch keamanan lingkungan paginated:", err);
pasarDesa.findMany.data = [];
pasarDesa.findMany.totalPages = 1;
} finally {
pasarDesa.findMany.loading = false;
} }
}, },
}, },
@@ -272,14 +298,41 @@ const kategoriProduk = proxy({
}, },
}, },
findMany: { findMany: {
data: null as Array<{ data: null as
id: string; | Prisma.KategoriProdukGetPayload<{
nama: string; omit: {
}> | null, isActive: true;
async load() { };
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get(); }>[]
if (res.status === 200) { | null,
kategoriProduk.findMany.data = res.data?.data ?? []; page: 1,
totalPages: 1,
loading: false,
search2: "",
load: async (page = 1, limit = 10, search2 = "") => {
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriProduk.findMany.page = page;
kategoriProduk.findMany.search2 = search2;
try {
const query: any = { page, limit };
if (search2) query.search2 = search2;
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriProduk.findMany.data = res.data.data ?? [];
kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kategoriProduk.findMany.data = [];
kategoriProduk.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori produk paginated:", err);
kategoriProduk.findMany.data = [];
kategoriProduk.findMany.totalPages = 1;
} finally {
kategoriProduk.findMany.loading = false;
} }
}, },
}, },

View File

@@ -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;
} }
}, },
}, },

View File

@@ -55,17 +55,17 @@ function EditLowonganKerja() {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
lowonganKerjaState.update.form = { // Set the ID for the update
...lowonganKerjaState.update.form, lowonganState.update.id = params?.id as string;
posisi: formData.posisi,
namaPerusahaan: formData.namaPerusahaan, // Update the form state
lokasi: formData.lokasi, lowonganState.update.form = {
tipePekerjaan: formData.tipePekerjaan, ...lowonganState.update.form,
gaji: formData.gaji, ...formData
deskripsi: formData.deskripsi, };
kualifikasi: formData.kualifikasi,
} // Call the update function
await lowonganState.update.update() await lowonganState.update.update();
toast.success("Lowongan kerja berhasil diperbarui!"); toast.success("Lowongan kerja berhasil diperbarui!");
router.push("/admin/ekonomi/lowongan-kerja-lokal"); router.push("/admin/ekonomi/lowongan-kerja-lokal");
} catch (error) { } catch (error) {
@@ -88,7 +88,7 @@ function EditLowonganKerja() {
<TextInput <TextInput
value={formData.posisi} value={formData.posisi}
onChange={(val) => { onChange={(val) => {
formData.posisi = val.target.value; setFormData(prev => ({ ...prev, posisi: val.target.value }));
}} }}
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>} label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
placeholder='Masukkan posisi' placeholder='Masukkan posisi'
@@ -96,7 +96,7 @@ function EditLowonganKerja() {
<TextInput <TextInput
value={formData.namaPerusahaan} value={formData.namaPerusahaan}
onChange={(val) => { onChange={(val) => {
formData.namaPerusahaan = val.target.value; setFormData(prev => ({ ...prev, namaPerusahaan: val.target.value }));
}} }}
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>} label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
placeholder='Masukkan nama perusahaan' placeholder='Masukkan nama perusahaan'
@@ -104,7 +104,7 @@ function EditLowonganKerja() {
<TextInput <TextInput
value={formData.lokasi} value={formData.lokasi}
onChange={(val) => { onChange={(val) => {
formData.lokasi = val.target.value; setFormData(prev => ({ ...prev, lokasi: val.target.value }));
}} }}
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>} label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
placeholder='Masukkan lokasi' placeholder='Masukkan lokasi'
@@ -112,7 +112,7 @@ function EditLowonganKerja() {
<TextInput <TextInput
value={formData.tipePekerjaan} value={formData.tipePekerjaan}
onChange={(val) => { onChange={(val) => {
formData.tipePekerjaan = val.target.value; setFormData(prev => ({ ...prev, tipePekerjaan: val.target.value }));
}} }}
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>} label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
placeholder='Masukkan tipe pekerjaan' placeholder='Masukkan tipe pekerjaan'
@@ -120,7 +120,7 @@ function EditLowonganKerja() {
<TextInput <TextInput
value={formData.gaji} value={formData.gaji}
onChange={(val) => { onChange={(val) => {
formData.gaji = val.target.value; setFormData(prev => ({ ...prev, gaji: val.target.value }));
}} }}
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>} label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
placeholder='Masukkan gaji' placeholder='Masukkan gaji'
@@ -130,7 +130,7 @@ function EditLowonganKerja() {
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => { onChange={(val) => {
formData.deskripsi = val; setFormData(prev => ({ ...prev, deskripsi: val }));
}} }}
/> />
</Box> </Box>
@@ -139,7 +139,7 @@ function EditLowonganKerja() {
<EditEditor <EditEditor
value={formData.kualifikasi} value={formData.kualifikasi}
onChange={(val) => { onChange={(val) => {
formData.kualifikasi = val; setFormData(prev => ({ ...prev, kualifikasi: val }));
}} }}
/> />
</Box> </Box>

View File

@@ -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 } 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,20 +30,21 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
const lowonganState = useProxy(lowonganKerjaState) const lowonganState = useProxy(lowonganKerjaState)
const router = useRouter(); const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = lowonganState.findMany
useShallowEffect(() => { useShallowEffect(() => {
lowonganState.findMany.load(); load(page, 10, search)
}, []) }, [page, search])
const filteredData = (lowonganState.findMany.data || []).filter(item => { const filteredData = data || []
const keyword = search.toLowerCase();
return (
item.posisi.toLowerCase().includes(keyword) ||
item.namaPerusahaan.toLowerCase().includes(keyword) ||
item.lokasi.toLowerCase().includes(keyword)
);
});
if (!lowonganState.findMany.data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton h={500} /> <Skeleton h={500} />
@@ -60,18 +61,24 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
<Table striped withTableBorder withRowBorders> <Table striped withTableBorder withRowBorders>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Bekerja Sebagai</TableTh> <TableTh>Pekerjaan</TableTh>
<TableTh>Nama Usaha</TableTh> <TableTh>Nama Perusahaan</TableTh>
<TableTh>Alamat Usaha</TableTh> <TableTh>Lokasi</TableTh>
<TableTh>Detail</TableTh> <TableTh>Detail</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.map((item) => ( {filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd>{item.posisi}</TableTd> <TableTd>
<TableTd>{item.namaPerusahaan}</TableTd> <Text fz={"md"}>{item.posisi}</Text>
<TableTd>{item.lokasi}</TableTd> </TableTd>
<TableTd>
<Text fz={"md"}>{item.namaPerusahaan}</Text>
</TableTd>
<TableTd>
<Text fz={"md"}>{item.lokasi}</Text>
</TableTd>
<TableTd> <TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}> <Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
<IconDeviceImac size={20} /> <IconDeviceImac size={20} />
@@ -82,6 +89,14 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Paper> </Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box> </Box>
); );
} }

View File

@@ -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 } from '@mantine/core'; import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react'; import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -13,31 +13,39 @@ import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
function PasarDesa() { function PasarDesa() {
const [search, setSearch] = useState("") const [search2, setSearch2] = useState("")
return ( return (
<Box> <Box>
<HeaderSearch <HeaderSearch
title='Kategori Produk' title='Kategori Produk'
placeholder='pencarian' placeholder='pencarian'
searchIcon={<IconSearch size={20} />} searchIcon={<IconSearch size={20} />}
value={search} value={search2}
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch2(e.currentTarget.value)}
/> />
<ListPasarDesa search={search} /> <ListPasarDesa search2={search2} />
</Box> </Box>
); );
} }
function ListPasarDesa({ search }: { search: string }) { function ListPasarDesa({ search2 }: { search2: string }) {
const statePasar = useProxy(pasarDesaState.kategoriProduk) const statePasar = useProxy(pasarDesaState.kategoriProduk)
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()
const {
data,
page,
totalPages,
loading,
load,
} = statePasar.findMany
useShallowEffect(() => { useShallowEffect(() => {
statePasar.findMany.load() load(page, 10, search2)
}, []) }, [page, search2])
const handleHapus = () => { const handleHapus = () => {
if (selectedId) { if (selectedId) {
@@ -47,14 +55,9 @@ function ListPasarDesa({ search }: { search: string }) {
} }
} }
const filteredData = (statePasar.findMany.data || []).filter(item => { const filteredData = data || []
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
if (!statePasar.findMany.data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton h={500} /> <Skeleton h={500} />
@@ -99,6 +102,14 @@ function ListPasarDesa({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Paper> </Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Konfirmasi Hapus */} {/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus <ModalKonfirmasiHapus
opened={modalHapus} opened={modalHapus}

View File

@@ -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 } from '@mantine/core'; import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -30,21 +30,21 @@ function ListPasarDesa({ search }: { search: string }) {
const statePasar = useProxy(pasarDesaState.pasarDesa) const statePasar = useProxy(pasarDesaState.pasarDesa)
const router = useRouter(); const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = statePasar.findMany
useShallowEffect(() => { useShallowEffect(() => {
statePasar.findMany.load() load(page, 10, search)
}, []) }, [page, search])
const filteredData = (statePasar.findMany.data || []).filter(item => { const filteredData = data || []
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.harga.toString().toLowerCase().includes(keyword) ||
item.rating.toString().toLowerCase().includes(keyword) ||
item.alamatUsaha.toLowerCase().includes(keyword)
);
});
if (!statePasar.findMany.data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton h={500} /> <Skeleton h={500} />
@@ -86,6 +86,14 @@ function ListPasarDesa({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Paper> </Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box> </Box>
); );
} }

View File

@@ -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>
); );
} }

View File

@@ -1,21 +1,55 @@
/* 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 lowonganKerjaFindMany() { async function lowonganKerjaFindMany(context: Context) {
try { // Ambil parameter dari query
const data = await prisma.lowonganPekerjaan.findMany({ const page = Number(context.query.page) || 1;
where: { isActive: true }, const limit = Number(context.query.limit) || 10;
}); const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
return { // Buat where clause
success: true, const where: any = { isActive: true };
message: "Success fetch lowongan kerja",
data, // Tambahkan pencarian (jika ada)
}; if (search) {
} catch (e) { where.OR = [
console.error("Find many error:", e); { posisi: { contains: search, mode: 'insensitive' } },
return { { namaPerusahaan: { contains: search, mode: 'insensitive' } },
success: false, { lokasi: { contains: search, mode: 'insensitive' } },
message: "Failed fetch lowongan kerja", ];
}; }
}
} try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.lowonganPekerjaan.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.lowonganPekerjaan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil lowongan kerja 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 lowongan kerja",
};
}
}
export default lowonganKerjaFindMany;

View File

@@ -1,26 +1,84 @@
/* 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 pasarDesaFindMany() { async function pasarDesaFindMany(context: Context) {
const data = await prisma.pasarDesa.findMany({ // Ambil parameter dari query
where: { const page = Number(context.query.page) || 1;
isActive: true, // Opsional filter const limit = Number(context.query.limit) || 10;
}, const search = (context.query.search as string) || '';
orderBy: { const categoryId = context.query.categoryId as string | undefined;
createdAt: "desc", const skip = (page - 1) * limit;
},
include: { // Buat where clause
image: true, const where: any = { isActive: true };
KategoriToPasar: {
// Tambahkan filter kategori (jika ada)
if (categoryId) {
where.KategoriToPasar = {
some: {
kategoriId: categoryId
}
};
}
// Tambahkan pencarian (jika ada)
if (search) {
where.AND = where.AND || [];
where.AND.push({
OR: [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamatUsaha: { contains: search, mode: 'insensitive' } },
{
KategoriToPasar: {
some: {
kategori: {
nama: { contains: search, mode: 'insensitive' }
}
}
}
}
]
});
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.pasarDesa.findMany({
where,
include: { include: {
kategori: true, image: true,
KategoriToPasar: {
include: {
kategori: true
}
}
}, },
}, skip,
}, take: limit,
}); orderBy: { createdAt: 'desc' },
}),
prisma.pasarDesa.count({ where }),
]);
return { return {
success: true, success: true,
message: "Berhasil mengambil semua data pasar desa", message: "Berhasil ambil pasar desa dengan pagination",
data, 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 pasar desa",
};
}
} }
export default pasarDesaFindMany;

View File

@@ -1,15 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* 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 kategoriProdukFindMany() { async function kategoriProdukFindMany(context: Context) {
const data = await prisma.kategoriProduk.findMany(); // Ambil parameter dari query
return { const page = Number(context.query.page) || 1;
success: true, const limit = Number(context.query.limit) || 10;
data: data.map((item: any) => { const search2 = (context.query.search as string) || '';
return { const skip = (page - 1) * limit;
id: item.id,
nama: item.nama, // Buat where clause
} const where: any = { isActive: true };
}),
// Tambahkan pencarian (jika ada)
if (search2) {
where.OR = [
{ nama: { contains: search2, mode: 'insensitive' } },
{KategoriToPasar : {
some: {
kategori: {
nama: { contains: search2, mode: 'insensitive' }
}
}
}}
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.kategoriProduk.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.kategoriProduk.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil kategori produk 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 kategori produk",
};
}
}
export default kategoriProdukFindMany;

View File

@@ -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;

View File

@@ -1,64 +1,52 @@
'use client' 'use client'
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Text, TextInput, Group, SimpleGrid, Paper, Flex, Button } from '@mantine/core'; import { Box, Button, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import React from 'react'; import { useDebouncedValue } from '@mantine/hooks';
import BackButton from '../../desa/layanan/_com/BackButto';
import { IconBriefcase, IconClock, IconMapPin, IconSearch } from '@tabler/icons-react'; import { IconBriefcase, IconClock, IconMapPin, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const data = [ const formatCurrency = (value: string | number) => {
{ // Convert to string if it's a number
id: 1, const numStr = typeof value === 'number' ? value.toString() : value;
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki', // Remove all non-digit characters
alamat: 'Desa Munggu , Badung', const digitsOnly = numStr.replace(/\D/g, '');
gaji: 'Rp. 2.500.000 / bulan'
// Format with thousand separators
const formatted = digitsOnly.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
return `Rp.${formatted}`;
};
},
{
id: 2,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 3,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 4,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 5,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 6,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
]
function Page() { function Page() {
const state = useProxy(lowonganKerjaState)
const router = useRouter() const router = useRouter()
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany
useEffect(() => {
load(page, 6, debouncedSearch)
}, [page, debouncedSearch, load])
if (loading || !data) {
return (
<Stack py={10}>
<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 }}>
@@ -74,6 +62,8 @@ function Page() {
w={{ base: 500, md: 700 }} w={{ base: 500, md: 700 }}
placeholder='Cari Pekerjaan' placeholder='Cari Pekerjaan'
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/> />
</Group> </Group>
</Box> </Box>
@@ -93,15 +83,15 @@ function Page() {
<Flex gap={'xl'} align={'center'}> <Flex gap={'xl'} align={'center'}>
<IconBriefcase color={colors['blue-button']} size={50} /> <IconBriefcase color={colors['blue-button']} size={50} />
<Box> <Box>
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.kerja}</Text> <Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.posisi}</Text>
<Text fz={'h4'}>{v.tempat}</Text> <Text fz={'h4'}>{v.namaPerusahaan}</Text>
</Box> </Box>
</Flex> </Flex>
</Box> </Box>
<Box> <Box>
<Flex gap={'xl'} align={'center'}> <Flex gap={'xl'} align={'center'}>
<IconMapPin color={colors['blue-button']} size={50} /> <IconMapPin color={colors['blue-button']} size={50} />
<Text fz={'h4'}>{v.alamat}</Text> <Text fz={'h4'}>{v.lokasi}</Text>
</Flex> </Flex>
</Box> </Box>
<Box> <Box>
@@ -109,7 +99,7 @@ function Page() {
<IconClock color={colors['blue-button']} size={50} /> <IconClock color={colors['blue-button']} size={50} />
<Box> <Box>
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>Full Time</Text> <Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>Full Time</Text>
<Text fz={'h4'}>{v.gaji}</Text> <Text fz={'h4'}>{formatCurrency(v.gaji)}</Text>
</Box> </Box>
</Flex> </Flex>
</Box> </Box>
@@ -119,6 +109,14 @@ function Page() {
) )
})} })}
</SimpleGrid> </SimpleGrid>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Stack> </Stack>
</Box > </Box >
</Stack > </Stack >

View File

@@ -1,90 +1,76 @@
'use client' 'use client'
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Combobox, Flex, Image, InputBase, InputPlaceholder, Paper, SimpleGrid, Stack, Text, TextInput, useCombobox } from '@mantine/core'; import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconMapPinFilled, IconSearch, IconShoppingCartFilled, IconStarFilled } from '@tabler/icons-react'; import { IconMapPinFilled, IconSearch, IconShoppingCartFilled, IconStarFilled } from '@tabler/icons-react';
import { motion } from 'motion/react'; import { motion } from 'motion/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 BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
const groceries = [
'Makanan',
'Minuman',
'Pakaian',
'Alat Dapur',
'Alat Mandi',
'Furniture',
];
const dataBarang = [
{
id: 1,
image: '/api/img/semat.png',
judul: 'Semat Bambu / Semat Banten',
harga: 'Rp. 3000 / pcs',
bintang: '4.9',
alamat: 'Jl. Kecubung no.6'
},
{
id: 2,
image: '/api/img/kerupuk.png',
judul: 'Kerupuk Babi',
harga: 'Rp. 12000 / pcs',
bintang: '4.9',
alamat: 'Jl. Kenari no.7'
},
{
id: 3,
image: '/api/img/beras.png',
judul: 'beras Merah Organik',
harga: 'Rp. 40000 / 1 kg',
bintang: '4.9',
alamat: 'Jl. Mawar no.8'
},
{
id: 4,
image: '/api/img/genteng.png',
judul: 'Genteng',
harga: 'Rp. 3600 / pcs',
bintang: '4.9',
alamat: 'Jl. Kecubung no.16'
},
]
function Page() { function Page() {
const [search, setSearch] = useState('');
const combobox = useCombobox({
onDropdownClose: () => {
combobox.resetSelectedOption();
combobox.focusTarget();
setSearch('');
},
onDropdownOpen: () => {
combobox.focusSearchInput();
},
});
const [value, setValue] = useState<string | null>(null);
const options = groceries
.filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))
.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
const router = useRouter() const router = useRouter()
const state = useProxy(pasarDesaState.pasarDesa)
const [search, setSearch] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const {
data,
page,
loading,
totalPages,
load,
} = state.findMany
useShallowEffect(() => {
pasarDesaState.kategoriProduk.findMany.load()
}, [])
// Filter data based on selected category
const filteredData = selectedCategory
? data?.filter(item =>
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
)
: data;
useShallowEffect(() => {
load(page, 4, search, selectedCategory || undefined)
}, [page, search, selectedCategory])
if (loading || !data) {
return (
<Stack py={10}>
<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 }}>
Pasar Desa <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" }} > Pasar Desa
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Produk'
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" }} >
Pasar Desa Online merupakan Media Promosi yang bertujuan untuk membantu warga desa dalam memasarkan dan memperkenalkan produknya kepada masyarakat. Pasar Desa Online merupakan Media Promosi yang bertujuan untuk membantu warga desa dalam memasarkan dan memperkenalkan produknya kepada masyarakat.
</Text> </Text>
</Box> </Box>
@@ -98,48 +84,23 @@ function Page() {
}} }}
> >
<Box> <Box>
<Combobox <Select
store={combobox} placeholder="Pilih Kategori"
withinPortal={false} data={pasarDesaState.kategoriProduk.findMany.data?.map((v) => ({
onOptionSubmit={(val) => { value: v.id,
setValue(val); label: v.nama
combobox.closeDropdown(); })) || []}
}} value={selectedCategory}
> onChange={setSelectedCategory}
<Combobox.Target> clearable
<InputBase searchable
component="button" nothingFoundMessage="Tidak ada kategori ditemukan"
type="button" style={{ width: '100%' }}
pointer
rightSection={<Combobox.Chevron />}
onClick={() => combobox.toggleDropdown()}
rightSectionPointerEvents="none"
>
{value || <InputPlaceholder>Kategori</InputPlaceholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Search
value={search}
onChange={(event) => setSearch(event.currentTarget.value)}
placeholder="Search groceries"
/>
<Combobox.Options>
{options.length > 0 ? options : <Combobox.Empty>Nothing found</Combobox.Empty>}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
</Box>
<Box>
<TextInput
placeholder='Cari Produk'
leftSection={<IconSearch size={20} />}
/> />
</Box> </Box>
</SimpleGrid> </SimpleGrid>
<SimpleGrid cols={{ base: 1, md: 4 }}> <SimpleGrid cols={{ base: 1, md: 4 }}>
{dataBarang.map((v, k) => { {filteredData?.map((v, k) => {
return ( return (
<Stack key={k}> <Stack key={k}>
<motion.div <motion.div
@@ -148,18 +109,25 @@ function Page() {
whileTap={{ scale: 0.8 }} whileTap={{ scale: 0.8 }}
> >
<Paper p={'lg'}> <Paper p={'lg'}>
<Image radius={'lg'} src={v.image} alt='' /> <Image
<Text py={10} fw={'bold'} fz={'lg'}>{v.judul}</Text> radius={'lg'}
<Text fz={'md'}>{v.harga}</Text> src={v.image?.link || '/placeholder-product.jpg'}
alt={v.nama}
h={200}
w='100%'
style={{ objectFit: 'cover' }}
/>
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text>
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text>
<Flex py={10} gap={'md'}> <Flex py={10} gap={'md'}>
<IconStarFilled size={20} color='#EBCB09' /> <IconStarFilled size={20} color='#EBCB09' />
<Text fz={'sm'} ml={2}>{v.bintang}</Text> <Text fz={'sm'} ml={2}>{v.rating}</Text>
</Flex> </Flex>
<Flex justify={'space-between'} align={'center'}> <Flex justify={'space-between'} align={'center'}>
<Box> <Box>
<Flex gap={'md'} align={'center'}> <Flex gap={'md'} align={'center'}>
<IconMapPinFilled size={20} color='red' /> <IconMapPinFilled size={20} color='red' />
<Text fz={'sm'} ml={2}>{v.alamat}</Text> <Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text>
</Flex> </Flex>
</Box> </Box>
<IconShoppingCartFilled size={20} color={colors['blue-button']} /> <IconShoppingCartFilled size={20} color={colors['blue-button']} />
@@ -170,6 +138,14 @@ function Page() {
) )
})} })}
</SimpleGrid> </SimpleGrid>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
my="md"
/>
</Center>
</Stack> </Stack>
</Box> </Box>
</Stack> </Stack>

View File

@@ -88,7 +88,7 @@ function Page() {
})} })}
</SimpleGrid> </SimpleGrid>
</Stack> </Stack>
</Box> </Box>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}

View File

@@ -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>
); );
} }