API & UI Admin Menu Keamanan Done

This commit is contained in:
2025-07-03 16:09:39 +08:00
parent 10361770b4
commit b92a974dcd
15 changed files with 779 additions and 178 deletions

View File

@@ -0,0 +1,85 @@
/*
Warnings:
- The values [SELESAI,PROSES,GAGAL] on the enum `StatusLaporan` will be removed. If these variants are still used in the database, this will fail.
- You are about to drop the column `icon` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `urutan` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `icon` on the `KontakItem` table. All the data in the column will be lost.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "StatusLaporan_new" AS ENUM ('Selesai', 'Proses', 'Gagal');
ALTER TABLE "LaporanPublik" ALTER COLUMN "status" TYPE "StatusLaporan_new" USING ("status"::text::"StatusLaporan_new");
ALTER TYPE "StatusLaporan" RENAME TO "StatusLaporan_old";
ALTER TYPE "StatusLaporan_new" RENAME TO "StatusLaporan";
DROP TYPE "StatusLaporan_old";
COMMIT;
-- AlterTable
ALTER TABLE "KontakDaruratKeamanan" DROP COLUMN "icon",
DROP COLUMN "urutan",
ADD COLUMN "imageId" TEXT;
-- AlterTable
ALTER TABLE "KontakItem" DROP COLUMN "icon",
ADD COLUMN "imageId" TEXT;
-- CreateTable
CREATE TABLE "LowonganPekerjaan" (
"id" TEXT NOT NULL,
"posisi" TEXT NOT NULL,
"namaPerusahaan" TEXT NOT NULL,
"lokasi" TEXT NOT NULL,
"tipePekerjaan" TEXT NOT NULL,
"gaji" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"kualifikasi" TEXT NOT NULL,
"tanggalPosting" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LowonganPekerjaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKemiskinan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"ikonUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"statistikId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProgramKemiskinan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StatistikKemiskinan" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"jumlah" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StatistikKemiskinan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "ProgramKemiskinan_statistikId_key" ON "ProgramKemiskinan"("statistikId");
-- CreateIndex
CREATE UNIQUE INDEX "StatistikKemiskinan_tahun_key" ON "StatistikKemiskinan"("tahun");
-- AddForeignKey
ALTER TABLE "KontakDaruratKeamanan" ADD CONSTRAINT "KontakDaruratKeamanan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProgramKemiskinan" ADD CONSTRAINT "ProgramKemiskinan_statistikId_fkey" FOREIGN KEY ("statistikId") REFERENCES "StatistikKemiskinan"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -1020,9 +1020,9 @@ model PenangananLaporanPublik {
}
enum StatusLaporan {
SELESAI
PROSES
GAGAL
Selesai
Proses
Gagal
}
model Pelapor {

View File

@@ -179,17 +179,20 @@ const keamananLingkunganState = proxy({
try {
keamananLingkunganState.edit.loading = true;
const response = await fetch(`/api/keamanan/keamananlingkungan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
const response = await fetch(
`/api/keamanan/keamananlingkungan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));

View File

@@ -0,0 +1,273 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
export type Status = "Selesai" | "Proses" | "Gagal";
const templateForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
tanggalWaktu: z.string().min(3, "Tanggal Waktu minimal 3 karakter"),
status: z.enum(["Selesai", "Proses", "Gagal"]),
penanganan: z.string(),
kronologi: z.string().optional(),
});
interface FormData {
judul: string;
lokasi: string;
tanggalWaktu: string;
status: Status;
penanganan: string;
kronologi: string;
}
const defaultForm: FormData = {
judul: "",
lokasi: "",
tanggalWaktu: new Date().toISOString(),
status: "Proses",
penanganan: "",
kronologi: "",
};
const laporanPublikState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(laporanPublikState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
laporanPublikState.create.loading = true;
// Ensure we have a valid date
if (!laporanPublikState.create.form.tanggalWaktu) {
return toast.error("Tanggal laporan harus diisi");
}
// Format the data before sending
const formData = {
...laporanPublikState.create.form,
// Ensure the date is in the correct format for the API
tanggalWaktu: new Date(laporanPublikState.create.form.tanggalWaktu).toISOString()
};
console.log("Sending form data:", formData); // Debug log
const res = await ApiFetch.api.keamanan.laporanpublik["create"].post(
formData
);
if (res.error) {
console.error("API Error:", res.error);
throw new Error("Failed to create laporan publik");
}
if (res.status === 200) {
laporanPublikState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.error("Error creating laporan publik:", error);
toast.error(error instanceof Error ? error.message : "Gagal membuat laporan publik");
throw error; // Re-throw to be handled by the caller
} finally {
laporanPublikState.create.loading = false;
}
},
resetForm() {
laporanPublikState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.LaporanPublikGetPayload<{
include: { penanganan: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get();
if (res.status === 200) {
laporanPublikState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.LaporanPublikGetPayload<{
include: { penanganan: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/laporanpublik/${id}`);
if (res.ok) {
const data = await res.json();
laporanPublikState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
laporanPublikState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
laporanPublikState.findUnique.data = null;
}
},
resetForm() {
laporanPublikState.findUnique.data = null;
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
laporanPublikState.delete.loading = true;
const response = await fetch(`/api/keamanan/laporanpublik/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Laporan publik berhasil dihapus"
);
await laporanPublikState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus laporan publik");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus laporan publik");
} finally {
laporanPublikState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/keamanan/laporanpublik/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
judul: data.judul,
lokasi: data.lokasi,
tanggalWaktu: data.tanggalWaktu,
status: data.status,
penanganan: data.penanganan,
kronologi: data.kronologi,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading keamanan lingkungan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(laporanPublikState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
laporanPublikState.edit.loading = true;
const response = await fetch(
`/api/keamanan/laporanpublik/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
lokasi: this.form.lokasi,
tanggalWaktu: this.form.tanggalWaktu,
status: this.form.status,
penanganan: this.form.penanganan,
kronologi: this.form.kronologi,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update laporan publik");
await laporanPublikState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update laporan publik");
}
} catch (error) {
console.error("Error updating laporan publik:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update laporan publik"
);
return false;
} finally {
laporanPublikState.edit.loading = false;
}
},
reset() {
laporanPublikState.edit.id = "";
laporanPublikState.edit.form = { ...defaultForm };
},
}
});
export default laporanPublikState;

View File

@@ -102,7 +102,7 @@ function ListProgramKemiskinan() {
{mounted && lineChart.length > 0 ? (<Box style={{ width: '100%', height: 'auto', }}>
<Box w={"100%"} style={{overflowX: 'auto'}}>
<LineChart
width={650}
width={820}
height={300}
data={lineChart}
>

View File

@@ -0,0 +1,151 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { DateTimePicker } from '@mantine/dates';
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';
export type Status = "Selesai" | "Proses" | "Gagal";
function EditLaporanPublik() {
const stateLaporan = useProxy(laporanPublikState)
const router = useRouter();
const params = useParams()
const [formData, setFormData] = useState({
judul: stateLaporan.edit.form.judul || '',
lokasi: stateLaporan.edit.form.lokasi || '',
tanggalWaktu: stateLaporan.edit.form.tanggalWaktu || '',
status: stateLaporan.edit.form.status || '',
penanganan: stateLaporan.edit.form.penanganan || '',
kronologi: stateLaporan.edit.form.kronologi || '',
})
useEffect(() => {
const loadLaporanPublik = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await stateLaporan.edit.load(id);
if (data) {
setFormData({
judul: data.judul || '',
lokasi: data.lokasi || '',
tanggalWaktu: data.tanggalWaktu || '',
status: data.status || '',
penanganan: data.penanganan?.map((p: any) => p.deskripsi)[0] || '',
kronologi: data.kronologi || '',
});
}
} catch (error) {
console.error('Error loading laporan publik:', error);
}
}
loadLaporanPublik();
}, [params?.id]);
const handleSubmit = async () => {
try {
stateLaporan.edit.form = {
...stateLaporan.edit.form,
judul: formData.judul,
lokasi: formData.lokasi,
tanggalWaktu: formData.tanggalWaktu,
status: formData.status,
penanganan: formData.penanganan,
kronologi: formData.kronologi,
}
await stateLaporan.edit.update();
toast.success("Laporan Publik berhasil diperbarui!");
router.push("/admin/keamanan/laporan-publik");
} catch (error) {
console.error("Error updating kontak darurat:", error);
toast.error("Terjadi kesalahan saat memperbarui kontak darurat");
}
}
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}>Edit Laporan Publik</Title>
<TextInput
value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
label={<Text fw={"bold"} fz={"sm"}>Judul Laporan Publik</Text>}
placeholder='Masukkan judul LaporanPublik'
/>
<TextInput
value={formData.lokasi}
onChange={(e) => setFormData({ ...formData, lokasi: e.target.value })}
label={<Text fw={"bold"} fz={"sm"}>Lokasi Laporan Publik</Text>}
placeholder='Masukkan lokasi LaporanPublik'
/>
<DateTimePicker
label="Tanggal Laporan Publik"
value={
formData.tanggalWaktu
? new Date(formData.tanggalWaktu)
: null
}
onChange={(val) => {
if (val) {
setFormData({ ...formData, tanggalWaktu: val.toString() });
} else {
setFormData({ ...formData, tanggalWaktu: "" }); // Reset kalau dikosongkan
}
}}
/>
<Select
value={formData.status}
onChange={(e) => setFormData({ ...formData, status: e?.valueOf() as Status })}
label={<Text fw={"bold"} fz={"sm"}>Status Laporan Publik</Text>}
placeholder='Masukkan status LaporanPublik'
data={[
{ value: "Selesai", label: "Selesai" },
{ value: "Proses", label: "Proses" },
{ value: "Gagal", label: "Gagal" },
]}
/>
<TextInput
value={formData.kronologi}
onChange={(e) => setFormData({ ...formData, kronologi: e.target.value })}
label={<Text fw={"bold"} fz={"sm"}>Kronologi Laporan Publik</Text>}
placeholder='Masukkan kronologi LaporanPublik'
/>
<Text fw={"bold"} fz={"sm"}>Penanganan Laporan Publik</Text>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Laporan Publik</Text>
<EditEditor
value={formData.penanganan}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, penanganan: htmlContent }));
stateLaporan.edit.form.penanganan = htmlContent;
}}
/>
</Box>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditLaporanPublik;

View File

@@ -0,0 +1,128 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import laporanPublikState from '../../../_state/keamanan/laporan-publik';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailLaporanPublik() {
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const stateLaporan = useProxy(laporanPublikState)
const params = useParams()
const router = useRouter();
useShallowEffect(() => {
stateLaporan.findUnique.load(params?.id as string)
}, [])
const handleDelete = () => {
if (selectedId) {
stateLaporan.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/keamanan/laporan-publik")
}
}
if (!stateLaporan.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
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>
<Text fz={"xl"} fw={"bold"}>Detail Laporan Publik</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fz={"lg"} fw={"bold"}>Judul Laporan Publik</Text>
<Text fz={"lg"}>{stateLaporan.findUnique.data?.judul}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tanggal Laporan Publik</Text>
<Text fz={"lg"}>
{stateLaporan.findUnique.data?.tanggalWaktu
? new Date(stateLaporan.findUnique.data.tanggalWaktu).toLocaleString('id-ID')
: '-'}
</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Lokasi</Text>
<Text fz={"lg"}>{stateLaporan.findUnique.data?.lokasi}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Status</Text>
<Text fz={"lg"}>{stateLaporan.findUnique.data?.status}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kronologi</Text>
<Text fz={"lg"}>
{stateLaporan.findUnique.data?.kronologi || '-'}
</Text>
</Box>
<Text fz={"lg"} fw={"bold"}>Penanganan</Text>
{stateLaporan.findUnique.data?.penanganan?.map((item, index) => (
<Box key={index}>
<Text fz={"lg"} fw={"bold"}>Deskripsi Penanganan</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
</Box>
))}
{!stateLaporan.findUnique.data?.penanganan?.length && (
<Text fz={"lg"} fs="italic">Belum ada penanganan</Text>
)}
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (stateLaporan.findUnique.data) {
setSelectedId(stateLaporan.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={stateLaporan.delete.loading || !stateLaporan.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (stateLaporan.findUnique.data) {
router.push(`/admin/keamanan/laporan-publik/${stateLaporan.findUnique.data.id}/edit`);
}
}}
disabled={!stateLaporan.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus laporan publik ini?'
/>
</Stack>
</Paper>
</Box>
);
}
export default DetailLaporanPublik;

View File

@@ -1,47 +1,105 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { DateTimePicker } from '@mantine/dates';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { KeamananEditor } from '../../_com/keamananEditor';
import { useProxy } from 'valtio/utils';
import CreateEditor from '../../../_com/createEditor';
import laporanPublikState from '../../../_state/keamanan/laporan-publik';
export type Status = "Selesai" | "Proses" | "Gagal";
function CreateLaporanPublik() {
const stateLaporan = useProxy(laporanPublikState)
const router = useRouter();
const resetForm = () => {
stateLaporan.create.form = {
judul: "",
lokasi: "",
tanggalWaktu: "",
status: "Proses" as Status,
penanganan: "",
kronologi: "",
}
}
const handleSubmit = async () => {
await stateLaporan.create.create();
resetForm();
router.push('/admin/keamanan/laporan-publik');
}
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Laporan Publik</Title>
<Box>
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
<IconImageInPicture size={50} />
</Box>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Laporan Publik</Text>}
placeholder='Masukkan judul LaporanPublik'
value={stateLaporan.create.form.judul}
onChange={(e) => stateLaporan.create.form.judul = e.target.value}
label={<Text fw={"bold"} fz={"sm"}>Judul Laporan Publik</Text>}
placeholder='Masukkan judul LaporanPublik'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Laporan Publik</Text>}
placeholder='Masukkan tanggal LaporanPublik'
value={stateLaporan.create.form.lokasi}
onChange={(e) => stateLaporan.create.form.lokasi = e.target.value}
label={<Text fw={"bold"} fz={"sm"}>Lokasi Laporan Publik</Text>}
placeholder='Masukkan lokasi LaporanPublik'
/>
<DateTimePicker
label="Tanggal Laporan Publik"
value={
stateLaporan.create.form.tanggalWaktu
? new Date(stateLaporan.create.form.tanggalWaktu)
: null
}
onChange={(val) => {
if (val) {
stateLaporan.create.form.tanggalWaktu = val.toString();
} else {
stateLaporan.create.form.tanggalWaktu = ""; // Reset kalau dikosongkan
}
}}
/>
<Select
value={stateLaporan.create.form.status}
onChange={(e) => stateLaporan.create.form.status = e?.valueOf() as Status}
label={<Text fw={"bold"} fz={"sm"}>Status Laporan Publik</Text>}
placeholder='Masukkan status LaporanPublik'
data={[
{ value: "Selesai", label: "Selesai" },
{ value: "Proses", label: "Proses" },
{ value: "Gagal", label: "Gagal" },
]}
/>
<TextInput
value={stateLaporan.create.form.kronologi}
onChange={(e) => stateLaporan.create.form.kronologi = e.target.value}
label={<Text fw={"bold"} fz={"sm"}>Kronologi Laporan Publik</Text>}
placeholder='Masukkan kronologi LaporanPublik'
/>
<Text fw={"bold"} fz={"sm"}>Penanganan Laporan Publik</Text>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Laporan Publik</Text>
<KeamananEditor
showSubmit={false}
<CreateEditor
value={stateLaporan.create.form.penanganan}
onChange={(e) => stateLaporan.create.form.penanganan = e}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Stack>
</Paper>
</Box>
</Box>
);
}

View File

@@ -1,70 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailLaporanPublik() {
const router = useRouter();
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>
<Text fz={"xl"} fw={"bold"}>Detail Laporan Publik</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fz={"lg"} fw={"bold"}>Judul Laporan Publik</Text>
<Text fz={"lg"}>Test Judul</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tanggal Laporan Publik</Text>
<Text fz={"lg"}>Test Tanggal</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
<Text fz={"lg"}>Test Deskripsi</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
<Image src={"/"} alt="gambar" />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Konten</Text>
<Text fz={"lg"} >Test Konten</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/keamanan/laporan-publik/edit')} color="green">
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus potensi ini?"
/> */}
</Box>
);
}
export default DetailLaporanPublik;

