Fix Menu Desa Admin & User
This commit is contained in:
BIN
public/beasiswa-siswa.png
Normal file
BIN
public/beasiswa-siswa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 446 KiB |
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +11,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
Tooltip
|
Tooltip,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -24,7 +25,7 @@ function EditKategoriBerita() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: editState.update.form.name || '',
|
name: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -48,8 +49,16 @@ function EditKategoriBerita() {
|
|||||||
loadKategori();
|
loadKategori();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
// update global state hanya saat submit
|
||||||
editState.update.form = {
|
editState.update.form = {
|
||||||
...editState.update.form,
|
...editState.update.form,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
@@ -94,10 +103,11 @@ function EditKategoriBerita() {
|
|||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
name="name"
|
||||||
label="Nama Kategori Berita"
|
label="Nama Kategori Berita"
|
||||||
placeholder="Masukkan nama kategori berita"
|
placeholder="Masukkan nama kategori berita"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { Dropzone } from "@mantine/dropzone";
|
import { Dropzone } from "@mantine/dropzone";
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
|
import {
|
||||||
|
IconArrowBack,
|
||||||
|
IconPhoto,
|
||||||
|
IconUpload,
|
||||||
|
IconX,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -33,16 +38,17 @@ function EditBerita() {
|
|||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
judul: beritaState.berita.edit.form.judul || "",
|
judul: "",
|
||||||
deskripsi: beritaState.berita.edit.form.deskripsi || "",
|
deskripsi: "",
|
||||||
kategoriBeritaId: beritaState.berita.edit.form.kategoriBeritaId || "",
|
kategoriBeritaId: "",
|
||||||
content: beritaState.berita.edit.form.content || "",
|
content: "",
|
||||||
imageId: beritaState.berita.edit.form.imageId || "",
|
imageId: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load berita by id saat pertama kali
|
// Load kategori + berita
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
beritaState.kategoriBerita.findMany.load();
|
beritaState.kategoriBerita.findMany.load();
|
||||||
|
|
||||||
const loadBerita = async () => {
|
const loadBerita = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -71,8 +77,13 @@ function EditBerita() {
|
|||||||
loadBerita();
|
loadBerita();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleChange = (field: string, value: string) => {
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Update global state hanya sekali di sini
|
||||||
beritaState.berita.edit.form = {
|
beritaState.berita.edit.form = {
|
||||||
...beritaState.berita.edit.form,
|
...beritaState.berita.edit.form,
|
||||||
...formData,
|
...formData,
|
||||||
@@ -103,6 +114,7 @@ function EditBerita() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: "sm", md: "lg" }} py="md">
|
<Box px={{ base: "sm", md: "lg" }} py="md">
|
||||||
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Button
|
<Button
|
||||||
@@ -119,6 +131,7 @@ function EditBerita() {
|
|||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
<Paper
|
<Paper
|
||||||
w={{ base: "100%", md: "50%" }}
|
w={{ base: "100%", md: "50%" }}
|
||||||
bg={colors["white-1"]}
|
bg={colors["white-1"]}
|
||||||
@@ -131,18 +144,14 @@ function EditBerita() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Judul"
|
label="Judul"
|
||||||
placeholder="Masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
defaultValue={formData.judul}
|
value={formData.judul}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange("judul", e.target.value)}
|
||||||
setFormData({ ...formData, judul: e.target.value })
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={formData.kategoriBeritaId}
|
value={formData.kategoriBeritaId}
|
||||||
onChange={(val) =>
|
onChange={(val) => handleChange("kategoriBeritaId", val || "")}
|
||||||
setFormData({ ...formData, kategoriBeritaId: val || "" })
|
|
||||||
}
|
|
||||||
label="Kategori"
|
label="Kategori"
|
||||||
placeholder="Pilih kategori"
|
placeholder="Pilih kategori"
|
||||||
data={
|
data={
|
||||||
@@ -160,13 +169,12 @@ function EditBerita() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Deskripsi Singkat"
|
label="Deskripsi Singkat"
|
||||||
placeholder="Masukkan deskripsi singkat"
|
placeholder="Masukkan deskripsi singkat"
|
||||||
defaultValue={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange("deskripsi", e.target.value)}
|
||||||
setFormData({ ...formData, deskripsi: e.target.value })
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Upload Gambar */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw="bold" fz="sm" mb={6}>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
Gambar Berita
|
Gambar Berita
|
||||||
@@ -179,7 +187,9 @@ function EditBerita() {
|
|||||||
setPreviewImage(URL.createObjectURL(selectedFile));
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error("File tidak valid, gunakan format gambar")}
|
onReject={() =>
|
||||||
|
toast.error("File tidak valid, gunakan format gambar")
|
||||||
|
}
|
||||||
maxSize={5 * 1024 ** 2}
|
maxSize={5 * 1024 ** 2}
|
||||||
accept={{ "image/*": [] }}
|
accept={{ "image/*": [] }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -187,7 +197,11 @@ function EditBerita() {
|
|||||||
>
|
>
|
||||||
<Group justify="center" gap="xl" mih={180}>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Accept>
|
<Dropzone.Accept>
|
||||||
<IconUpload size={48} color={colors["blue-button"]} stroke={1.5} />
|
<IconUpload
|
||||||
|
size={48}
|
||||||
|
color={colors["blue-button"]}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
</Dropzone.Accept>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Reject>
|
||||||
<IconX size={48} color="red" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
@@ -223,18 +237,20 @@ function EditBerita() {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Konten */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold">
|
<Text fz="sm" fw="bold">
|
||||||
Konten
|
Konten
|
||||||
</Text>
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.content}
|
value={formData.content}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) =>
|
||||||
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
setFormData((prev) => ({ ...prev, content: htmlContent }))
|
||||||
beritaState.berita.edit.form.content = htmlContent;
|
}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Action */}
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
|
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
|
||||||
@@ -31,17 +31,19 @@ function EditVideo() {
|
|||||||
linkVideo: '',
|
linkVideo: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// load data video sekali saat id ada
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadVideo = async () => {
|
const loadVideo = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await videoState.update.load(id);
|
const data = await videoState.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || '',
|
name: data.name ?? '',
|
||||||
deskripsi: data.deskripsi || '',
|
deskripsi: data.deskripsi ?? '',
|
||||||
linkVideo: data.linkVideo || '',
|
linkVideo: data.linkVideo ?? '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -49,10 +51,16 @@ function EditVideo() {
|
|||||||
toast.error('Gagal memuat data video');
|
toast.error('Gagal memuat data video');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadVideo();
|
loadVideo();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
|
const handleChange = useCallback(
|
||||||
|
(field: keyof typeof formData, value: string) => {
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||||
@@ -63,7 +71,6 @@ function EditVideo() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
videoState.update.form = {
|
videoState.update.form = {
|
||||||
...videoState.update.form,
|
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
deskripsi: formData.deskripsi,
|
deskripsi: formData.deskripsi,
|
||||||
linkVideo: formData.linkVideo,
|
linkVideo: formData.linkVideo,
|
||||||
@@ -77,11 +84,18 @@ function EditVideo() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -102,8 +116,8 @@ function EditVideo() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Judul Video"
|
label="Judul Video"
|
||||||
placeholder="Masukkan judul video"
|
placeholder="Masukkan judul video"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => handleChange('name', e.currentTarget.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -111,8 +125,8 @@ function EditVideo() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Link Video YouTube"
|
label="Link Video YouTube"
|
||||||
placeholder="https://www.youtube.com/watch?v=abc123"
|
placeholder="https://www.youtube.com/watch?v=abc123"
|
||||||
defaultValue={formData.linkVideo}
|
value={formData.linkVideo}
|
||||||
onChange={(e) => setFormData({ ...formData, linkVideo: e.currentTarget.value })}
|
onChange={(e) => handleChange('linkVideo', e.currentTarget.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{embedLink && (
|
{embedLink && (
|
||||||
@@ -135,7 +149,7 @@ function EditVideo() {
|
|||||||
</Title>
|
</Title>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
onChange={(val) => handleChange('deskripsi', val)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
Tooltip
|
Tooltip,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -24,16 +24,19 @@ function EditAjukanPermohonan() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const stateAjukan = useProxy(stateLayananDesa.ajukanPermohonan);
|
const stateAjukan = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||||
|
|
||||||
|
// State lokal form
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
nama: stateAjukan.edit.form.nama,
|
nama: '',
|
||||||
nik: stateAjukan.edit.form.nik,
|
nik: '',
|
||||||
alamat: stateAjukan.edit.form.alamat,
|
alamat: '',
|
||||||
nomorKk: stateAjukan.edit.form.nomorKk,
|
nomorKk: '',
|
||||||
kategoriId: stateAjukan.edit.form.kategoriId,
|
kategoriId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load data awal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateLayananDesa.suratKeterangan.findManyAll.load();
|
stateLayananDesa.suratKeterangan.findManyAll.load();
|
||||||
|
|
||||||
const loadAjukan = async () => {
|
const loadAjukan = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -58,6 +61,14 @@ function EditAjukanPermohonan() {
|
|||||||
loadAjukan();
|
loadAjukan();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
// Handler untuk input controlled
|
||||||
|
const handleChange = (field: string, value: string) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateAjukan.edit.form = {
|
stateAjukan.edit.form = {
|
||||||
@@ -98,8 +109,8 @@ function EditAjukanPermohonan() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Nama"
|
label="Nama"
|
||||||
placeholder="Masukkan nama"
|
placeholder="Masukkan nama"
|
||||||
defaultValue={formData.nama}
|
value={formData.nama}
|
||||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
onChange={(e) => handleChange('nama', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -107,16 +118,16 @@ function EditAjukanPermohonan() {
|
|||||||
type="number"
|
type="number"
|
||||||
label="NIK"
|
label="NIK"
|
||||||
placeholder="Masukkan NIK"
|
placeholder="Masukkan NIK"
|
||||||
defaultValue={formData.nik}
|
value={formData.nik}
|
||||||
onChange={(e) => setFormData({ ...formData, nik: e.target.value })}
|
onChange={(e) => handleChange('nik', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Alamat"
|
label="Alamat"
|
||||||
placeholder="Masukkan alamat"
|
placeholder="Masukkan alamat"
|
||||||
defaultValue={formData.alamat}
|
value={formData.alamat}
|
||||||
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
onChange={(e) => handleChange('alamat', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -124,8 +135,8 @@ function EditAjukanPermohonan() {
|
|||||||
type="number"
|
type="number"
|
||||||
label="Nomor KK"
|
label="Nomor KK"
|
||||||
placeholder="Masukkan nomor KK"
|
placeholder="Masukkan nomor KK"
|
||||||
defaultValue={formData.nomorKk}
|
value={formData.nomorKk}
|
||||||
onChange={(e) => setFormData({ ...formData, nomorKk: e.target.value })}
|
onChange={(e) => handleChange('nomorKk', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -137,18 +148,7 @@ function EditAjukanPermohonan() {
|
|||||||
value: item.id,
|
value: item.id,
|
||||||
}))}
|
}))}
|
||||||
value={formData.kategoriId || null}
|
value={formData.kategoriId || null}
|
||||||
onChange={(val: string | null) => {
|
onChange={(val) => handleChange('kategoriId', val || '')}
|
||||||
if (val) {
|
|
||||||
const selected = stateLayananDesa.suratKeterangan.findMany.data?.find(
|
|
||||||
(item) => item.id === val
|
|
||||||
);
|
|
||||||
if (selected) {
|
|
||||||
stateAjukan.edit.form.kategoriId = selected.id;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stateAjukan.edit.form.kategoriId = '';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
searchable
|
searchable
|
||||||
clearable
|
clearable
|
||||||
nothingFoundMessage="Tidak ditemukan"
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
'use client'
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
|
||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -12,17 +23,22 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
function EditPelayananPendudukNonPermanent() {
|
function EditPelayananPendudukNonPermanent() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const statePendudukNonPermanent = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
|
const statePendudukNonPermanent = useProxy(
|
||||||
const [formData, setFormData] = useState({
|
stateLayananDesa.pelayananPendudukNonPermanen
|
||||||
name: statePendudukNonPermanent.findById.data?.name || '',
|
);
|
||||||
deskripsi: statePendudukNonPermanent.findById.data?.deskripsi || '',
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
deskripsi: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load data sekali dari backend
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPelayananPerizinan = async () => {
|
const loadData = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await statePendudukNonPermanent.update.load(id);
|
const data = await statePendudukNonPermanent.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -32,27 +48,48 @@ function EditPelayananPendudukNonPermanent() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading pelayanan perizinan berusaha:", error);
|
console.error('Error loading data:', error);
|
||||||
toast.error("Gagal memuat data pelayanan perizinan berusaha");
|
toast.error('Gagal memuat data pelayanan penduduk non permanent');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadPelayananPerizinan();
|
|
||||||
|
loadData();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleChange =
|
||||||
|
(field: keyof typeof formData) =>
|
||||||
|
(value: string) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (statePendudukNonPermanent.findById.data) {
|
if (!statePendudukNonPermanent.findById.data) return;
|
||||||
statePendudukNonPermanent.findById.data.name = formData.name;
|
|
||||||
statePendudukNonPermanent.findById.data.deskripsi = formData.deskripsi;
|
// Update global state hanya di submit
|
||||||
statePendudukNonPermanent.update.update(statePendudukNonPermanent.findById.data)
|
const updated = {
|
||||||
}
|
...statePendudukNonPermanent.findById.data,
|
||||||
router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent')
|
name: formData.name,
|
||||||
}
|
deskripsi: formData.deskripsi,
|
||||||
|
};
|
||||||
|
|
||||||
|
await statePendudukNonPermanent.update.update(updated);
|
||||||
|
router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -62,7 +99,7 @@ function EditPelayananPendudukNonPermanent() {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
w={{ base: "100%", md: "50%" }}
|
w={{ base: '100%', md: '50%' }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="md"
|
p="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -76,23 +113,19 @@ function EditPelayananPendudukNonPermanent() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Judul"
|
label="Judul"
|
||||||
placeholder="Masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange('name')(e.target.value)}
|
||||||
setFormData({ ...formData, name: e.target.value })
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Posisi Field */}
|
{/* Deskripsi Field */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold">
|
<Text fz="sm" fw="bold">
|
||||||
Deskripsi
|
Deskripsi
|
||||||
</Text>
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(htmlContent) => {
|
onChange={handleChange('deskripsi')}
|
||||||
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -104,7 +137,9 @@ function EditPelayananPendudukNonPermanent() {
|
|||||||
loading={statePendudukNonPermanent.update.loading}
|
loading={statePendudukNonPermanent.update.loading}
|
||||||
disabled={!formData.name}
|
disabled={!formData.name}
|
||||||
>
|
>
|
||||||
{statePendudukNonPermanent.update.loading ? 'Menyimpan...' : 'Simpan Perubahan'}
|
{statePendudukNonPermanent.update.loading
|
||||||
|
? 'Menyimpan...'
|
||||||
|
: 'Simpan Perubahan'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
'use client'
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -12,15 +21,18 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
function EditPelayananPerizinanBerusaha() {
|
function EditPelayananPerizinanBerusaha() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const statePerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha)
|
const statePerizinanBerusaha = useProxy(
|
||||||
|
stateLayananDesa.pelayananPerizinanBerusaha
|
||||||
|
);
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: statePerizinanBerusaha.findById.data?.name || '',
|
name: '',
|
||||||
deskripsi: statePerizinanBerusaha.findById.data?.deskripsi || '',
|
deskripsi: '',
|
||||||
link: statePerizinanBerusaha.findById.data?.link || '',
|
link: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// load data pertama kali
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPelayananPerizinan = async () => {
|
const loadPelayananPerizinan = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -35,22 +47,35 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading pelayanan perizinan berusaha:", error);
|
console.error('Error loading pelayanan perizinan berusaha:', error);
|
||||||
toast.error("Gagal memuat data pelayanan perizinan berusaha");
|
toast.error('Gagal memuat data pelayanan perizinan berusaha');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadPelayananPerizinan();
|
loadPelayananPerizinan();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleChange =
|
||||||
|
(field: keyof typeof formData) =>
|
||||||
|
(value: string) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
const { name, deskripsi, link } = formData;
|
||||||
if (statePerizinanBerusaha.findById.data) {
|
if (statePerizinanBerusaha.findById.data) {
|
||||||
statePerizinanBerusaha.findById.data.name = formData.name;
|
const updatedData = {
|
||||||
statePerizinanBerusaha.findById.data.deskripsi = formData.deskripsi;
|
...statePerizinanBerusaha.findById.data,
|
||||||
statePerizinanBerusaha.findById.data.link = formData.link;
|
name,
|
||||||
statePerizinanBerusaha.update.update(statePerizinanBerusaha.findById.data)
|
deskripsi,
|
||||||
}
|
link,
|
||||||
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha')
|
};
|
||||||
|
await statePerizinanBerusaha.update.update(updatedData);
|
||||||
|
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -58,7 +83,12 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -69,7 +99,7 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
|
|
||||||
{/* Form Section */}
|
{/* Form Section */}
|
||||||
<Paper
|
<Paper
|
||||||
w={{ base: "100%", md: "50%" }}
|
w={{ base: '100%', md: '50%' }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="md"
|
p="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -83,8 +113,8 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Judul"
|
label="Judul"
|
||||||
placeholder="Masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => handleChange('name')(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -92,8 +122,8 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Link"
|
label="Link"
|
||||||
placeholder="Masukkan link terkait"
|
placeholder="Masukkan link terkait"
|
||||||
defaultValue={formData.link}
|
value={formData.link}
|
||||||
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
onChange={(e) => handleChange('link')(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Deskripsi Field */}
|
{/* Deskripsi Field */}
|
||||||
@@ -101,7 +131,7 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
<Title order={6}>Deskripsi</Title>
|
<Title order={6}>Deskripsi</Title>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
onChange={handleChange('deskripsi')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -113,7 +143,9 @@ function EditPelayananPerizinanBerusaha() {
|
|||||||
loading={statePerizinanBerusaha.update.loading}
|
loading={statePerizinanBerusaha.update.loading}
|
||||||
disabled={!formData.name}
|
disabled={!formData.name}
|
||||||
>
|
>
|
||||||
{statePerizinanBerusaha.update.loading ? 'Menyimpan...' : 'Simpan Perubahan'}
|
{statePerizinanBerusaha.update.loading
|
||||||
|
? 'Menyimpan...'
|
||||||
|
: 'Simpan Perubahan'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
@@ -19,7 +18,7 @@ import {
|
|||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -28,17 +27,23 @@ function EditSuratKeterangan() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const stateSurat = useProxy(stateLayananDesa.suratKeterangan);
|
const stateSurat = useProxy(stateLayananDesa.suratKeterangan);
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
// state lokal untuk form
|
||||||
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
|
|
||||||
const [file, setFile] = useState<File | null>(null);
|
|
||||||
const [file2, setFile2] = useState<File | null>(null);
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateSurat.edit.form.name,
|
name: '',
|
||||||
deskripsi: stateSurat.edit.form.deskripsi,
|
deskripsi: '',
|
||||||
imageId: stateSurat.edit.form.imageId,
|
imageId: '',
|
||||||
image2Id: stateSurat.edit.form.image2Id,
|
image2Id: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// state file upload
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [file2, setFile2] = useState<File | null>(null);
|
||||||
|
|
||||||
|
// state preview gambar
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// load data awal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadSurat = async () => {
|
const loadSurat = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -64,15 +69,15 @@ function EditSuratKeterangan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadSurat();
|
loadSurat();
|
||||||
}, [params?.id]);
|
}, [params?.id, stateSurat.edit]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
// handler untuk submit
|
||||||
|
const handleSubmit = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
stateSurat.edit.form = {
|
// update form global hanya saat submit
|
||||||
...stateSurat.edit.form,
|
stateSurat.edit.form = { ...stateSurat.edit.form, ...formData };
|
||||||
...formData,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// upload file 1
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
@@ -80,6 +85,7 @@ function EditSuratKeterangan() {
|
|||||||
stateSurat.edit.form.imageId = uploaded.id;
|
stateSurat.edit.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upload file 2
|
||||||
if (file2) {
|
if (file2) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name });
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
@@ -94,7 +100,7 @@ function EditSuratKeterangan() {
|
|||||||
console.error('Error updating surat:', error);
|
console.error('Error updating surat:', error);
|
||||||
toast.error('Terjadi kesalahan saat memperbarui surat');
|
toast.error('Terjadi kesalahan saat memperbarui surat');
|
||||||
}
|
}
|
||||||
};
|
}, [formData, file, file2, router, stateSurat.edit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
@@ -119,21 +125,25 @@ function EditSuratKeterangan() {
|
|||||||
style={{ border: '1px solid #e0e0e0' }}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
{/* Input nama */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Nama Surat Keterangan"
|
label="Nama Surat Keterangan"
|
||||||
placeholder="Masukkan nama surat keterangan"
|
placeholder="Masukkan nama surat keterangan"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Input deskripsi */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold" mb={6}>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
Konten
|
Konten
|
||||||
</Text>
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(htmlContent) => setFormData({ ...formData, deskripsi: htmlContent })}
|
onChange={(htmlContent) =>
|
||||||
|
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
@@ -26,22 +26,24 @@ function EditPelayananTelunjukSakti() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateTelunjukDesa.edit.form.name,
|
name: '',
|
||||||
deskripsi: stateTelunjukDesa.edit.form.deskripsi,
|
deskripsi: '',
|
||||||
link: stateTelunjukDesa.edit.form.link,
|
link: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load data awal hanya sekali (pas ada id)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPelayananTelunjukSakti = async () => {
|
const loadData = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await stateTelunjukDesa.edit.load(id);
|
const data = await stateTelunjukDesa.edit.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || '',
|
name: data.name ?? '',
|
||||||
deskripsi: data.deskripsi || '',
|
deskripsi: data.deskripsi ?? '',
|
||||||
link: data.link || '',
|
link: data.link ?? '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -49,9 +51,19 @@ function EditPelayananTelunjukSakti() {
|
|||||||
toast.error('Gagal memuat data pelayanan telunjuk sakti');
|
toast.error('Gagal memuat data pelayanan telunjuk sakti');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadPelayananTelunjukSakti();
|
|
||||||
|
loadData();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
// Handler input controlled
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(field: keyof typeof formData, value: string) => {
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Submit: update global state hanya saat simpan
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateTelunjukDesa.edit.form = {
|
stateTelunjukDesa.edit.form = {
|
||||||
@@ -94,8 +106,8 @@ function EditPelayananTelunjukSakti() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Nama Pelayanan"
|
label="Nama Pelayanan"
|
||||||
placeholder="Masukkan nama pelayanan"
|
placeholder="Masukkan nama pelayanan"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => handleChange('name', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -106,7 +118,7 @@ function EditPelayananTelunjukSakti() {
|
|||||||
</Text>
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(htmlContent) => setFormData({ ...formData, deskripsi: htmlContent })}
|
onChange={(htmlContent) => handleChange('deskripsi', htmlContent)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -114,8 +126,8 @@ function EditPelayananTelunjukSakti() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Link"
|
label="Link"
|
||||||
placeholder="Masukkan link terkait"
|
placeholder="Masukkan link terkait"
|
||||||
defaultValue={formData.link}
|
value={formData.link}
|
||||||
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
onChange={(e) => handleChange('link', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Tombol Simpan */}
|
{/* Tombol Simpan */}
|
||||||
|
|||||||
@@ -24,18 +24,22 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditPenghargaan() {
|
function EditPenghargaan() {
|
||||||
const statePenghargaan = useProxy(penghargaanState)
|
const statePenghargaan = useProxy(penghargaanState);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null)
|
|
||||||
const [file, setFile] = useState<File | null>(null)
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: statePenghargaan.findUnique.data?.name || '',
|
|
||||||
juara: statePenghargaan.findUnique.data?.juara || '',
|
|
||||||
deskripsi: statePenghargaan.findUnique.data?.deskripsi || '',
|
|
||||||
imageId: statePenghargaan.findUnique.data?.imageId || '',
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
// Lokal formData
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
juara: '',
|
||||||
|
deskripsi: '',
|
||||||
|
imageId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load data pertama kali
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPenghargaan = async () => {
|
const loadPenghargaan = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -56,43 +60,43 @@ function EditPenghargaan() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading penghargaan:", error);
|
console.error('Error loading penghargaan:', error);
|
||||||
toast.error("Gagal memuat data penghargaan");
|
toast.error('Gagal memuat data penghargaan');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadPenghargaan();
|
loadPenghargaan();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
// Submit
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Sync ke global state saat submit
|
||||||
statePenghargaan.edit.form = {
|
statePenghargaan.edit.form = {
|
||||||
...statePenghargaan.edit.form,
|
...statePenghargaan.edit.form,
|
||||||
name: formData.name,
|
...formData,
|
||||||
juara: formData.juara,
|
};
|
||||||
deskripsi: formData.deskripsi,
|
|
||||||
imageId: formData.imageId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Upload file baru (kalau ada)
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
return toast.error('Gagal upload gambar');
|
||||||
}
|
}
|
||||||
|
|
||||||
statePenghargaan.edit.form.imageId = uploaded.id;
|
statePenghargaan.edit.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await statePenghargaan.edit.update();
|
await statePenghargaan.edit.update();
|
||||||
toast.success("Penghargaan berhasil diperbarui!");
|
toast.success('Penghargaan berhasil diperbarui!');
|
||||||
router.push("/admin/desa/penghargaan");
|
router.push('/admin/desa/penghargaan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating penghargaan:", error);
|
console.error('Error updating penghargaan:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui penghargaan");
|
toast.error('Terjadi kesalahan saat memperbarui penghargaan');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
@@ -122,8 +126,8 @@ function EditPenghargaan() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Judul"
|
label="Judul"
|
||||||
placeholder="Masukkan judul penghargaan"
|
placeholder="Masukkan judul penghargaan"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -131,8 +135,8 @@ function EditPenghargaan() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label="Juara"
|
label="Juara"
|
||||||
placeholder="Masukkan juara"
|
placeholder="Masukkan juara"
|
||||||
defaultValue={formData.juara}
|
value={formData.juara}
|
||||||
onChange={(e) => setFormData({ ...formData, juara: e.target.value })}
|
onChange={(e) => setFormData((prev) => ({ ...prev, juara: e.target.value }))}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -182,7 +186,11 @@ function EditPenghargaan() {
|
|||||||
src={previewImage}
|
src={previewImage}
|
||||||
alt="Preview Gambar"
|
alt="Preview Gambar"
|
||||||
radius="md"
|
radius="md"
|
||||||
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
style={{
|
||||||
|
maxHeight: 220,
|
||||||
|
objectFit: 'contain',
|
||||||
|
border: `1px solid ${colors['blue-button']}`,
|
||||||
|
}}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -196,10 +204,9 @@ function EditPenghargaan() {
|
|||||||
</Text>
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) =>
|
||||||
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
|
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
|
||||||
statePenghargaan.edit.form.deskripsi = htmlContent;
|
}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create')}
|
||||||
>
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -97,17 +97,21 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz="sm"
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
lineClamp={2}
|
lineClamp={1}
|
||||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -107,12 +107,16 @@ function ListKeamananLingkungan({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
<Text fz="sm" c="dimmed" truncate lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text fz="sm" c="dimmed" truncate lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -99,12 +99,16 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd style={{ width: '25%' }}>
|
||||||
|
<Box w={200}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.judul}
|
{item.judul}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '45%' }}>
|
<TableTd style={{ width: '45%' }}>
|
||||||
<Text fz="sm" c="dimmed" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={200}>
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '15%' }}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ function EditMediaSosial() {
|
|||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateMediaSosial.update.form.name || '',
|
name: '',
|
||||||
iconUrl: stateMediaSosial.update.form.iconUrl || '',
|
iconUrl: '',
|
||||||
imageId: stateMediaSosial.update.form.imageId || '',
|
imageId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load data by ID
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
const loadMediaSosial = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await stateMediaSosial.update.load(id);
|
const data = await stateMediaSosial.update.load(id);
|
||||||
|
|
||||||
@@ -59,11 +60,16 @@ function EditMediaSosial() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadMediaSosial();
|
loadData();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleChange = (field: string, value: string) => {
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
// update global state hanya saat submit
|
||||||
stateMediaSosial.update.form = { ...stateMediaSosial.update.form, ...formData };
|
stateMediaSosial.update.form = { ...stateMediaSosial.update.form, ...formData };
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -85,7 +91,10 @@ function EditMediaSosial() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box
|
||||||
|
px={{ base: 'sm', md: 'lg' }}
|
||||||
|
py="md"
|
||||||
|
>
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
@@ -106,6 +115,7 @@ function EditMediaSosial() {
|
|||||||
style={{ border: '1px solid #e0e0e0' }}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
{/* Upload Gambar */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw="bold" fz="sm" mb={6}>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
Gambar Media Sosial
|
Gambar Media Sosial
|
||||||
@@ -151,26 +161,32 @@ function EditMediaSosial() {
|
|||||||
src={previewImage}
|
src={previewImage}
|
||||||
alt="Preview Gambar"
|
alt="Preview Gambar"
|
||||||
radius="md"
|
radius="md"
|
||||||
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
style={{
|
||||||
|
maxHeight: 220,
|
||||||
|
objectFit: 'contain',
|
||||||
|
border: `1px solid ${colors['blue-button']}`,
|
||||||
|
}}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Nama Media Sosial */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Nama Media Sosial / Kontak"
|
label="Nama Media Sosial / Kontak"
|
||||||
placeholder="Masukkan nama media sosial atau kontak"
|
placeholder="Masukkan nama media sosial atau kontak"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => handleChange('name', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Link Media Sosial */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Link Media Sosial / Nomor Telepon"
|
label="Link Media Sosial / Nomor Telepon"
|
||||||
placeholder="Masukkan link media sosial atau nomor telepon"
|
placeholder="Masukkan link media sosial atau nomor telepon"
|
||||||
defaultValue={formData.iconUrl}
|
value={formData.iconUrl}
|
||||||
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
|
onChange={(e) => handleChange('iconUrl', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,7 @@ function DetailMediaSosial() {
|
|||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w="100%"
|
w={{ base: "100%", md: "50%" }}
|
||||||
maw={500} // <= tambahkan ini, biar tidak lebih dari 500px
|
|
||||||
mx="auto" // center di layar
|
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Alert, Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
import {
|
||||||
|
Alert, Box, Button, Center, Group, Image,
|
||||||
|
Paper, Stack, Text, TextInput, Title, Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -17,7 +20,14 @@ function EditPejabatDesa() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// UI States
|
// Local form state
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
position: '',
|
||||||
|
imageId: null as string | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// UI states
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@@ -34,12 +44,18 @@ function EditPejabatDesa() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const profileData = await profileLandingPageState.pejabatDesa.findUnique.load(id);
|
const profileData = await profileLandingPageState.pejabatDesa.findUnique.load(id);
|
||||||
profileLandingPageState.pejabatDesa.edit.initialize(profileData);
|
|
||||||
|
|
||||||
|
if (profileData) {
|
||||||
|
setFormData({
|
||||||
|
name: profileData.name || '',
|
||||||
|
position: profileData.position || '',
|
||||||
|
imageId: profileData.imageId || null,
|
||||||
|
});
|
||||||
|
|
||||||
if (profileData && profileData.image?.link) {
|
if (profileData.image?.link) {
|
||||||
setPreviewImage(profileData.image.link);
|
setPreviewImage(profileData.image.link);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading profile:", error);
|
console.error("Error loading profile:", error);
|
||||||
toast.error("Gagal memuat data profile");
|
toast.error("Gagal memuat data profile");
|
||||||
@@ -47,16 +63,15 @@ function EditPejabatDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
|
return () => profileLandingPageState.pejabatDesa.edit.reset();
|
||||||
return () => {
|
|
||||||
profileLandingPageState.pejabatDesa.edit.reset(); // cleanup form
|
|
||||||
};
|
|
||||||
}, [params?.id, router]);
|
}, [params?.id, router]);
|
||||||
|
|
||||||
const handleFieldChange = (field: string, value: string) => {
|
// Handle input change
|
||||||
profileLandingPageState.pejabatDesa.edit.updateField(field as any, value);
|
const handleChange = (field: string, value: string) => {
|
||||||
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle file change
|
||||||
const handleFileChange = (newFile: File | null) => {
|
const handleFileChange = (newFile: File | null) => {
|
||||||
if (!newFile) {
|
if (!newFile) {
|
||||||
setFile(null);
|
setFile(null);
|
||||||
@@ -72,15 +87,17 @@ function EditPejabatDesa() {
|
|||||||
reader.readAsDataURL(newFile);
|
reader.readAsDataURL(newFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Submit form
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (isSubmitting || !profileLandingPageState.pejabatDesa.edit.form.name.trim()) {
|
if (isSubmitting || !formData.name.trim()) {
|
||||||
toast.error("Nama wajib diisi");
|
toast.error("Nama wajib diisi");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let imageId = formData.imageId;
|
||||||
|
|
||||||
// Upload file jika ada
|
// Upload file jika ada
|
||||||
if (file) {
|
if (file) {
|
||||||
const uploadResponse = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const uploadResponse = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
@@ -90,13 +107,16 @@ function EditPejabatDesa() {
|
|||||||
toast.error("Gagal upload gambar");
|
toast.error("Gagal upload gambar");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
imageId = uploaded.id;
|
||||||
profileLandingPageState.pejabatDesa.edit.form.imageId = uploaded.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit form
|
// Update global state only on submit
|
||||||
const success = await profileLandingPageState.pejabatDesa.edit.submit();
|
profileLandingPageState.pejabatDesa.edit.form = {
|
||||||
|
...formData,
|
||||||
|
imageId: imageId || '', // Ensure imageId is always a string
|
||||||
|
};
|
||||||
|
|
||||||
|
const success = await profileLandingPageState.pejabatDesa.edit.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success("Berhasil menyimpan perubahan");
|
toast.success("Berhasil menyimpan perubahan");
|
||||||
router.push("/admin/landing-page/profile/pejabat-desa");
|
router.push("/admin/landing-page/profile/pejabat-desa");
|
||||||
@@ -109,11 +129,9 @@ function EditPejabatDesa() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => router.back();
|
||||||
router.back();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Loading state
|
// Loading
|
||||||
if (allState.edit.loading) {
|
if (allState.edit.loading) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -124,7 +142,7 @@ function EditPejabatDesa() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error state
|
// Error
|
||||||
if (allState.edit.error) {
|
if (allState.edit.error) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -146,7 +164,7 @@ function EditPejabatDesa() {
|
|||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -170,28 +188,27 @@ function EditPejabatDesa() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw="bold">Nama Perbekel</Text>}
|
label={<Text fw="bold">Nama Perbekel</Text>}
|
||||||
placeholder="Masukkan nama perbekel"
|
placeholder="Masukkan nama perbekel"
|
||||||
defaultValue={allState.edit.form.name}
|
value={formData.name}
|
||||||
onChange={(e) => handleFieldChange('name', e.currentTarget.value)}
|
onChange={(e) => handleChange('name', e.currentTarget.value)}
|
||||||
error={!allState.edit.form.name && "Nama wajib diisi"}
|
error={!formData.name && "Nama wajib diisi"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Posisi Field */}
|
{/* Posisi Field */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw="bold">Posisi</Text>}
|
label={<Text fw="bold">Posisi</Text>}
|
||||||
placeholder="Masukkan posisi"
|
placeholder="Masukkan posisi"
|
||||||
defaultValue={allState.edit.form.position}
|
value={formData.position}
|
||||||
onChange={(e) => handleFieldChange('position', e.currentTarget.value)}
|
onChange={(e) => handleChange('position', e.currentTarget.value)}
|
||||||
error={!allState.edit.form.position && "Posisi wajib diisi"}
|
error={!formData.position && "Posisi wajib diisi"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* File Upload */}
|
{/* File Upload */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
<Box>
|
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onDrop={(files) => handleFileChange(files[0])}
|
onDrop={(files) => handleFileChange(files[0])}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
maxSize={5 * 1024 ** 2}
|
||||||
accept={{ 'image/*': [] }}
|
accept={{ 'image/*': [] }}
|
||||||
>
|
>
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
@@ -216,7 +233,6 @@ function EditPejabatDesa() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
{previewImage && (
|
||||||
<Box mt="sm">
|
<Box mt="sm">
|
||||||
<Image
|
<Image
|
||||||
@@ -233,11 +249,9 @@ function EditPejabatDesa() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Preview Gambar */}
|
{/* Preview */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
||||||
{previewImage ? (
|
{previewImage ? (
|
||||||
@@ -252,13 +266,13 @@ function EditPejabatDesa() {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Submit */}
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
bg={colors['blue-button']}
|
bg={colors['blue-button']}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
loading={isSubmitting || allState.edit.loading}
|
loading={isSubmitting || allState.edit.loading}
|
||||||
disabled={!allState.edit.form.name}
|
disabled={!formData.name}
|
||||||
>
|
>
|
||||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ function EditProgramInovasi() {
|
|||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateProgramInovasi.update.form.name || "",
|
name: "",
|
||||||
description: stateProgramInovasi.update.form.description || "",
|
description: "",
|
||||||
imageId: stateProgramInovasi.update.form.imageId || "",
|
imageId: "",
|
||||||
link: stateProgramInovasi.update.form.link || "",
|
link: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -51,9 +51,12 @@ function EditProgramInovasi() {
|
|||||||
imageId: data.imageId || "",
|
imageId: data.imageId || "",
|
||||||
link: data.link || ""
|
link: data.link || ""
|
||||||
});
|
});
|
||||||
// Tampilkan preview gambar
|
|
||||||
|
// Preview image
|
||||||
if (data.image?.link) {
|
if (data.image?.link) {
|
||||||
setPreviewImage(data.image.link);
|
setPreviewImage(data.image.link);
|
||||||
|
} else {
|
||||||
|
setPreviewImage(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -69,24 +72,25 @@ function EditProgramInovasi() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Upload file kalau ada file baru
|
||||||
|
let imageId = formData.imageId;
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update global state form (baru di sini)
|
||||||
stateProgramInovasi.update.form = {
|
stateProgramInovasi.update.form = {
|
||||||
...stateProgramInovasi.update.form,
|
...stateProgramInovasi.update.form,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
imageId: formData.imageId,
|
imageId,
|
||||||
link: formData.link,
|
link: formData.link,
|
||||||
}
|
}
|
||||||
if (file) {
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
|
||||||
const uploaded = res.data?.data;
|
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
stateProgramInovasi.update.form.imageId = uploaded.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
await stateProgramInovasi.update.update();
|
await stateProgramInovasi.update.update();
|
||||||
toast.success("Program Inovasi berhasil diperbarui!");
|
toast.success("Program Inovasi berhasil diperbarui!");
|
||||||
@@ -170,29 +174,31 @@ function EditProgramInovasi() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
key={String(params.id)} // Convert to string to ensure valid key
|
||||||
label="Nama Program Inovasi"
|
label="Nama Program Inovasi"
|
||||||
placeholder="Masukkan nama program inovasi"
|
placeholder="Masukkan nama program inovasi"
|
||||||
defaultValue={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) =>
|
||||||
setFormData((prev) => ({ ...prev, description: htmlContent }));
|
setFormData((prev) => ({ ...prev, description: htmlContent }))
|
||||||
stateProgramInovasi.update.form.description = htmlContent;
|
}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
key={`${params.id}-link`}
|
||||||
label="Link Program Inovasi"
|
label="Link Program Inovasi"
|
||||||
placeholder="Masukkan link program inovasi (opsional)"
|
placeholder="Masukkan link program inovasi (opsional)"
|
||||||
defaultValue={formData.link}
|
value={formData.link}
|
||||||
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
|||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Data Lingkungan Desa</Title>
|
<Title order={4}>Daftar Data Lingkungan Desa</Title>
|
||||||
<Tooltip label="Tambah Data Lingkungan Desa" withArrow>
|
<Tooltip label="Tambah Data Lingkungan Desa" withArrow>
|
||||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan/create')}>
|
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
export default async function findUniqueAjukanPermohonan(request: Request) {
|
export default async function findUniqueAjukanPermohonan({ params }: { params: { id: string } }) {
|
||||||
const url = new URL(request.url);
|
const { id } = params;
|
||||||
const pathSegments = url.pathname.split("/");
|
|
||||||
const id = pathSegments[pathSegments.length - 1];
|
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
|
||||||
|
|
||||||
export default async function findByMonthYear(context: Context) {
|
|
||||||
const { month, year } = context.params as { month: string; year: string };
|
export default async function findByMonthYear({ params }: { params: { month: string; year: number } }) {
|
||||||
|
const { month, year } = params;
|
||||||
|
|
||||||
if (!month || !year) {
|
if (!month || !year) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ async function img({
|
|||||||
// Validasi ekstensi file
|
// Validasi ekstensi file
|
||||||
if (![".jpg", ".jpeg", ".png"].includes(ext)) {
|
if (![".jpg", ".jpeg", ".png"].includes(ext)) {
|
||||||
console.warn(`Ekstensi file tidak didukung: ${ext}`);
|
console.warn(`Ekstensi file tidak didukung: ${ext}`);
|
||||||
return new Response(await fs.readFile(noImage), {
|
const buffer = await fs.readFile(noImage);
|
||||||
|
const uint8Array = new Uint8Array(buffer);
|
||||||
|
return new Response(new Blob([uint8Array], { type: 'image/jpeg' }), {
|
||||||
headers: { "Content-Type": "image/jpeg" },
|
headers: { "Content-Type": "image/jpeg" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -43,7 +45,8 @@ async function img({
|
|||||||
.resize(size || metadata.width) // Gunakan size jika diberikan, jika tidak gunakan width asli
|
.resize(size || metadata.width) // Gunakan size jika diberikan, jika tidak gunakan width asli
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
return new Response(resizedImageBuffer, {
|
const uint8Array = new Uint8Array(resizedImageBuffer);
|
||||||
|
return new Response(new Blob([uint8Array], { type: 'image/jpeg' }), {
|
||||||
headers: {
|
headers: {
|
||||||
"Cache-Control": "public, max-age=3600, stale-while-revalidate=600",
|
"Cache-Control": "public, max-age=3600, stale-while-revalidate=600",
|
||||||
"Content-Type": "image/jpeg",
|
"Content-Type": "image/jpeg",
|
||||||
@@ -52,7 +55,9 @@ async function img({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Gagal memproses file: ${name}`, error);
|
console.error(`Gagal memproses file: ${name}`, error);
|
||||||
// Jika file tidak ditemukan atau gagal diproses, kembalikan default image
|
// Jika file tidak ditemukan atau gagal diproses, kembalikan default image
|
||||||
return new Response(await fs.readFile(noImage), {
|
const buffer = await fs.readFile(noImage);
|
||||||
|
const uint8Array = new Uint8Array(buffer);
|
||||||
|
return new Response(new Blob([uint8Array], { type: 'image/jpeg' }), {
|
||||||
headers: { "Content-Type": "image/jpeg" },
|
headers: { "Content-Type": "image/jpeg" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
export default async function pengaduanMasyarakatFindUnique(request: Request) {
|
export default async function pengaduanMasyarakatFindUnique({ params }: { params: { id: string } }) {
|
||||||
const url = new URL(request.url);
|
const { id } = params;
|
||||||
const pathSegments = url.pathname.split("/");
|
|
||||||
const id = pathSegments[pathSegments.length - 1];
|
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return Response.json({
|
return Response.json({
|
||||||
|
|||||||
@@ -1,11 +1,174 @@
|
|||||||
|
// 'use client'
|
||||||
|
// import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
|
// import colors from '@/con/colors';
|
||||||
|
// import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
|
// import { useProxy } from 'valtio/utils';
|
||||||
|
// import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
// import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
|
|
||||||
|
// function Page() {
|
||||||
|
// const state = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||||
|
|
||||||
|
// useShallowEffect(() => {
|
||||||
|
// state.findMany.load();
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// useShallowEffect(() => {
|
||||||
|
// PendapatanAsliDesa.pembiayaan.findMany.load();
|
||||||
|
// PendapatanAsliDesa.belanja.findMany.load();
|
||||||
|
// PendapatanAsliDesa.pendapatan.findMany.load();
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// // Get the latest APB data
|
||||||
|
// const latestApb = state.findMany.data?.[0];
|
||||||
|
|
||||||
|
// // Calculate totals
|
||||||
|
// const totalPendapatan = latestApb?.pendapatan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||||
|
// const totalBelanja = latestApb?.belanja?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||||
|
// const totalPembiayaan = latestApb?.pembiayaan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||||
|
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||||
|
// <Box px={{ base: 'md', md: 100 }}>
|
||||||
|
// <BackButton />
|
||||||
|
// </Box>
|
||||||
|
// <Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||||
|
// Pendapatan Asli Desa
|
||||||
|
// </Text>
|
||||||
|
// <Box px={{ base: "md", md: 100 }}>
|
||||||
|
// <Stack gap="lg" justify="center">
|
||||||
|
// <Paper bg={colors['white-1']} p="xl">
|
||||||
|
// <SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||||
|
// {/* Pendapatan Card */}
|
||||||
|
// <Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||||
|
// <Stack gap={"xs"}>
|
||||||
|
// <Title order={3}>Pendapatan</Title>
|
||||||
|
// {PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => (
|
||||||
|
// <Box key={item.id}>
|
||||||
|
// <Grid>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="md" fw={500}>{item.name}</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||||
|
// style: 'currency',
|
||||||
|
// currency: 'IDR',
|
||||||
|
// minimumFractionDigits: 0
|
||||||
|
// }).format(item.value)}</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// </Grid>
|
||||||
|
// </Box>
|
||||||
|
// ))}
|
||||||
|
// <Grid>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="xl" fw={700} c={colors['blue-button']}>
|
||||||
|
// {new Intl.NumberFormat('id-ID', {
|
||||||
|
// style: 'currency',
|
||||||
|
// currency: 'IDR',
|
||||||
|
// minimumFractionDigits: 0
|
||||||
|
// }).format(totalPendapatan)}
|
||||||
|
// </Text>
|
||||||
|
// </GridCol>
|
||||||
|
// </Grid>
|
||||||
|
// </Stack>
|
||||||
|
// </Box>
|
||||||
|
|
||||||
|
// {/* Belanja Card */}
|
||||||
|
// <Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||||
|
// <Stack gap={"xs"}>
|
||||||
|
// <Title order={3}>Belanja</Title>
|
||||||
|
// {PendapatanAsliDesa.belanja.findMany.data?.map((item) => (
|
||||||
|
// <Box key={item.id}>
|
||||||
|
// <Grid>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="md" fw={500}>{item.name}</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||||
|
// style: 'currency',
|
||||||
|
// currency: 'IDR',
|
||||||
|
// minimumFractionDigits: 0
|
||||||
|
// }).format(item.value)}</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// </Grid>
|
||||||
|
// </Box>
|
||||||
|
// ))}
|
||||||
|
// <Grid>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="xl" fw={700} c="orange">
|
||||||
|
// {new Intl.NumberFormat('id-ID', {
|
||||||
|
// style: 'currency',
|
||||||
|
// currency: 'IDR',
|
||||||
|
// minimumFractionDigits: 0
|
||||||
|
// }).format(totalBelanja)}
|
||||||
|
// </Text>
|
||||||
|
// </GridCol>
|
||||||
|
// </Grid>
|
||||||
|
// </Stack>
|
||||||
|
// </Box>
|
||||||
|
|
||||||
|
// {/* Pembiayaan Card */}
|
||||||
|
// <Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||||
|
// <Stack gap={"xs"}>
|
||||||
|
// <Title order={3}>Pembiayaan</Title>
|
||||||
|
// {PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => (
|
||||||
|
// <Box key={item.id}>
|
||||||
|
// <Grid>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="md" fw={500}>{item.name}</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||||
|
// style: 'currency',
|
||||||
|
// currency: 'IDR',
|
||||||
|
// minimumFractionDigits: 0
|
||||||
|
// }).format(item.value)}</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// </Grid>
|
||||||
|
// </Box>
|
||||||
|
// ))}
|
||||||
|
// <Grid>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
|
||||||
|
// </GridCol>
|
||||||
|
// <GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
// <Text fz="xl" fw={700} c="green">
|
||||||
|
// {new Intl.NumberFormat('id-ID', {
|
||||||
|
// style: 'currency',
|
||||||
|
// currency: 'IDR',
|
||||||
|
// minimumFractionDigits: 0
|
||||||
|
// }).format(totalPembiayaan)}
|
||||||
|
// </Text>
|
||||||
|
// </GridCol>
|
||||||
|
// </Grid>
|
||||||
|
// </Stack>
|
||||||
|
// </Box>
|
||||||
|
|
||||||
|
// </SimpleGrid>
|
||||||
|
// </Paper>
|
||||||
|
// </Stack>
|
||||||
|
// </Box>
|
||||||
|
// </Stack>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default Page;
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
import { Box, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(PendapatanAsliDesa.ApbDesa);
|
const state = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||||
@@ -28,6 +191,9 @@ function Page() {
|
|||||||
const totalBelanja = latestApb?.belanja?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
const totalBelanja = latestApb?.belanja?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||||
const totalPembiayaan = latestApb?.pembiayaan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
const totalPembiayaan = latestApb?.pembiayaan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||||
|
|
||||||
|
// Hasil akhir
|
||||||
|
const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
@@ -40,119 +206,51 @@ function Page() {
|
|||||||
<Stack gap="lg" justify="center">
|
<Stack gap="lg" justify="center">
|
||||||
<Paper bg={colors['white-1']} p="xl">
|
<Paper bg={colors['white-1']} p="xl">
|
||||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||||
{/* Pendapatan Card */}
|
{/* Pendapatan, Belanja, Pembiayaan Card sama seperti sebelumnya */}
|
||||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
{/* ... */}
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Pendapatan</Title>
|
|
||||||
{PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => (
|
|
||||||
<Box key={item.id}>
|
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="md" fw={500}>{item.name}</Text>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(item.value)}</Text>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="xl" fw={700} c={colors['blue-button']}>
|
|
||||||
{new Intl.NumberFormat('id-ID', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(totalPendapatan)}
|
|
||||||
</Text>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Belanja Card */}
|
|
||||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Belanja</Title>
|
|
||||||
{PendapatanAsliDesa.belanja.findMany.data?.map((item) => (
|
|
||||||
<Box key={item.id}>
|
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="md" fw={500}>{item.name}</Text>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(item.value)}</Text>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="xl" fw={700} c="orange">
|
|
||||||
{new Intl.NumberFormat('id-ID', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(totalBelanja)}
|
|
||||||
</Text>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Pembiayaan Card */}
|
|
||||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Pembiayaan</Title>
|
|
||||||
{PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => (
|
|
||||||
<Box key={item.id}>
|
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="md" fw={500}>{item.name}</Text>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(item.value)}</Text>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
|
||||||
<Text fz="xl" fw={700} c="green">
|
|
||||||
{new Intl.NumberFormat('id-ID', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(totalPembiayaan)}
|
|
||||||
</Text>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* 🔽 Tambahan Ringkasan Anggaran */}
|
||||||
|
<Paper bg={colors['white-1']} p="xl" shadow="sm" withBorder>
|
||||||
|
<Title order={3} mb="md">Ringkasan Anggaran</Title>
|
||||||
|
<Table striped highlightOnHover withTableBorder>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Keterangan</Table.Th>
|
||||||
|
<Table.Th align="right">Jumlah</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>Total Pendapatan</Table.Td>
|
||||||
|
<Table.Td align="right">
|
||||||
|
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPendapatan)}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>Total Belanja</Table.Td>
|
||||||
|
<Table.Td align="right" c="orange">
|
||||||
|
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalBelanja)}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>Total Pembiayaan</Table.Td>
|
||||||
|
<Table.Td align="right" c="green">
|
||||||
|
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPembiayaan)}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td><b>Sisa Anggaran</b></Table.Td>
|
||||||
|
<Table.Td align="right" c={sisaAnggaran >= 0 ? "blue" : "red"}>
|
||||||
|
<b>
|
||||||
|
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(sisaAnggaran)}
|
||||||
|
</b>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -160,3 +258,4 @@ function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan';
|
import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -11,6 +11,7 @@ import BackButton from '../../desa/layanan/_com/BackButto';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(keamananLingkunganState)
|
const state = useProxy(keamananLingkunganState)
|
||||||
|
const [expandedMap, setExpandedMap] = useState<Record<number, boolean>>({});
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||||
const {
|
const {
|
||||||
@@ -25,6 +26,13 @@ function Page() {
|
|||||||
load(page, 3, debouncedSearch)
|
load(page, 3, debouncedSearch)
|
||||||
}, [page, debouncedSearch])
|
}, [page, debouncedSearch])
|
||||||
|
|
||||||
|
const toggleExpanded = (index: number, value: boolean) => {
|
||||||
|
setExpandedMap((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[index]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
@@ -80,7 +88,22 @@ function Page() {
|
|||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Spoiler
|
||||||
|
showLabel={
|
||||||
|
<Text fw="bold" fz="sm" c={colors['blue-button']}>
|
||||||
|
Show more
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
hideLabel={
|
||||||
|
<Text fw="bold" fz="sm" c={colors['blue-button']}>
|
||||||
|
Hide details
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
expanded={expandedMap[k] || false}
|
||||||
|
onExpandedChange={(val) => toggleExpanded(k, val)}
|
||||||
|
>
|
||||||
<Text pb={10} fz={"h4"} ta={'justify'} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
<Text pb={10} fz={"h4"} ta={'justify'} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||||
|
</Spoiler>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
|
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Flex, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
import { ActionIcon, Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowRight } from '@tabler/icons-react';
|
import { IconArrowRight } from '@tabler/icons-react';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
@@ -63,16 +63,15 @@ function Page() {
|
|||||||
<Stack pt={30} gap="lg">
|
<Stack pt={30} gap="lg">
|
||||||
{data.length > 0 ? (
|
{data.length > 0 ? (
|
||||||
data.map((item) => (
|
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}`)}>
|
<ActionIcon key={item.id} variant='transparent' onClick={() => router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`)}>
|
||||||
|
<Paper p="md" bg={colors['blue-button']} radius="md" shadow="sm">
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Flex align="center" justify="space-between">
|
|
||||||
<Text fz="h3" c={colors['white-1']}>
|
<Text fz="h3" c={colors['white-1']}>
|
||||||
{item.judul}
|
{item.judul}
|
||||||
</Text>
|
</Text>
|
||||||
<IconArrowRight size={28} color={colors['white-1']} />
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
</ActionIcon>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Text color="dimmed">
|
<Text color="dimmed">
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ function Page() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Image alt="Beasiswa Desa" src="/api/img/beasiswa-siswa.png" radius="lg" loading="lazy"/>
|
<Image alt="Beasiswa Desa" src="/beasiswa-siswa.png" radius="lg" loading="lazy"/>
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user