Sinkronisasi UI & API Menu Keamanan, Admin - User Submenu Keamanan Lingkungan & Polse Terdekat
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 keamananLingkunganState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.KeamananLingkunganGetPayload<{
|
| Prisma.KeamananLingkunganGetPayload<{
|
||||||
include: { image: true };
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.keamananlingkungan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
keamananLingkunganState.findMany.data = res.data?.data ?? [];
|
keamananLingkunganState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
keamananLingkunganState.findMany.page = page;
|
||||||
|
keamananLingkunganState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.keamananlingkungan["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
keamananLingkunganState.findMany.data = res.data.data ?? [];
|
||||||
|
keamananLingkunganState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
keamananLingkunganState.findMany.data = [];
|
||||||
|
keamananLingkunganState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch keamanan lingkungan paginated:", err);
|
||||||
|
keamananLingkunganState.findMany.data = [];
|
||||||
|
keamananLingkunganState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
keamananLingkunganState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -63,13 +64,41 @@ const polsekTerdekatState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.PolsekTerdekatGetPayload<{
|
| Prisma.PolsekTerdekatGetPayload<{
|
||||||
include: { layananPolsek: true };
|
include: {
|
||||||
|
layananPolsek: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
polsekTerdekatState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
polsekTerdekatState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
polsekTerdekatState.findMany.page = page;
|
||||||
|
polsekTerdekatState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get(
|
||||||
|
{ query }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
polsekTerdekatState.findMany.data = res.data.data ?? [];
|
||||||
|
polsekTerdekatState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
polsekTerdekatState.findMany.data = [];
|
||||||
|
polsekTerdekatState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch polsek terdekat paginated:", err);
|
||||||
|
polsekTerdekatState.findMany.data = [];
|
||||||
|
polsekTerdekatState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
polsekTerdekatState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -237,6 +266,29 @@ const polsekTerdekatState = proxy({
|
|||||||
polsekTerdekatState.edit.form = { ...defaultForm };
|
polsekTerdekatState.edit.form = { ...defaultForm };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findFirst: {
|
||||||
|
data: null as Prisma.PolsekTerdekatGetPayload<{
|
||||||
|
include: {
|
||||||
|
layananPolsek: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
loading: false,
|
||||||
|
load: async () => { // Changed to arrow function
|
||||||
|
polsekTerdekatState.findFirst.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.keamanan.polsekterdekat["find-first"].get();
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
polsekTerdekatState.findFirst.data = res.data.data || null;
|
||||||
|
} else {
|
||||||
|
polsekTerdekatState.findFirst.data = null;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch polsek terdekat terbaru:", err);
|
||||||
|
} finally {
|
||||||
|
polsekTerdekatState.findFirst.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default polsekTerdekatState;
|
export default polsekTerdekatState;
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ function DetailKeamananLingkungan() {
|
|||||||
{keamananState.findUnique.data ? (
|
{keamananState.findUnique.data ? (
|
||||||
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 150, md: 490}} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
|
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
|
||||||
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
|
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
|
||||||
@@ -62,10 +66,6 @@ function DetailKeamananLingkungan() {
|
|||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
|
||||||
<Image w={{ base: 150, md: 150, lg: 150 }} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
<Flex gap={"xs"} mt={10}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -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 ListKeamananLingkungan({ search }: { search: string }) {
|
|||||||
const keamananState = useProxy(keamananLingkunganState)
|
const keamananState = useProxy(keamananLingkunganState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = keamananState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
keamananState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (keamananState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!keamananState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -67,9 +69,15 @@ function ListKeamananLingkungan({ search }: { search: string }) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={180}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.name}</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/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
|
||||||
@@ -81,6 +89,14 @@ function ListKeamananLingkungan({ 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,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 { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -21,7 +21,7 @@ function PolsekTerdekat() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListPolsekTerdekat search={search}/>
|
<ListPolsekTerdekat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -30,20 +30,21 @@ function ListPolsekTerdekat({ search }: { search: string }) {
|
|||||||
const polsekState = useProxy(polsekTerdekat)
|
const polsekState = useProxy(polsekTerdekat)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = polsekState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
polsekState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (polsekState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword) ||
|
|
||||||
item.jarakKeDesa.toLowerCase().includes(keyword) ||
|
|
||||||
item.alamat.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!polsekState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -64,24 +65,44 @@ function ListPolsekTerdekat({ search }: { search: string }) {
|
|||||||
<TableTh>Jarak Polsek</TableTh>
|
<TableTh>Jarak Polsek</TableTh>
|
||||||
<TableTh>Alamat</TableTh>
|
<TableTh>Alamat</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.nama}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.jarakKeDesa}</TableTd>
|
<Box w={180}>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
<Text fz='md' truncate={"end"} lineClamp={1}>{item.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={180}>
|
||||||
|
<Text fz='md' truncate={"end"} lineClamp={1}>{item.jarakKeDesa}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={250}>
|
||||||
|
<Text fz='md' truncate={"end"} lineClamp={1}>{item.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))}
|
||||||
</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 keamananLingkunganFindMany() {
|
async function keamananLingkunganFindMany(context: Context) {
|
||||||
try {
|
// Ambil parameter dari query
|
||||||
const data = await prisma.keamananLingkungan.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 keamanan lingkungan",
|
|
||||||
data,
|
// Tambahkan pencarian (jika ada)
|
||||||
};
|
if (search) {
|
||||||
} catch (e) {
|
where.OR = [
|
||||||
console.error("Find many error:", e);
|
{ name: { contains: search, mode: 'insensitive' } },
|
||||||
return {
|
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||||
success: false,
|
];
|
||||||
message: "Failed fetch keamanan lingkungan",
|
}
|
||||||
};
|
|
||||||
}
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.keamananLingkungan.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
image: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
}),
|
||||||
|
prisma.keamananLingkungan.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil keamanan lingkungan 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 keamanan lingkungan",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default keamananLingkunganFindMany;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
// find-first.ts
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
async function polsekTerdekatFindFirst() {
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await prisma.polsekTerdekat.findFirst({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
layananPolsek: true,
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil polsek terdekat terbaru",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findFirst:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal ambil polsek terdekat terbaru",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default polsekTerdekatFindFirst;
|
||||||
@@ -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 polsekTerdekatFindMany() {
|
async function polsekTerdekatFindMany(context: Context) {
|
||||||
try {
|
// Ambil parameter dari query
|
||||||
const data = await prisma.polsekTerdekat.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) || '';
|
||||||
layananPolsek: true,
|
const skip = (page - 1) * limit;
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
// Buat where clause
|
||||||
success: true,
|
const where: any = { isActive: true };
|
||||||
message: "Success fetch polsek terdekat",
|
|
||||||
data,
|
// Tambahkan pencarian (jika ada)
|
||||||
};
|
if (search) {
|
||||||
} catch (e) {
|
where.OR = [
|
||||||
console.error("Find many error:", e);
|
{ nama: { contains: search, mode: 'insensitive' } },
|
||||||
return {
|
{ alamat: { contains: search, mode: 'insensitive' } },
|
||||||
success: false,
|
];
|
||||||
message: "Failed fetch polsek terdekat",
|
}
|
||||||
};
|
|
||||||
}
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.polsekTerdekat.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
layananPolsek: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
}),
|
||||||
|
prisma.polsekTerdekat.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil polsek terdekat 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 polsek terdekat",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default polsekTerdekatFindMany;
|
||||||
@@ -4,6 +4,7 @@ import polsekTerdekatDelete from "./del";
|
|||||||
import polsekTerdekatFindMany from "./findMany";
|
import polsekTerdekatFindMany from "./findMany";
|
||||||
import polsekTerdekatFindUnique from "./findUnique";
|
import polsekTerdekatFindUnique from "./findUnique";
|
||||||
import polsekTerdekatUpdate from "./updt";
|
import polsekTerdekatUpdate from "./updt";
|
||||||
|
import polsekTerdekatFindFirst from "./findFirst";
|
||||||
|
|
||||||
|
|
||||||
const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/Polsek Terdekat"] })
|
const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/Polsek Terdekat"] })
|
||||||
@@ -12,6 +13,7 @@ const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/
|
|||||||
const response = await polsekTerdekatFindUnique(new Request(context.request));
|
const response = await polsekTerdekatFindUnique(new Request(context.request));
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
|
.get("/find-first", polsekTerdekatFindFirst)
|
||||||
.post("/create", polsekTerdekatCreate, {
|
.post("/create", polsekTerdekatCreate, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
nama: t.String(),
|
nama: t.String(),
|
||||||
|
|||||||
@@ -1,67 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan';
|
||||||
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconSearch } from '@tabler/icons-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 data1 = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
judul: 'Peran Pecalang dalam Keamanan Desa',
|
|
||||||
image: '/api/img/pecalang.png',
|
|
||||||
pengertian: 'Pecalang adalah petugas keamanan adat di Bali yang memiliki peran penting dalam menjaga ketertiban dan budaya lokal. Tugas mereka meliputi:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengamankan upacara adat dan kegiatan keagamaan.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengatur lalu lintas saat ada perayaan atau kegiatan besar.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli untuk mencegah gangguan keamanan di lingkungan desa.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berkoordinasi dengan aparat desa dan kepolisian dalam penanganan situasi darurat.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
judul: 'Patwal (Patroli Pengawal) Desa',
|
|
||||||
image: '/api/img/patwal-1.png',
|
|
||||||
pengertian: 'Selain Pecalang, Desa Darmasaba juga memiliki Patwal yang bertugas menjaga keamanan sehari-hari. Peran mereka antara lain:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli secara rutin untuk memastikan lingkungan tetap aman.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Menjaga ketertiban lalu lintas di area desa.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Melakukan tindakan preventif terhadap potensi gangguan keamanan.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Siap siaga dalam keadaan darurat untuk membantu warga.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
judul: 'Layanan Keamanan yang Tersedia',
|
|
||||||
image: '/api/img/pospecalang.png',
|
|
||||||
pengertian: 'Jika terjadi keadaan darurat atau membutuhkan bantuan keamanan, warga dapat menghubungi:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Pos Pecalang Desa: [Masukkan Nomor Kontak].</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Patwal Desa Darmasaba: [Masukkan Nomor Kontak].</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Polsek Terdekat: 110 (Layanan Kepolisian).</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
judul: 'Program Keamanan Desa',
|
|
||||||
image: '/api/img/rond.png',
|
|
||||||
pengertian: 'Untuk meningkatkan keamanan, Desa Darmasaba menjalankan berbagai program, seperti:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Ronda Malam Warga: Kegiatan jaga malam secara bergilir oleh warga bersama Pecalang dan Patwal.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Sosialisasi Keamanan: Edukasi bagi warga tentang cara menjaga keamanan lingkungan.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}> Pengawasan Kamera CCTV: Memantau titik- titik strategis untuk mencegah tindak kejahatan.</ListItem>
|
|
||||||
</List>
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(keamananLingkunganState)
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 3, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
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 }}>
|
||||||
Keamanan Lingkungan (Pecalang / Patwal)
|
<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" }} >
|
Keamanan Lingkungan (Pecalang / Patwal)
|
||||||
|
</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
|
<TextInput
|
||||||
|
radius={"lg"}
|
||||||
|
placeholder='Cari Puskesmas'
|
||||||
|
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.
|
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>
|
||||||
@@ -73,24 +67,19 @@ 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 px={10} py={20}>
|
<Center px={10} py={20}>
|
||||||
<Image src={v.image} alt='' />
|
<Image src={v.image?.link} alt='' />
|
||||||
</Center>
|
</Center>
|
||||||
<Box px={'lg'}>
|
<Box px={'lg'}>
|
||||||
<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.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text pb={10} fz={"h4"} ta={'justify'}>
|
<Text pb={10} fz={"h4"} ta={'justify'} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||||
{v.pengertian}
|
|
||||||
</Text>
|
|
||||||
<Box px={10}>
|
|
||||||
{v.deskripsi}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -100,6 +89,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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Badge, Box, Button, Flex, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
|
import { Badge, Box, Button, Center, Flex, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { IconArrowDown, IconClock, IconNavigation, IconPhone, IconPin, IconSearch } from '@tabler/icons-react';
|
import { IconArrowDown, IconClock, IconNavigation, IconPhone, IconPin } from '@tabler/icons-react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(polsekTerdekatState.findFirst);
|
||||||
|
const router = useRouter()
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data && !loading) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
}, [data, loading]);
|
||||||
|
|
||||||
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 }}>
|
||||||
@@ -17,7 +37,6 @@ function Page() {
|
|||||||
<Text pb={15} fz={'h4'} >
|
<Text pb={15} fz={'h4'} >
|
||||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
|
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput radius={10} leftSection={<IconSearch size={20} />} placeholder='Cari Kantor Polisi' />
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'}>
|
<Stack gap={'lg'}>
|
||||||
@@ -30,59 +49,56 @@ function Page() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Content Sebelah Kiri */}
|
{/* Content Sebelah Kiri */}
|
||||||
<Box>
|
{loading ? (
|
||||||
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>Polsek Abiansemal</Text>
|
<Center><Skeleton h={400} /></Center>
|
||||||
<Text c={colors["blue-button"]} fz={'sm'}>2,5 Km dari Desa Darmasaba</Text>
|
) : data ? (
|
||||||
<Flex py={10} gap={9} align={'center'}>
|
<>
|
||||||
<IconPin size={25} color={colors["blue-button"]} />
|
<Box>
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>Jl. Gandamayu 1 Blahkiuh</Text>
|
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>{data.nama}</Text>
|
||||||
</Flex>
|
<Text c={colors["blue-button"]} fz={'sm'}>{data.jarakKeDesa}</Text>
|
||||||
<Flex gap={9} align={'center'}>
|
<Flex py={10} gap={9} align={'center'}>
|
||||||
<IconPhone size={25} color={colors["blue-button"]} />
|
<IconPin size={25} color={colors["blue-button"]} />
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>08xxxxxxxx</Text>
|
<Text c={colors["blue-button"]} fz={'lg'}>{data.alamat}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex py={10} gap={9} align={'center'}>
|
<Flex gap={9} align={'center'}>
|
||||||
<IconClock size={25} color={colors["blue-button"]} />
|
<IconPhone size={25} color={colors["blue-button"]} />
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>24 Jam</Text>
|
<Text c={colors["blue-button"]} fz={'lg'}>{data.nomorTelepon}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box>
|
<Flex py={10} gap={9} align={'center'}>
|
||||||
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>Layanan Yang Tersedia :</Text>
|
<IconClock size={25} color={colors["blue-button"]} />
|
||||||
<SimpleGrid
|
<Text c={colors["blue-button"]} fz={'lg'}>{data.jamOperasional}</Text>
|
||||||
py={10}
|
</Flex>
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>Laporan Kehilangan</Text>
|
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>Layanan Yang Tersedia :</Text>
|
||||||
|
<SimpleGrid
|
||||||
|
py={10}
|
||||||
|
cols={{
|
||||||
|
base: 1,
|
||||||
|
md: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Text c={colors["blue-button"]} fz={'lg'}>{data.layananPolsek.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>Laporan Kriminal</Text>
|
<Button bg={colors["blue-button"]} onClick={() => router.push(`/darmasaba/keamanan/polsek-terdekat/semua-polsek`)} rightSection={<IconArrowDown size={20} />}>Lihat Kantor Polisi Lainnya</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
</Box>
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>Pelayanan SKCK</Text>
|
<Box pos={'relative'}>
|
||||||
|
<Box style={{ position: 'absolute', top: 0, right: 0 }}>
|
||||||
|
<Badge size='lg' c={'#287407'} bg={'#A8EDC4'}>Buka</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box pt={40}>
|
||||||
<Text c={colors["blue-button"]} fz={'lg'}>Pengaduan Masyarakat</Text>
|
<iframe style={{ border: 2, width: "100%" }} src={data.embedMapUrl} width="550" height="300" ></iframe>
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
<Box pt={20}>
|
||||||
</Box>
|
<Button onClick={() => router.push(data.linkPetunjukArah)} fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20} />}>Petunjuk Arah</Button>
|
||||||
<Box>
|
</Box>
|
||||||
<Button bg={colors["blue-button"]} rightSection={<IconArrowDown size={20}/>}>Lihat Kantor Polisi Lainnya</Button>
|
</Box>
|
||||||
</Box>
|
</>
|
||||||
</Box>
|
) : null}
|
||||||
<Box pos={'relative'}>
|
|
||||||
<Box style={{ position: 'absolute', top: 0, right: 0 }}>
|
|
||||||
<Badge size='lg' c={'#287407'} bg={'#A8EDC4'}>Buka</Badge>
|
|
||||||
</Box>
|
|
||||||
<Box pt={40}>
|
|
||||||
<iframe style={{ border: 2, width: "100%" }} src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3945.7949871034166!2d115.20778387533218!3d-8.519275686287415!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23ceb4f6e5363%3A0xa353662f070f47d8!2sAbian%20Semal%20Police%20Station!5e0!3m2!1sid!2sid!4v1742789148825!5m2!1sid!2sid" width="550" height="300" ></iframe>
|
|
||||||
</Box>
|
|
||||||
<Box pt={20}>
|
|
||||||
<Button fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20}/>}>Petunjuk Arah</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Page
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>HIPMI Feature Checklist</title>
|
<title>Desa Darmasaba Feature Checklist</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
|
|||||||
Reference in New Issue
Block a user