View File

@@ -1,48 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { KeamananEditor } from '../../_com/keamananEditor';
function EditLaporanPublik() {
const router = useRouter();
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}>Edit Laporan Publik</Title>
<Box>
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
<IconImageInPicture size={50} />
</Box>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Laporan Publik</Text>}
placeholder='Masukkan judul Laporan Publik'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Laporan Publik</Text>}
placeholder='Masukkan tanggal Laporan Publik'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Laporan Publik</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditLaporanPublik;

View File

@@ -1,10 +1,13 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import laporanPublikState from '../../_state/keamanan/laporan-publik';
import { useShallowEffect } from '@mantine/hooks';
function LaporanPublik() {
return (
@@ -20,7 +23,20 @@ function LaporanPublik() {
}
function ListLaporanPublik() {
const stateLaporan = useProxy(laporanPublikState)
const router = useRouter();
useShallowEffect(() => {
stateLaporan.findMany.load()
}, [])
if (!stateLaporan.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
@@ -33,21 +49,25 @@ function ListLaporanPublik() {
<TableTr>
<TableTh>Judul Laporan Publik</TableTh>
<TableTh>Tanggal Laporan Publik</TableTh>
<TableTh>Status</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Laporan Publik 1</TableTd>
<TableTd>0896232831883</TableTd>
<TableTd>Laporan Publik 1</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/keamanan/laporan-publik/detail')}>
{stateLaporan.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.judul}</TableTd>
<TableTd>{new Date(item.tanggalWaktu).toLocaleDateString('id-ID')}</TableTd>
<TableTd>{item.status}</TableTd>
<TableTd>{item.kronologi}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/laporan-publik/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>

View File

@@ -5,8 +5,8 @@ type LaporanPublikInput = {
judul: string;
lokasi: string;
tanggalWaktu: string;
status: "SELESAI" | "PROSES" | "GAGAL";
penanganan: string[];
status: "Selesai" | "Proses" | "Gagal";
penanganan: string;
kronologi?: string;
};
@@ -21,9 +21,9 @@ const laporanPublikCreate = async (context: Context) => {
tanggalWaktu: new Date(tanggalWaktu),
status,
penanganan: {
create: penanganan.map((item) => ({
deskripsi: item,
})),
create: {
deskripsi: penanganan,
},
},
kronologi,
},

View File

@@ -15,11 +15,11 @@ const LaporanPublik = new Elysia({
lokasi: t.String(),
tanggalWaktu: t.String(), // ISO string
status: t.Union([
t.Literal("SELESAI"),
t.Literal("PROSES"),
t.Literal("GAGAL"),
t.Literal("Selesai"),
t.Literal("Proses"),
t.Literal("Gagal"),
]),
penanganan: t.Array(t.String()), // 🛠️ ARRAY of strings
penanganan: t.String(), // 🛠️ ARRAY of strings
kronologi: t.Optional(t.String()),
}),
})
@@ -31,11 +31,11 @@ const LaporanPublik = new Elysia({
lokasi: t.String(),
tanggalWaktu: t.String(), // ISO string
status: t.Union([
t.Literal("SELESAI"),
t.Literal("PROSES"),
t.Literal("GAGAL"),
t.Literal("Selesai"),
t.Literal("Proses"),
t.Literal("Gagal"),
]),
penanganan: t.Array(t.String()), // 🛠️ ARRAY of strings
penanganan: t.String(), // 🛠️ ARRAY of strings
kronologi: t.Optional(t.String()),
}),
})

View File

@@ -5,8 +5,8 @@ type LaporanPublikUpdateInput = {
judul: string;
lokasi: string;
tanggalWaktu: string;
status: "SELESAI" | "PROSES" | "GAGAL";
penanganan: string[];
status: "Selesai" | "Proses" | "Gagal";
penanganan: string;
kronologi?: string;
};
@@ -32,9 +32,9 @@ const LaporanPublikUpdate = async (context: Context) => {
status,
kronologi,
penanganan: {
create: penanganan.map((item) => ({
deskripsi: item,
})),
create: {
deskripsi: penanganan,
},
},
},
include: {

View File

@@ -46,8 +46,9 @@ const menuTipsKeamananDelete = async (context: Context) => {
});
return {
status: 200,
body: deleted,
success: true,
message: "Success delete menu tips keamanan",
data: deleted,
};
};
export default menuTipsKeamananDelete;