Fix Admin - User Menu Keamanan, Submenu Pencegahan Kriminalitas

This commit is contained in:
2025-09-17 17:54:03 +08:00
parent 79ad39fc55
commit 9f72e94557
18 changed files with 782 additions and 847 deletions

View File

@@ -6,45 +6,17 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
pencegahanKriminalitas: z.object({
programKeamanan: z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
tipsKeamanan: z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
konten: z.string().min(1, "Konten minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
videoKeamanan: z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
videoUrl: z.string().min(1, "Video URL minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
}),
judul: z.string().min(1, "Judul minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
deskripsiSingkat: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
linkVideo: z.string().min(1, "Link video minimal 1 karakter"),
});
const defaultForm = {
pencegahanKriminalitas: {
programKeamanan: {
nama: "",
deskripsi: "",
slug: "",
},
tipsKeamanan: {
judul: "",
konten: "",
slug: "",
},
videoKeamanan: {
judul: "",
deskripsi: "",
videoUrl: "",
slug: "",
},
},
judul: "",
deskripsi: "",
deskripsiSingkat: "",
linkVideo: "",
};
const pencegahanKriminalitasState = proxy({
@@ -65,7 +37,7 @@ const pencegahanKriminalitasState = proxy({
pencegahanKriminalitasState.create.loading = true;
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"create"
].post(pencegahanKriminalitasState.create.form.pencegahanKriminalitas);
].post(pencegahanKriminalitasState.create.form);
if (res.status === 200) {
pencegahanKriminalitasState.findMany.load();
return toast.success("success create");
@@ -82,11 +54,7 @@ const pencegahanKriminalitasState = proxy({
findMany: {
data: null as
| Prisma.PencegahanKriminalitasGetPayload<{
include: {
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
omit: { isActive: true };
}>[]
| null,
page: 1,
@@ -125,11 +93,7 @@ const pencegahanKriminalitasState = proxy({
},
findUnique: {
data: null as Prisma.PencegahanKriminalitasGetPayload<{
include: {
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
omit: { isActive: true };
}> | null,
loading: false,
async load(id: string) {
@@ -148,6 +112,30 @@ const pencegahanKriminalitasState = proxy({
}
},
},
findFirst: {
data: null as Prisma.PencegahanKriminalitasGetPayload<{
omit: { isActive: true };
}> | null,
loading: false,
// findFirst.load()
async load() {
this.loading = true;
try {
const res = await ApiFetch.api.keamanan.pencegahankriminalitas["find-first"].get();
if (res.status === 200 && res.data?.success) {
this.data = res.data.data || null;
} else {
this.data = null;
}
} catch (err) {
console.error("Gagal fetch pencegahan kriminalitas terbaru:", err);
this.data = null;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
@@ -213,24 +201,10 @@ const pencegahanKriminalitasState = proxy({
const data = result.data;
pencegahanKriminalitasState.update.id = data.id;
pencegahanKriminalitasState.update.form = {
pencegahanKriminalitas: {
programKeamanan: {
nama: data.programKeamanan.nama,
deskripsi: data.programKeamanan.deskripsi,
slug: data.programKeamanan.slug,
},
tipsKeamanan: {
judul: data.tipsKeamanan.judul,
konten: data.tipsKeamanan.konten,
slug: data.tipsKeamanan.slug,
},
videoKeamanan: {
judul: data.videoKeamanan.judul,
deskripsi: data.videoKeamanan.deskripsi,
videoUrl: data.videoKeamanan.videoUrl,
slug: data.videoKeamanan.slug,
},
},
judul: data.judul,
deskripsi: data.deskripsi,
deskripsiSingkat: data.deskripsiSingkat,
linkVideo: data.linkVideo,
};
return data;
} else {
@@ -266,40 +240,11 @@ const pencegahanKriminalitasState = proxy({
"Content-Type": "application/json",
},
body: JSON.stringify({
pencegahanKriminalitas: {
programKeamanan: {
nama: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.nama,
deskripsi:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.deskripsi,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.slug,
},
tipsKeamanan: {
judul:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.judul,
konten:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.konten,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.slug,
},
videoKeamanan: {
judul:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.judul,
deskripsi:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.deskripsi,
videoUrl:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.videoUrl,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.slug,
},
},
judul: pencegahanKriminalitasState.update.form.judul,
deskripsi: pencegahanKriminalitasState.update.form.deskripsi,
deskripsiSingkat:
pencegahanKriminalitasState.update.form.deskripsiSingkat,
linkVideo: pencegahanKriminalitasState.update.form.linkVideo,
}),
}
);

View File

@@ -121,7 +121,7 @@ function DetailLaporanPublik() {
<Box>
<Text fz="lg" fw="bold">Kronologi</Text>
<Text fz="md" c="dimmed">{data.kronologi || '-'}</Text>
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.kronologi || '' }} />
</Box>
<Box>

View File

@@ -135,9 +135,7 @@ function ListLaporanPublik({ search }: { search: string }) {
</TableTd>
<TableTd>
<Box w={200}>
<Text truncate fz="sm" c="dimmed" lineClamp={1}>
{item.kronologi}
</Text>
<Text truncate fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.kronologi || '' }} />
</Box>
</TableTd>
<TableTd>

View File

@@ -2,39 +2,35 @@
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
import { convertYoutubeUrlToEmbed } from '@/app/admin/(dashboard)/desa/gallery/lib/youtube-utils';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import {
Box,
Button,
Group,
Paper,
Stack,
TextInput,
Title,
Tooltip
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPencegahanKriminalitas() {
const router = useRouter();
const params = useParams()
const kriminalitasState = useProxy(pencegahanKriminalitasState)
const params = useParams();
const kriminalitasState = useProxy(pencegahanKriminalitasState);
const [formData, setFormData] = useState({
pencegahanKriminalitas: {
programKeamanan: {
nama: kriminalitasState.update.form.pencegahanKriminalitas.programKeamanan.nama,
deskripsi: kriminalitasState.update.form.pencegahanKriminalitas.programKeamanan.deskripsi,
slug: kriminalitasState.update.form.pencegahanKriminalitas.programKeamanan.slug,
},
tipsKeamanan: {
judul: kriminalitasState.update.form.pencegahanKriminalitas.tipsKeamanan.judul,
konten: kriminalitasState.update.form.pencegahanKriminalitas.tipsKeamanan.konten,
slug: kriminalitasState.update.form.pencegahanKriminalitas.tipsKeamanan.slug,
},
videoKeamanan: {
judul: kriminalitasState.update.form.pencegahanKriminalitas.videoKeamanan.judul,
deskripsi: kriminalitasState.update.form.pencegahanKriminalitas.videoKeamanan.deskripsi,
videoUrl: kriminalitasState.update.form.pencegahanKriminalitas.videoKeamanan.videoUrl,
slug: kriminalitasState.update.form.pencegahanKriminalitas.videoKeamanan.slug,
},
},
})
judul: '',
deskripsi: '',
deskripsiSingkat: '',
linkVideo: '',
});
useEffect(() => {
const loadKriminalitas = async () => {
@@ -43,167 +39,154 @@ function EditPencegahanKriminalitas() {
try {
const data = await kriminalitasState.update.load(id);
if (data && data.pencegahanKriminalitas) {
const { programKeamanan, tipsKeamanan, videoKeamanan } = data.pencegahanKriminalitas;
if (data) {
setFormData({
pencegahanKriminalitas: {
programKeamanan: {
nama: programKeamanan?.nama || "",
deskripsi: programKeamanan?.deskripsi || "",
slug: programKeamanan?.slug || "",
},
tipsKeamanan: {
judul: tipsKeamanan?.judul || "",
konten: tipsKeamanan?.konten || "",
slug: tipsKeamanan?.slug || "",
},
videoKeamanan: {
judul: videoKeamanan?.judul || "",
deskripsi: videoKeamanan?.deskripsi || "",
videoUrl: videoKeamanan?.videoUrl || "",
slug: videoKeamanan?.slug || "",
},
},
judul: data.judul || '',
deskripsi: data.deskripsi || '',
deskripsiSingkat: data.deskripsiSingkat || '',
linkVideo: data.linkVideo || '',
});
}
} catch (error) {
console.error("Error loading pencegahan kriminalitas:", error);
toast.error("Gagal memuat data pencegahan kriminalitas");
console.error('Error loading pencegahan kriminalitas:', error);
toast.error('Gagal memuat data pencegahan kriminalitas');
}
}
};
loadKriminalitas();
}, [params.id]);
}, [params?.id]);
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
const handleSubmit = async () => {
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
if (!converted) {
toast.error('Link YouTube tidak valid. Pastikan formatnya benar.');
return;
}
try {
// Update the form data first
kriminalitasState.update.form = {
...kriminalitasState.update.form,
pencegahanKriminalitas: {
programKeamanan: {
nama: formData.pencegahanKriminalitas.programKeamanan.nama,
deskripsi: formData.pencegahanKriminalitas.programKeamanan.deskripsi,
slug: formData.pencegahanKriminalitas.programKeamanan.slug,
},
tipsKeamanan: {
judul: formData.pencegahanKriminalitas.tipsKeamanan.judul,
konten: formData.pencegahanKriminalitas.tipsKeamanan.konten,
slug: formData.pencegahanKriminalitas.tipsKeamanan.slug,
},
videoKeamanan: {
judul: formData.pencegahanKriminalitas.videoKeamanan.judul,
deskripsi: formData.pencegahanKriminalitas.videoKeamanan.deskripsi,
videoUrl: formData.pencegahanKriminalitas.videoKeamanan.videoUrl,
slug: formData.pencegahanKriminalitas.videoKeamanan.slug,
},
},
}
await kriminalitasState.update.update();
toast.success("Pencegahan Kriminalitas berhasil diperbarui!");
router.push("/admin/keamanan/pencegahan-kriminalitas");
} catch (error) {
console.error("Error updating pencegahan kriminalitas:", error);
toast.error("Gagal memuat data pencegahan kriminalitas");
}
}
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
judul: formData.judul,
deskripsi: formData.deskripsi,
deskripsiSingkat: formData.deskripsiSingkat,
linkVideo: formData.linkVideo,
};
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Edit Pencegahan Kriminalitas</Title>
// Set the ID and then call update
kriminalitasState.update.id = params?.id as string;
await kriminalitasState.update.update();
toast.success('Pencegahan Kriminalitas berhasil diperbarui!');
router.push('/admin/keamanan/pencegahan-kriminalitas');
} catch (error) {
console.error('Error updating pencegahan kriminalitas:', error);
toast.error('Terjadi kesalahan saat memperbarui data');
}
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Back button + Title */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button
variant="subtle"
onClick={() => router.back()}
p="xs"
radius="md"
>
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Edit Pencegahan Kriminalitas
</Title>
</Group>
{/* Form container */}
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md">
<TextInput
value={formData.pencegahanKriminalitas.programKeamanan.nama}
onChange={(val) => {
formData.pencegahanKriminalitas.programKeamanan.nama = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Judul Program Keamanan</Text>}
placeholder='Masukkan judul Program Keamanan'
label="Judul"
placeholder="Masukkan judul"
value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
required
/>
<TextInput
value={formData.pencegahanKriminalitas.programKeamanan.slug}
onChange={(val) => {
formData.pencegahanKriminalitas.programKeamanan.slug = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Slug Program Keamanan</Text>}
placeholder='Masukkan slug Program Keamanan'
label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat"
value={formData.deskripsiSingkat}
onChange={(e) =>
setFormData({ ...formData, deskripsiSingkat: e.target.value })
}
required
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Program Keamanan</Text>
<Title order={6} fw="bold" fz="sm" mb={6}>
Deskripsi Lengkap
</Title>
<EditEditor
value={formData.pencegahanKriminalitas.programKeamanan.deskripsi}
onChange={(val) => {
formData.pencegahanKriminalitas.programKeamanan.deskripsi = val;
}}
value={formData.deskripsi}
onChange={(val) =>
setFormData({ ...formData, deskripsi: val })
}
/>
</Box>
<TextInput
value={formData.pencegahanKriminalitas.tipsKeamanan.judul}
onChange={(val) => {
formData.pencegahanKriminalitas.tipsKeamanan.judul = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Judul Tips Keamanan</Text>}
placeholder='Masukkan judul Tips Keamanan'
/>
<TextInput
value={formData.pencegahanKriminalitas.tipsKeamanan.slug}
onChange={(val) => {
formData.pencegahanKriminalitas.tipsKeamanan.slug = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Slug Tips Keamanan</Text>}
placeholder='Masukkan slug Tips Keamanan'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Tips Keamanan</Text>
<EditEditor
value={formData.pencegahanKriminalitas.tipsKeamanan.konten}
onChange={(val) => {
formData.pencegahanKriminalitas.tipsKeamanan.konten = val;
}}
<TextInput
label="Link Video YouTube"
placeholder="https://www.youtube.com/watch?v=abc123"
value={formData.linkVideo}
onChange={(e) =>
setFormData({ ...formData, linkVideo: e.currentTarget.value })
}
required
/>
{embedLink && (
<Box
mt="sm"
style={{ display: 'flex', justifyContent: 'center' }}
>
<iframe
className="rounded"
width="100%"
height="220"
src={embedLink}
title="Preview Video"
allowFullScreen
></iframe>
</Box>
)}
</Box>
<TextInput
value={formData.pencegahanKriminalitas.videoKeamanan.judul}
onChange={(val) => {
formData.pencegahanKriminalitas.videoKeamanan.judul = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Judul Video Keamanan</Text>}
placeholder='Masukkan judul Video Keamanan'
/>
<TextInput
value={formData.pencegahanKriminalitas.videoKeamanan.slug}
onChange={(val) => {
formData.pencegahanKriminalitas.videoKeamanan.slug = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Slug Video Keamanan</Text>}
placeholder='Masukkan slug Video Keamanan'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Tips Keamanan</Text>
<EditEditor
value={formData.pencegahanKriminalitas.videoKeamanan.deskripsi}
onChange={(val) => {
formData.pencegahanKriminalitas.videoKeamanan.deskripsi = val;
{/* Action button */}
<Group justify="right">
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
/>
</Box>
<TextInput
value={formData.pencegahanKriminalitas.videoKeamanan.videoUrl}
onChange={(val) => {
formData.pencegahanKriminalitas.videoKeamanan.videoUrl = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Video URL</Text>}
placeholder='Masukkan video URL'
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
>
Simpan
</Button>
</Group>
</Stack>
</Paper>

View File

@@ -1,113 +1,146 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
import { useRouter, useParams } from 'next/navigation';
import { useState } from 'react';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import pencegahanKriminalitasState from '../../../_state/keamanan/pencegahan-kriminalitas';
function DetailPencegahanKriminalitas() {
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter();
const params = useParams()
const kriminalitasState = useProxy(pencegahanKriminalitasState)
const params = useParams();
const kriminalitasState = useProxy(pencegahanKriminalitasState);
useShallowEffect(() => {
kriminalitasState.findUnique.load(params?.id as string)
}, [])
kriminalitasState.findUnique.load(params?.id as string);
}, []);
const handleDelete = () => {
if (selectedId) {
kriminalitasState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/keamanan/pencegahan-kriminalitas")
kriminalitasState.delete.byId(selectedId);
setModalHapus(false);
setSelectedId(null);
router.push("/admin/keamanan/pencegahan-kriminalitas");
}
}
};
if (!kriminalitasState.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={40} />
<Skeleton height={500} radius="md" />
</Stack>
)
);
}
const data = kriminalitasState.findUnique.data;
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Paper bg={colors['BG-trans']} p={'md'}>
<Text fz={"xl"} fw={"bold"}>Detail Pencegahan Kriminalitas</Text>
{kriminalitasState.findUnique.data ? (
<Paper key={kriminalitasState.findUnique.data.id} bg={colors['BG-trans']}>
<Stack gap={"xs"} py={'md'}>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul Program Keamanan</Text>
<Text fz={"lg"}>{kriminalitasState.findUnique.data?.programKeamanan.nama}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Slug</Text>
<Text fz={"lg"}>{kriminalitasState.findUnique.data?.programKeamanan.slug}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kriminalitasState.findUnique.data?.programKeamanan.deskripsi || '' }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul Tips Keamanan</Text>
<Text fz={"lg"}>{kriminalitasState.findUnique.data?.tipsKeamanan.judul}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Slug Tips Keamanan</Text>
<Text fz={"lg"}>{kriminalitasState.findUnique.data?.tipsKeamanan.slug}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi Tips Keamanan</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kriminalitasState.findUnique.data?.tipsKeamanan.konten || '' }} />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (kriminalitasState.findUnique.data) {
setSelectedId(kriminalitasState.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={kriminalitasState.delete.loading || !kriminalitasState.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (kriminalitasState.findUnique.data) {
router.push(`/admin/keamanan/pencegahan-kriminalitas/${kriminalitasState.findUnique.data.id}/edit`);
}
}}
disabled={!kriminalitasState.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
<Box py={10}>
{/* Tombol Kembali */}
<Button
variant="subtle"
onClick={() => router.back()}
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
mb={15}
>
Kembali
</Button>
{/* Detail */}
<Paper
withBorder
w={{ base: "100%", md: "50%" }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
>
<Stack gap="md">
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
Detail Pencegahan Kriminalitas
</Text>
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm">
<Box>
<Text fz="lg" fw="bold">Judul</Text>
<Text fz="md" c="dimmed">{data?.judul || '-'}</Text>
</Box>
<Box>
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
{data?.deskripsiSingkat ? (
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat }} />
) : (
<Text fz="sm" c="dimmed">Tidak ada deskripsi singkat</Text>
)}
</Box>
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
{data?.deskripsi ? (
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.deskripsi }} />
) : (
<Text fz="sm" c="dimmed">Tidak ada deskripsi</Text>
)}
</Box>
<Box>
<Text fz="lg" fw="bold">Video</Text>
{data?.linkVideo ? (
<Box
component="iframe"
src={convertToEmbedUrl(data.linkVideo)}
width="100%"
height={300}
allowFullScreen
style={{ borderRadius: 8 }}
/>
) : (
<Text fz="sm" c="dimmed">Tidak ada video</Text>
)}
</Box>
{/* Tombol Aksi */}
<Flex gap="sm" mt="sm">
<Tooltip label="Hapus" withArrow position="top">
<Button
color="red"
onClick={() => {
setSelectedId(data.id);
setModalHapus(true);
}}
variant="light"
radius="md"
size="md"
>
<IconTrash size={20} />
</Button>
</Tooltip>
<Tooltip label="Edit" withArrow position="top">
<Button
color="green"
onClick={() => router.push(`/admin/keamanan/pencegahan-kriminalitas/${data.id}/edit`)}
variant="light"
radius="md"
size="md"
>
<IconEdit size={20} />
</Button>
</Tooltip>
</Flex>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus */}
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
@@ -116,6 +149,18 @@ function DetailPencegahanKriminalitas() {
/>
</Box>
);
function convertToEmbedUrl(youtubeUrl: string): string {
try {
const url = new URL(youtubeUrl);
const videoId = url.searchParams.get("v");
if (!videoId) return youtubeUrl;
return `https://www.youtube.com/embed/${videoId}`;
} catch (err) {
console.error("Error converting YouTube URL to embed:", err);
return youtubeUrl;
}
}
}
export default DetailPencegahanKriminalitas;

View File

@@ -1,141 +1,158 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import {
Box,
Button,
Group,
Paper,
Stack,
Text,
TextInput,
Title,
Tooltip,
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import CreateEditor from '../../../_com/createEditor';
import pencegahanKriminalitasState from '../../../_state/keamanan/pencegahan-kriminalitas';
import { useState } from 'react';
import { convertYoutubeUrlToEmbed } from '../../../desa/gallery/lib/youtube-utils';
import { toast } from 'react-toastify';
function CreatePencegahanKriminalitas() {
const router = useRouter();
const kriminalitasState = useProxy(pencegahanKriminalitasState)
const kriminalitasState = useProxy(pencegahanKriminalitasState);
const [link, setLink] = useState('');
const embedLink = convertYoutubeUrlToEmbed(link);
const resetForm = () => {
kriminalitasState.create.form = {
pencegahanKriminalitas: {
programKeamanan: {
nama: "",
deskripsi: "",
slug: "",
},
tipsKeamanan: {
judul: "",
konten: "",
slug: "",
},
videoKeamanan: {
judul: "",
deskripsi: "",
videoUrl: "",
slug: "",
},
},
}
}
judul: "",
deskripsi: "",
deskripsiSingkat: "",
linkVideo: "",
};
setLink('');
};
const handleSubmit = async () => {
if (!embedLink) {
toast.error('Link YouTube tidak valid. Pastikan formatnya benar.');
return;
}
kriminalitasState.create.form.linkVideo = embedLink;
await kriminalitasState.create.create();
resetForm();
router.push('/admin/keamanan/pencegahan-kriminalitas');
}
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
};
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Pencegahan Kriminalitas</Title>
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header Back Button + Title */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button
variant="subtle"
onClick={() => router.back()}
p="xs"
radius="md"
>
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Tambah Pencegahan Kriminalitas
</Title>
</Group>
{/* Card Form */}
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md">
{/* Judul */}
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.programKeamanan.nama}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.programKeamanan.nama = val.target.value;
label="Judul Pencegahan Kriminalitas"
placeholder="Masukkan judul Pencegahan Kriminalitas"
value={kriminalitasState.create.form.judul}
onChange={(e) => {
kriminalitasState.create.form.judul = e.currentTarget.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Judul Program Keamanan</Text>}
placeholder='Masukkan judul Program Keamanan'
required
/>
{/* Deskripsi Singkat */}
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.programKeamanan.slug}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.programKeamanan.slug = val.target.value;
label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat"
value={kriminalitasState.create.form.deskripsiSingkat}
onChange={(e) => {
kriminalitasState.create.form.deskripsiSingkat = e.currentTarget.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Slug Program Keamanan</Text>}
placeholder='Masukkan slug Program Keamanan'
required
/>
{/* Deskripsi Panjang */}
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Program Keamanan</Text>
<Text fw="bold" fz="sm" mb={6}>
Deskripsi
</Text>
<CreateEditor
value={kriminalitasState.create.form.pencegahanKriminalitas.programKeamanan.deskripsi}
value={kriminalitasState.create.form.deskripsi}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.programKeamanan.deskripsi = val;
kriminalitasState.create.form.deskripsi = val;
}}
/>
</Box>
{/* Link YouTube */}
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.tipsKeamanan.judul}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.tipsKeamanan.judul = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Judul Tips Keamanan</Text>}
placeholder='Masukkan judul Tips Keamanan'
label="Link Video YouTube"
placeholder="https://www.youtube.com/watch?v=abc123"
value={link}
onChange={(e) => setLink(e.currentTarget.value)}
required
/>
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.tipsKeamanan.slug}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.tipsKeamanan.slug = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Slug Tips Keamanan</Text>}
placeholder='Masukkan slug Tips Keamanan'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Tips Keamanan</Text>
<CreateEditor
value={kriminalitasState.create.form.pencegahanKriminalitas.tipsKeamanan.konten}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.tipsKeamanan.konten = val;
{/* Preview Video */}
{embedLink && (
<Box mt="sm">
<iframe
style={{
borderRadius: 10,
width: '100%',
height: 400,
border: '1px solid #ddd',
}}
src={embedLink}
title="Preview Video"
allowFullScreen
></iframe>
</Box>
)}
{/* Button Submit */}
<Group justify="right">
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
/>
</Box>
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.judul}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.judul = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Judul Video Keamanan</Text>}
placeholder='Masukkan judul Video Keamanan'
/>
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.slug}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.slug = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Slug Video Keamanan</Text>}
placeholder='Masukkan slug Video Keamanan'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Tips Keamanan</Text>
<CreateEditor
value={kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.deskripsi}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.deskripsi = val;
}}
/>
</Box>
<TextInput
value={kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.videoUrl}
onChange={(val) => {
kriminalitasState.create.form.pencegahanKriminalitas.videoKeamanan.videoUrl = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Video URL</Text>}
placeholder='Masukkan video URL'
/>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
>
Simpan
</Button>
</Group>
</Stack>
</Paper>

View File

@@ -62,14 +62,6 @@ function ListPencegahanKriminalitas({ search }: { search: string }) {
load(page, 10, search);
}, [page, search]);
const filteredData = (data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.programKeamanan.nama.toLowerCase().includes(keyword) ||
item.programKeamanan.slug.toLowerCase().includes(keyword) ||
item.programKeamanan.deskripsi?.toLowerCase().includes(keyword)
);
});
if (loading || !data) {
return (
@@ -103,35 +95,43 @@ function ListPencegahanKriminalitas({ search }: { search: string }) {
<TableThead>
<TableTr>
<TableTh>Nama Pencegahan</TableTh>
<TableTh>Slug</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Deskripsi Singkat</TableTh>
<TableTh>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item) => (
{data.length > 0 ? (
data.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>
{item.programKeamanan.nama}
{item.judul}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed" truncate lineClamp={1}>
{item.programKeamanan.slug}
</Text>
<Box w={200}>
<Text
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
fz="sm"
c="dimmed"
truncate
lineClamp={1}
/>
</Box>
</TableTd>
<TableTd>
<Text
fz="sm"
c="dimmed"
truncate
lineClamp={1}
dangerouslySetInnerHTML={{
__html: item.programKeamanan.deskripsi || ''
}}
/>
<Box w={200}>
<Text
fz="sm"
c="dimmed"
truncate
lineClamp={1}
dangerouslySetInnerHTML={{
__html: item.deskripsiSingkat || ''
}}
/>
</Box>
</TableTd>
<TableTd>
<Button

View File

@@ -1,81 +1,35 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type ProgramKeamananInput = {
nama: string;
deskripsi: string;
slug: string;
};
type TipsKeamanan = {
judul: string;
konten: string;
slug: string;
};
type VideoKeamanan = {
judul: string;
deskripsi: string;
videoUrl: string;
slug: string;
};
type createdPencegahanKriminalitas = {
programKeamanan: ProgramKeamananInput;
tipsKeamanan: TipsKeamanan;
videoKeamanan: VideoKeamanan;
};
type FormCreate = Prisma.PencegahanKriminalitasGetPayload<{
select: {
judul: true;
deskripsi: true;
deskripsiSingkat: true;
linkVideo: true;
};
}>;
const pencegahanKriminalitasCreate = async (context: Context) => {
const { programKeamanan, tipsKeamanan, videoKeamanan } =
(await context.body) as createdPencegahanKriminalitas;
const body = context.body as FormCreate;
const createdProgram = await prisma.programKeamanan.create({
await prisma.pencegahanKriminalitas.create({
data: {
nama: programKeamanan.nama,
deskripsi: programKeamanan.deskripsi,
slug: programKeamanan.slug,
judul: body.judul,
deskripsi: body.deskripsi,
deskripsiSingkat: body.deskripsiSingkat,
linkVideo: body.linkVideo,
},
});
const createdTips = await prisma.tipsKeamanan.create({
return {
success: true,
message: "Success create pencegahan kriminalitas",
data: {
judul: tipsKeamanan.judul,
konten: tipsKeamanan.konten,
slug: tipsKeamanan.slug,
...body,
},
});
const createdVideo = await prisma.videoKeamanan.create({
data: {
judul: videoKeamanan.judul,
deskripsi: videoKeamanan.deskripsi,
videoUrl: videoKeamanan.videoUrl,
slug: videoKeamanan.slug,
},
});
const createdPencegahanKriminalitas =
await prisma.pencegahanKriminalitas.create({
data: {
programKeamananId: createdProgram.id,
tipsKeamananId: createdTips.id,
videoKeamananId: createdVideo.id,
},
include: {
programKeamanan: true,
tipsKeamanan: true,
videoKeamanan: true,
},
});
return Response.json(
{
success: true,
message: "Success create program keamanan",
data: createdPencegahanKriminalitas,
},
{ status: 200 }
);
status: 200,
};
};
export default pencegahanKriminalitasCreate;

View File

@@ -13,11 +13,6 @@ const pencegahanKriminalitasDelete = async (context: Context) => {
const pencegahanKriminalitas = await prisma.pencegahanKriminalitas.findUnique({
where: { id },
include: {
programKeamanan: true,
tipsKeamanan: true,
videoKeamanan: true,
}
});
if (!pencegahanKriminalitas) {

View File

@@ -0,0 +1,60 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
export default async function pencegahanKriminalitasFindFirst(context: Context) {
// Ambil parameter query
const search = (context.query.search as string) || "";
// Buat where clause
const where: Prisma.PencegahanKriminalitasWhereInput = { isActive: true };
if (search) {
where.OR = [
{
judul: {
contains: search,
mode: "insensitive" as const,
},
},
{
deskripsi: {
contains: search,
mode: "insensitive" as const,
},
},
{
deskripsiSingkat: {
contains: search,
mode: "insensitive" as const,
},
},
];
}
try {
const data = await prisma.pencegahanKriminalitas.findFirst({
where,
orderBy: { createdAt: "desc" }, // ambil yang terbaru
});
if (!data) {
return {
success: false,
message: "Data pencegahan kriminalitas tidak ditemukan",
};
}
return {
success: true,
message: "Success fetch first pencegahan kriminalitas",
data,
};
} catch (error) {
console.error("Find first error:", error);
return {
success: false,
message: "Failed fetch first pencegahan kriminalitas",
};
}
}

View File

@@ -16,18 +16,21 @@ export default async function pencegahanKriminalitasFindMany(context: Context) {
if (search) {
where.OR = [
{
programKeamanan: {
nama: { contains: search, mode: "insensitive" as const },
judul: {
contains: search,
mode: "insensitive" as const,
},
},
{
tipsKeamanan: {
judul: { contains: search, mode: "insensitive" as const },
deskripsi: {
contains: search,
mode: "insensitive" as const,
},
},
{
videoKeamanan: {
judul: { contains: search, mode: "insensitive" as const },
deskripsiSingkat: {
contains: search,
mode: "insensitive" as const,
},
},
];
@@ -36,11 +39,6 @@ export default async function pencegahanKriminalitasFindMany(context: Context) {
const [data, total] = await Promise.all([
prisma.pencegahanKriminalitas.findMany({
where,
include: {
programKeamanan: true,
tipsKeamanan: true,
videoKeamanan: true,
},
skip,
take: limit,
orderBy: { createdAt: "desc" },

View File

@@ -22,11 +22,6 @@ export default async function pencegahanKriminalitasFindUnique(request: Request)
const data = await prisma.pencegahanKriminalitas.findUnique({
where: {id},
include: {
programKeamanan: true,
tipsKeamanan: true,
videoKeamanan: true,
}
})
if (!data) {

View File

@@ -4,6 +4,7 @@ import pencegahanKriminalitasFindUnique from "./findUnique";
import pencegahanKriminalitasFindMany from "./findMany";
import pencegahanKriminalitasDelete from "./del";
import pencegahanKriminalitasUpdate from "./updt";
import pencegahanKriminalitasFindFirst from "./findFirst";
const PencegahanKriminalitas = new Elysia({
prefix: "pencegahankriminalitas",
@@ -11,22 +12,10 @@ const PencegahanKriminalitas = new Elysia({
})
.post("/create", pencegahanKriminalitasCreate, {
body: t.Object({
programKeamanan: t.Object({
nama: t.String(),
deskripsi: t.String(),
slug: t.String(),
}),
tipsKeamanan: t.Object({
judul: t.String(),
konten: t.String(),
slug: t.String(),
}),
videoKeamanan: t.Object({
judul: t.String(),
deskripsi: t.String(),
videoUrl: t.String(),
slug: t.String(),
}),
judul: t.String(),
deskripsi: t.String(),
deskripsiSingkat: t.String(),
linkVideo: t.String(),
}),
})
.get("/find-many", pencegahanKriminalitasFindMany)
@@ -36,31 +25,20 @@ const PencegahanKriminalitas = new Elysia({
);
return response;
})
.get("/find-first", pencegahanKriminalitasFindFirst)
.delete("/del/:id", pencegahanKriminalitasDelete)
.put(
":/id",
"/:id",
async (context) => {
const response = await pencegahanKriminalitasUpdate(context);
return response;
},
{
body: t.Object({
programKeamanan: t.Object({
nama: t.String(),
deskripsi: t.String(),
slug: t.String(),
}),
tipsKeamanan: t.Object({
judul: t.String(),
konten: t.String(),
slug: t.String(),
}),
videoKeamanan: t.Object({
judul: t.String(),
deskripsi: t.String(),
videoUrl: t.String(),
slug: t.String(),
}),
judul: t.String(),
deskripsi: t.String(),
deskripsiSingkat: t.String(),
linkVideo: t.String(),
}),
}
);

View File

@@ -3,110 +3,91 @@ import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormUpdate = Prisma.PencegahanKriminalitasGetPayload<{
select: {
id: true;
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
}
}>
select: {
id: true;
judul: true;
deskripsi: true;
deskripsiSingkat: true;
linkVideo: true;
};
}>;
export default async function pencegahanKriminalitasUpdate(context: Context) {
try {
const id = context.params?.id as string;
const body = (await context.body) as Omit<FormUpdate, "id">;
try {
const id = context.params?.id as string;
const body = (await context.body) as Omit<FormUpdate, "id">;
const {programKeamanan, tipsKeamanan, videoKeamanan} = body;
const { judul, deskripsi, deskripsiSingkat, linkVideo } = body;
if (!id) {
return new Response(JSON.stringify({
success: false,
message: "ID tidak diberikan",
}), {
status: 400,
headers: {
"Content-Type": "application/json",
},
});
if (!id) {
return new Response(
JSON.stringify({
success: false,
message: "ID tidak diberikan",
}),
{
status: 400,
headers: {
"Content-Type": "application/json",
},
}
const existing = await prisma.pencegahanKriminalitas.findUnique({
where: { id },
include: {
programKeamanan: true,
tipsKeamanan: true,
videoKeamanan: true,
}
});
if (!existing) {
return new Response(JSON.stringify({
success: false,
message: "Pencegahan kriminalitas tidak ditemukan",
}), {
status: 404,
headers: {
"Content-Type": "application/json",
},
});
}
await prisma.programKeamanan.update({
where: {id: existing.programKeamananId},
data: {
nama: programKeamanan.nama,
deskripsi: programKeamanan.deskripsi,
slug: programKeamanan.slug,
}
});
await prisma.tipsKeamanan.update({
where: {id: existing.tipsKeamananId},
data: {
judul: tipsKeamanan.judul,
konten: tipsKeamanan.konten,
slug: tipsKeamanan.slug,
}
});
await prisma.videoKeamanan.update({
where: {id: existing.videoKeamananId},
data: {
judul: videoKeamanan.judul,
deskripsi: videoKeamanan.deskripsi,
videoUrl: videoKeamanan.videoUrl,
slug: videoKeamanan.slug,
}
});
const updated = await prisma.pencegahanKriminalitas.update({
where: { id },
data: {
programKeamananId: programKeamanan.id,
tipsKeamananId: tipsKeamanan.id,
videoKeamananId: videoKeamanan.id,
}
});
return new Response(JSON.stringify({
success: true,
message: "Success update pencegahan kriminalitas",
data: updated,
}), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
} catch (error) {
console.error("Update error:", error);
return new Response(JSON.stringify({
success: false,
message: "Failed update pencegahan kriminalitas",
}), {
status: 500,
headers: {
"Content-Type": "application/json",
},
});
);
}
const existing = await prisma.pencegahanKriminalitas.findUnique({
where: { id },
});
if (!existing) {
return new Response(
JSON.stringify({
success: false,
message: "Pencegahan kriminalitas tidak ditemukan",
}),
{
status: 404,
headers: {
"Content-Type": "application/json",
},
}
);
}
const updated = await prisma.pencegahanKriminalitas.update({
where: { id },
data: {
judul,
deskripsi,
deskripsiSingkat,
linkVideo,
},
});
return new Response(
JSON.stringify({
success: true,
message: "Success update pencegahan kriminalitas",
data: updated,
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
},
}
);
} catch (error) {
console.error("Update error:", error);
return new Response(
JSON.stringify({
success: false,
message: "Failed update pencegahan kriminalitas",
}),
{
status: 500,
headers: {
"Content-Type": "application/json",
},
}
);
}
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,141 +1,138 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
import colors from '@/con/colors';
import { Box, Button, Flex, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { Box, Button, Center, Flex, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowRight } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function Page() {
const kriminalitasState = useProxy(pencegahanKriminalitasState);
const findFirst = useProxy(pencegahanKriminalitasState.findFirst);
const router = useTransitionRouter();
const {
data,
loading,
page,
load,
} = kriminalitasState.findMany;
useEffect(() => {
if (!findFirst.data && !findFirst.loading) {
kriminalitasState.findFirst.load();
}
}, [findFirst.data, findFirst.loading]);
useShallowEffect(() => {
const LIMIT = 3;
load(page, LIMIT);
}, [page]);
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={600} radius="md" />
</Stack>
)
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
Pencegahan Kriminalitas
</Text>
<Text c={colors['blue-button']} fz={{ base: 'h4', md: 'h3' }}>
Keamanan Komunitas & Pencegahan Kriminal
</Text>
</Box>
<SimpleGrid
px={{ base: 20, md: 100 }}
cols={{ base: 1, md: 2 }}
spacing="xl"
>
<Box pt={{ base: 0, md: 35 }}>
<Text c={colors['blue-button']} fz={{ base: 'h4', md: 'h3' }}>
Community Safety & Crime Prevention
<Paper p="xl" radius="xl" shadow="lg" >
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold">
Program Keamanan Berjalan
</Text>
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
Emergency Contacts
</Text>
<Group>
<Stack pt={30} gap="lg">
{data.length > 0 ? (
data.map((item) => (
<Paper key={item.id} p="md" bg={colors['blue-button']} radius="md" shadow="sm" onClick={() => router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`)}>
<Stack gap={"xs"}>
<Flex align="center" justify="space-between">
<Text fz="h3" c={colors['white-1']}>
{item.judul}
</Text>
<IconArrowRight size={28} color={colors['white-1']} />
</Flex>
</Stack>
</Paper>
))
) : (
<Text color="dimmed">
Tidak ada data pencegahan kriminalitas yang cocok
</Text>
)}
<Button
rightSection={<IconArrowRight />}
mt={20}
fullWidth
radius="xl"
size="md"
bg={colors['blue-button']}
c={colors['white-1']}
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
onClick={() => router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/program-lainnya`)}
>
View Details
Jelajahi Program Lainnya
</Button>
</Group>
</Box>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
<Paper p="lg" radius="xl" shadow="md">
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
How to Keep Your Neighborhood Safe
</Text>
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
Practical safety habits everyone can apply daily to reduce risks.
</Text>
<Group>
<Button
rightSection={<IconArrowRight />}
mt={20}
radius="xl"
size="md"
bg={colors['blue-button']}
c={colors['white-1']}
>
Learn More
</Button>
</Group>
</Paper>
<Paper p="lg" radius="xl" shadow="md">
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
Recognizing Criminal Activities
</Text>
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
Key warning signs and behavior patterns you should stay aware of.
</Text>
<Group>
<Button
rightSection={<IconArrowRight />}
mt={20}
radius="xl"
size="md"
bg={colors['blue-button']}
c={colors['white-1']}
>
Learn More
</Button>
</Group>
</Paper>
</SimpleGrid>
<Paper p="xl" radius="xl" shadow="lg">
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold">
Ongoing Security Programs
</Text>
<Stack pt={30} gap="lg">
{['Night Patrol', 'Neighborhood Watch', 'Emergency Preparedness'].map((program, i) => (
<Paper key={i} p="md" bg={colors['blue-button']} radius="md" shadow="sm">
<Flex align="center" justify="space-between">
<Text fz="h3" c={colors['white-1']}>
{program}
</Text>
<IconArrowRight size={28} color={colors['white-1']} />
</Flex>
</Paper>
))}
<Box pt={25}>
<Button
fullWidth
radius="xl"
size="md"
bg={colors['blue-button']}
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
>
Explore More Programs
</Button>
</Box>
</Stack>
</Paper>
<Box>
<Paper p="xl" radius="xl" shadow="lg">
<Box style={{ maxWidth: 560, width: '100%', aspectRatio: '16/9' }}>
<iframe
width="100%"
height="100%"
src="https://www.youtube.com/embed/p5OkdBgasWA?si=6lFKXeE9LN5zcJQq"
title="Community Safety Patrol"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
</Box>
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
Darmasaba Night Patrol
</Text>
<Text fz="h4" c={colors['blue-button']}>
A closer look at how the community works together to maintain safety.
</Text>
<Box pt={10}>
<Button
radius="xl"
size="md"
bg={colors['blue-button']}
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
>
Watch More Videos
</Button>
</Box>
</Paper>
{findFirst.loading ? (
<Center><Skeleton h={400} /></Center>
) : findFirst.data ? (
<Paper p="xl" radius="xl" shadow="lg">
{findFirst.data?.linkVideo ? (
<Box
component="iframe"
src={convertToEmbedUrl(findFirst.data.linkVideo)}
width="100%"
height={300}
allowFullScreen
style={{ borderRadius: 8 }}
/>
) : (
<Text fz="sm" c="dimmed">Tidak ada video</Text>
)}
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
{findFirst.data?.judul}
</Text>
<Text fz="h4" c={colors['blue-button']}>
{findFirst.data?.deskripsiSingkat}
</Text>
</Paper>
) : null}
</Box>
</SimpleGrid>
</Stack>
);
function convertToEmbedUrl(youtubeUrl: string): string {
try {
const url = new URL(youtubeUrl);
const videoId = url.searchParams.get("v");
if (!videoId) return youtubeUrl;
return `https://www.youtube.com/embed/${videoId}`;
} catch (err) {
console.error("Error converting YouTube URL to embed:", err);
return youtubeUrl;
}
}
}
export default Page;

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;