Fix Menu Desa Admin & User

This commit is contained in:
2025-09-30 17:13:06 +08:00
parent 295d6f7d63
commit c2f1ab8179
27 changed files with 897 additions and 593 deletions

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import colors from '@/con/colors';
import {
@@ -10,7 +11,7 @@ import {
Stack,
TextInput,
Title,
Tooltip
Tooltip,
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -24,7 +25,7 @@ function EditKategoriBerita() {
const params = useParams();
const [formData, setFormData] = useState({
name: editState.update.form.name || '',
name: '',
});
useEffect(() => {
@@ -48,8 +49,16 @@ function EditKategoriBerita() {
loadKategori();
}, [params?.id]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = async () => {
try {
// update global state hanya saat submit
editState.update.form = {
...editState.update.form,
name: formData.name,
@@ -94,10 +103,11 @@ function EditKategoriBerita() {
>
<Stack gap="md">
<TextInput
name="name"
label="Nama Kategori Berita"
placeholder="Masukkan nama kategori berita"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={handleChange}
required
/>

View File

@@ -19,7 +19,12 @@ import {
Tooltip,
} from "@mantine/core";
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 { useEffect, useState } from "react";
import { toast } from "react-toastify";
@@ -33,16 +38,17 @@ function EditBerita() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [formData, setFormData] = useState({
judul: beritaState.berita.edit.form.judul || "",
deskripsi: beritaState.berita.edit.form.deskripsi || "",
kategoriBeritaId: beritaState.berita.edit.form.kategoriBeritaId || "",
content: beritaState.berita.edit.form.content || "",
imageId: beritaState.berita.edit.form.imageId || "",
judul: "",
deskripsi: "",
kategoriBeritaId: "",
content: "",
imageId: "",
});
// Load berita by id saat pertama kali
// Load kategori + berita
useEffect(() => {
beritaState.kategoriBerita.findMany.load();
const loadBerita = async () => {
const id = params?.id as string;
if (!id) return;
@@ -71,8 +77,13 @@ function EditBerita() {
loadBerita();
}, [params?.id]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
try {
// Update global state hanya sekali di sini
beritaState.berita.edit.form = {
...beritaState.berita.edit.form,
...formData,
@@ -103,6 +114,7 @@ function EditBerita() {
return (
<Box px={{ base: "sm", md: "lg" }} py="md">
{/* Header */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button
@@ -119,6 +131,7 @@ function EditBerita() {
</Title>
</Group>
{/* Form */}
<Paper
w={{ base: "100%", md: "50%" }}
bg={colors["white-1"]}
@@ -131,18 +144,14 @@ function EditBerita() {
<TextInput
label="Judul"
placeholder="Masukkan judul"
defaultValue={formData.judul}
onChange={(e) =>
setFormData({ ...formData, judul: e.target.value })
}
value={formData.judul}
onChange={(e) => handleChange("judul", e.target.value)}
required
/>
<Select
value={formData.kategoriBeritaId}
onChange={(val) =>
setFormData({ ...formData, kategoriBeritaId: val || "" })
}
onChange={(val) => handleChange("kategoriBeritaId", val || "")}
label="Kategori"
placeholder="Pilih kategori"
data={
@@ -160,13 +169,12 @@ function EditBerita() {
<TextInput
label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat"
defaultValue={formData.deskripsi}
onChange={(e) =>
setFormData({ ...formData, deskripsi: e.target.value })
}
value={formData.deskripsi}
onChange={(e) => handleChange("deskripsi", e.target.value)}
required
/>
{/* Upload Gambar */}
<Box>
<Text fw="bold" fz="sm" mb={6}>
Gambar Berita
@@ -179,7 +187,9 @@ function EditBerita() {
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}
accept={{ "image/*": [] }}
radius="md"
@@ -187,7 +197,11 @@ function EditBerita() {
>
<Group justify="center" gap="xl" mih={180}>
<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.Reject>
<IconX size={48} color="red" stroke={1.5} />
@@ -223,18 +237,20 @@ function EditBerita() {
)}
</Box>
{/* Konten */}
<Box>
<Text fz="sm" fw="bold">
Konten
</Text>
<EditEditor
value={formData.content}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, content: htmlContent }));
beritaState.berita.edit.form.content = htmlContent;
}}
onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, content: htmlContent }))
}
/>
</Box>
{/* Action */}
<Group justify="right">
<Button
onClick={handleSubmit}

View File

@@ -15,7 +15,7 @@ import {
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
@@ -31,17 +31,19 @@ function EditVideo() {
linkVideo: '',
});
// load data video sekali saat id ada
useEffect(() => {
const loadVideo = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await videoState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
linkVideo: data.linkVideo || '',
name: data.name ?? '',
deskripsi: data.deskripsi ?? '',
linkVideo: data.linkVideo ?? '',
});
}
} catch (error) {
@@ -49,10 +51,16 @@ function EditVideo() {
toast.error('Gagal memuat data video');
}
};
loadVideo();
}, [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 converted = convertYoutubeUrlToEmbed(formData.linkVideo);
@@ -63,7 +71,6 @@ function EditVideo() {
try {
videoState.update.form = {
...videoState.update.form,
name: formData.name,
deskripsi: formData.deskripsi,
linkVideo: formData.linkVideo,
@@ -77,11 +84,18 @@ function EditVideo() {
}
};
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md">
<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} />
</Button>
</Tooltip>
@@ -102,8 +116,8 @@ function EditVideo() {
<TextInput
label="Judul Video"
placeholder="Masukkan judul video"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={(e) => handleChange('name', e.currentTarget.value)}
required
/>
@@ -111,8 +125,8 @@ function EditVideo() {
<TextInput
label="Link Video YouTube"
placeholder="https://www.youtube.com/watch?v=abc123"
defaultValue={formData.linkVideo}
onChange={(e) => setFormData({ ...formData, linkVideo: e.currentTarget.value })}
value={formData.linkVideo}
onChange={(e) => handleChange('linkVideo', e.currentTarget.value)}
required
/>
{embedLink && (
@@ -135,7 +149,7 @@ function EditVideo() {
</Title>
<EditEditor
value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
onChange={(val) => handleChange('deskripsi', val)}
/>
</Box>

View File

@@ -1,17 +1,17 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import {
Box,
Button,
Group,
Paper,
Select,
Stack,
TextInput,
Title,
Tooltip
Box,
Button,
Group,
Paper,
Select,
Stack,
TextInput,
Title,
Tooltip,
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -20,159 +20,159 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditAjukanPermohonan() {
const router = useRouter();
const params = useParams();
const stateAjukan = useProxy(stateLayananDesa.ajukanPermohonan);
const router = useRouter();
const params = useParams();
const stateAjukan = useProxy(stateLayananDesa.ajukanPermohonan);
const [formData, setFormData] = useState({
nama: stateAjukan.edit.form.nama,
nik: stateAjukan.edit.form.nik,
alamat: stateAjukan.edit.form.alamat,
nomorKk: stateAjukan.edit.form.nomorKk,
kategoriId: stateAjukan.edit.form.kategoriId,
});
// State lokal form
const [formData, setFormData] = useState({
nama: '',
nik: '',
alamat: '',
nomorKk: '',
kategoriId: '',
});
useEffect(() => {
stateLayananDesa.suratKeterangan.findManyAll.load();
const loadAjukan = async () => {
const id = params?.id as string;
if (!id) return;
// Load data awal
useEffect(() => {
stateLayananDesa.suratKeterangan.findManyAll.load();
try {
const data = await stateAjukan.edit.load(id);
if (data) {
setFormData({
nama: data.nama || '',
nik: data.nik || '',
alamat: data.alamat || '',
nomorKk: data.nomorKk || '',
kategoriId: data.kategoriId || '',
});
}
} catch (error) {
console.error('Error loading ajukan:', error);
toast.error('Gagal memuat data ajukan');
}
};
const loadAjukan = async () => {
const id = params?.id as string;
if (!id) return;
loadAjukan();
}, [params?.id]);
const handleSubmit = async () => {
try {
stateAjukan.edit.form = {
...stateAjukan.edit.form,
...formData,
};
toast.success('Ajukan berhasil diperbarui!');
router.push('/admin/desa/layanan/ajukan_permohonan');
} catch (error) {
console.error('Error updating ajukan:', error);
toast.error('Terjadi kesalahan saat memperbarui ajukan');
try {
const data = await stateAjukan.edit.load(id);
if (data) {
setFormData({
nama: data.nama || '',
nik: data.nik || '',
alamat: data.alamat || '',
nomorKk: data.nomorKk || '',
kategoriId: data.kategoriId || '',
});
}
} catch (error) {
console.error('Error loading ajukan:', error);
toast.error('Gagal memuat data ajukan');
}
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Back Button */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Edit Ajukan Permohonan
</Title>
</Group>
loadAjukan();
}, [params?.id]);
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
// Handler untuk input controlled
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => {
try {
stateAjukan.edit.form = {
...stateAjukan.edit.form,
...formData,
};
toast.success('Ajukan berhasil diperbarui!');
router.push('/admin/desa/layanan/ajukan_permohonan');
} catch (error) {
console.error('Error updating ajukan:', error);
toast.error('Terjadi kesalahan saat memperbarui ajukan');
}
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Back Button */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Edit Ajukan Permohonan
</Title>
</Group>
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md">
<TextInput
label="Nama"
placeholder="Masukkan nama"
value={formData.nama}
onChange={(e) => handleChange('nama', e.target.value)}
required
/>
<TextInput
type="number"
label="NIK"
placeholder="Masukkan NIK"
value={formData.nik}
onChange={(e) => handleChange('nik', e.target.value)}
required
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat"
value={formData.alamat}
onChange={(e) => handleChange('alamat', e.target.value)}
required
/>
<TextInput
type="number"
label="Nomor KK"
placeholder="Masukkan nomor KK"
value={formData.nomorKk}
onChange={(e) => handleChange('nomorKk', e.target.value)}
required
/>
<Select
label="Kategori"
placeholder="Pilih kategori"
data={stateLayananDesa.suratKeterangan.findManyAll.data?.map((item) => ({
label: item.name,
value: item.id,
}))}
value={formData.kategoriId || null}
onChange={(val) => handleChange('kategoriId', val || '')}
searchable
clearable
nothingFoundMessage="Tidak ditemukan"
required
/>
<Group justify="right">
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
<Stack gap="md">
<TextInput
label="Nama"
placeholder="Masukkan nama"
defaultValue={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
required
/>
<TextInput
type="number"
label="NIK"
placeholder="Masukkan NIK"
defaultValue={formData.nik}
onChange={(e) => setFormData({ ...formData, nik: e.target.value })}
required
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat"
defaultValue={formData.alamat}
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
required
/>
<TextInput
type="number"
label="Nomor KK"
placeholder="Masukkan nomor KK"
defaultValue={formData.nomorKk}
onChange={(e) => setFormData({ ...formData, nomorKk: e.target.value })}
required
/>
<Select
label="Kategori"
placeholder="Pilih kategori"
data={stateLayananDesa.suratKeterangan.findManyAll.data?.map((item) => ({
label: item.name,
value: item.id,
}))}
value={formData.kategoriId || null}
onChange={(val: string | null) => {
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
clearable
nothingFoundMessage="Tidak ditemukan"
required
/>
<Group justify="right">
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
</Button>
</Group>
</Stack>
</Paper>
</Box>
);
Simpan
</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditAjukanPermohonan;

View File

@@ -1,9 +1,20 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
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 { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -12,17 +23,22 @@ import { useProxy } from 'valtio/utils';
function EditPelayananPendudukNonPermanent() {
const router = useRouter();
const params = useParams()
const statePendudukNonPermanent = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
const [formData, setFormData] = useState({
name: statePendudukNonPermanent.findById.data?.name || '',
deskripsi: statePendudukNonPermanent.findById.data?.deskripsi || '',
})
const params = useParams();
const statePendudukNonPermanent = useProxy(
stateLayananDesa.pelayananPendudukNonPermanen
);
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
});
// Load data sekali dari backend
useEffect(() => {
const loadPelayananPerizinan = async () => {
const loadData = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await statePendudukNonPermanent.update.load(id);
if (data) {
@@ -32,27 +48,48 @@ function EditPelayananPendudukNonPermanent() {
});
}
} catch (error) {
console.error("Error loading pelayanan perizinan berusaha:", error);
toast.error("Gagal memuat data pelayanan perizinan berusaha");
console.error('Error loading data:', error);
toast.error('Gagal memuat data pelayanan penduduk non permanent');
}
};
loadPelayananPerizinan();
loadData();
}, [params?.id]);
const handleChange =
(field: keyof typeof formData) =>
(value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => {
if (statePendudukNonPermanent.findById.data) {
statePendudukNonPermanent.findById.data.name = formData.name;
statePendudukNonPermanent.findById.data.deskripsi = formData.deskripsi;
statePendudukNonPermanent.update.update(statePendudukNonPermanent.findById.data)
}
router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent')
}
if (!statePendudukNonPermanent.findById.data) return;
// Update global state hanya di submit
const updated = {
...statePendudukNonPermanent.findById.data,
name: formData.name,
deskripsi: formData.deskripsi,
};
await statePendudukNonPermanent.update.update(updated);
router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent');
};
return (
<Box>
<Stack gap="xs">
<Group mb="md">
<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} />
</Button>
</Tooltip>
@@ -62,7 +99,7 @@ function EditPelayananPendudukNonPermanent() {
</Group>
<Paper
w={{ base: "100%", md: "50%" }}
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="md"
radius="md"
@@ -76,23 +113,19 @@ function EditPelayananPendudukNonPermanent() {
<TextInput
label="Judul"
placeholder="Masukkan judul"
defaultValue={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
value={formData.name}
onChange={(e) => handleChange('name')(e.target.value)}
required
/>
{/* Posisi Field */}
{/* Deskripsi Field */}
<Box>
<Text fz="sm" fw="bold">
Deskripsi
</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
}}
onChange={handleChange('deskripsi')}
/>
</Box>
@@ -104,7 +137,9 @@ function EditPelayananPendudukNonPermanent() {
loading={statePendudukNonPermanent.update.loading}
disabled={!formData.name}
>
{statePendudukNonPermanent.update.loading ? 'Menyimpan...' : 'Simpan Perubahan'}
{statePendudukNonPermanent.update.loading
? 'Menyimpan...'
: 'Simpan Perubahan'}
</Button>
<Button

View File

@@ -1,9 +1,18 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
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 { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -12,15 +21,18 @@ import { useProxy } from 'valtio/utils';
function EditPelayananPerizinanBerusaha() {
const router = useRouter();
const params = useParams()
const statePerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha)
const params = useParams();
const statePerizinanBerusaha = useProxy(
stateLayananDesa.pelayananPerizinanBerusaha
);
const [formData, setFormData] = useState({
name: statePerizinanBerusaha.findById.data?.name || '',
deskripsi: statePerizinanBerusaha.findById.data?.deskripsi || '',
link: statePerizinanBerusaha.findById.data?.link || '',
})
name: '',
deskripsi: '',
link: '',
});
// load data pertama kali
useEffect(() => {
const loadPelayananPerizinan = async () => {
const id = params?.id as string;
@@ -35,22 +47,35 @@ function EditPelayananPerizinanBerusaha() {
});
}
} catch (error) {
console.error("Error loading pelayanan perizinan berusaha:", error);
toast.error("Gagal memuat data pelayanan perizinan berusaha");
console.error('Error loading pelayanan perizinan berusaha:', error);
toast.error('Gagal memuat data pelayanan perizinan berusaha');
}
};
loadPelayananPerizinan();
}, [params?.id]);
const handleChange =
(field: keyof typeof formData) =>
(value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => {
const { name, deskripsi, link } = formData;
if (statePerizinanBerusaha.findById.data) {
statePerizinanBerusaha.findById.data.name = formData.name;
statePerizinanBerusaha.findById.data.deskripsi = formData.deskripsi;
statePerizinanBerusaha.findById.data.link = formData.link;
statePerizinanBerusaha.update.update(statePerizinanBerusaha.findById.data)
const updatedData = {
...statePerizinanBerusaha.findById.data,
name,
deskripsi,
link,
};
await statePerizinanBerusaha.update.update(updatedData);
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha');
}
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha')
}
};
return (
<Box>
@@ -58,7 +83,12 @@ function EditPelayananPerizinanBerusaha() {
{/* Header Section */}
<Group mb="md">
<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} />
</Button>
</Tooltip>
@@ -69,7 +99,7 @@ function EditPelayananPerizinanBerusaha() {
{/* Form Section */}
<Paper
w={{ base: "100%", md: "50%" }}
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="md"
radius="md"
@@ -83,8 +113,8 @@ function EditPelayananPerizinanBerusaha() {
<TextInput
label="Judul"
placeholder="Masukkan judul"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={(e) => handleChange('name')(e.target.value)}
required
/>
@@ -92,8 +122,8 @@ function EditPelayananPerizinanBerusaha() {
<TextInput
label="Link"
placeholder="Masukkan link terkait"
defaultValue={formData.link}
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
value={formData.link}
onChange={(e) => handleChange('link')(e.target.value)}
/>
{/* Deskripsi Field */}
@@ -101,7 +131,7 @@ function EditPelayananPerizinanBerusaha() {
<Title order={6}>Deskripsi</Title>
<EditEditor
value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
onChange={handleChange('deskripsi')}
/>
</Box>
@@ -113,7 +143,9 @@ function EditPelayananPerizinanBerusaha() {
loading={statePerizinanBerusaha.update.loading}
disabled={!formData.name}
>
{statePerizinanBerusaha.update.loading ? 'Menyimpan...' : 'Simpan Perubahan'}
{statePerizinanBerusaha.update.loading
? 'Menyimpan...'
: 'Simpan Perubahan'}
</Button>
<Button

View File

@@ -1,5 +1,4 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
@@ -19,7 +18,7 @@ import {
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
@@ -28,17 +27,23 @@ function EditSuratKeterangan() {
const params = useParams();
const stateSurat = useProxy(stateLayananDesa.suratKeterangan);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [file2, setFile2] = useState<File | null>(null);
// state lokal untuk form
const [formData, setFormData] = useState({
name: stateSurat.edit.form.name,
deskripsi: stateSurat.edit.form.deskripsi,
imageId: stateSurat.edit.form.imageId,
image2Id: stateSurat.edit.form.image2Id,
name: '',
deskripsi: '',
imageId: '',
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(() => {
const loadSurat = async () => {
const id = params?.id as string;
@@ -64,15 +69,15 @@ function EditSuratKeterangan() {
};
loadSurat();
}, [params?.id]);
}, [params?.id, stateSurat.edit]);
const handleSubmit = async () => {
// handler untuk submit
const handleSubmit = useCallback(async () => {
try {
stateSurat.edit.form = {
...stateSurat.edit.form,
...formData,
};
// update form global hanya saat submit
stateSurat.edit.form = { ...stateSurat.edit.form, ...formData };
// upload file 1
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
const uploaded = res.data?.data;
@@ -80,6 +85,7 @@ function EditSuratKeterangan() {
stateSurat.edit.form.imageId = uploaded.id;
}
// upload file 2
if (file2) {
const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name });
const uploaded = res.data?.data;
@@ -94,7 +100,7 @@ function EditSuratKeterangan() {
console.error('Error updating surat:', error);
toast.error('Terjadi kesalahan saat memperbarui surat');
}
};
}, [formData, file, file2, router, stateSurat.edit]);
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -119,28 +125,32 @@ function EditSuratKeterangan() {
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md">
{/* Input nama */}
<TextInput
label="Nama Surat Keterangan"
placeholder="Masukkan nama surat keterangan"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
required
/>
{/* Input deskripsi */}
<Box>
<Text fz="sm" fw="bold" mb={6}>
Konten
</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => setFormData({ ...formData, deskripsi: htmlContent })}
onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
}
/>
</Box>
{/* Upload Gambar 1 */}
<Box>
<Text fw="bold" fz="sm" mb={6}>
Gambar Konten Pelayanan
Gambar Konten Pelayanan
</Text>
<Dropzone
onDrop={(files) => {

View File

@@ -15,7 +15,7 @@ import {
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
@@ -26,22 +26,24 @@ function EditPelayananTelunjukSakti() {
const params = useParams();
const [formData, setFormData] = useState({
name: stateTelunjukDesa.edit.form.name,
deskripsi: stateTelunjukDesa.edit.form.deskripsi,
link: stateTelunjukDesa.edit.form.link,
name: '',
deskripsi: '',
link: '',
});
// Load data awal hanya sekali (pas ada id)
useEffect(() => {
const loadPelayananTelunjukSakti = async () => {
const loadData = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await stateTelunjukDesa.edit.load(id);
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
link: data.link || '',
name: data.name ?? '',
deskripsi: data.deskripsi ?? '',
link: data.link ?? '',
});
}
} catch (error) {
@@ -49,9 +51,19 @@ function EditPelayananTelunjukSakti() {
toast.error('Gagal memuat data pelayanan telunjuk sakti');
}
};
loadPelayananTelunjukSakti();
loadData();
}, [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 () => {
try {
stateTelunjukDesa.edit.form = {
@@ -94,8 +106,8 @@ function EditPelayananTelunjukSakti() {
<TextInput
label="Nama Pelayanan"
placeholder="Masukkan nama pelayanan"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={(e) => handleChange('name', e.target.value)}
required
/>
@@ -106,7 +118,7 @@ function EditPelayananTelunjukSakti() {
</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => setFormData({ ...formData, deskripsi: htmlContent })}
onChange={(htmlContent) => handleChange('deskripsi', htmlContent)}
/>
</Box>
@@ -114,8 +126,8 @@ function EditPelayananTelunjukSakti() {
<TextInput
label="Link"
placeholder="Masukkan link terkait"
defaultValue={formData.link}
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
value={formData.link}
onChange={(e) => handleChange('link', e.target.value)}
/>
{/* Tombol Simpan */}

View File

@@ -24,18 +24,22 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPenghargaan() {
const statePenghargaan = useProxy(penghargaanState)
const router = useRouter()
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 statePenghargaan = useProxy(penghargaanState);
const router = useRouter();
const params = useParams();
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(() => {
const loadPenghargaan = async () => {
const id = params?.id as string;
@@ -56,43 +60,43 @@ function EditPenghargaan() {
}
}
} catch (error) {
console.error("Error loading penghargaan:", error);
toast.error("Gagal memuat data penghargaan");
console.error('Error loading penghargaan:', error);
toast.error('Gagal memuat data penghargaan');
}
};
loadPenghargaan();
}, [params?.id]);
// Submit
const handleSubmit = async () => {
try {
// Sync ke global state saat submit
statePenghargaan.edit.form = {
...statePenghargaan.edit.form,
name: formData.name,
juara: formData.juara,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
}
...formData,
};
// Upload file baru (kalau ada)
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");
return toast.error('Gagal upload gambar');
}
statePenghargaan.edit.form.imageId = uploaded.id;
}
await statePenghargaan.edit.update();
toast.success("Penghargaan berhasil diperbarui!");
router.push("/admin/desa/penghargaan");
toast.success('Penghargaan berhasil diperbarui!');
router.push('/admin/desa/penghargaan');
} catch (error) {
console.error("Error updating penghargaan:", error);
toast.error("Terjadi kesalahan saat memperbarui penghargaan");
console.error('Error updating penghargaan:', error);
toast.error('Terjadi kesalahan saat memperbarui penghargaan');
}
}
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -122,8 +126,8 @@ function EditPenghargaan() {
<TextInput
label="Judul"
placeholder="Masukkan judul penghargaan"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
required
/>
@@ -131,8 +135,8 @@ function EditPenghargaan() {
<TextInput
label="Juara"
placeholder="Masukkan juara"
defaultValue={formData.juara}
onChange={(e) => setFormData({ ...formData, juara: e.target.value })}
value={formData.juara}
onChange={(e) => setFormData((prev) => ({ ...prev, juara: e.target.value }))}
required
/>
@@ -182,7 +186,11 @@ function EditPenghargaan() {
src={previewImage}
alt="Preview Gambar"
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"
/>
</Box>
@@ -196,10 +204,9 @@ function EditPenghargaan() {
</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
statePenghargaan.edit.form.deskripsi = htmlContent;
}}
onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
}
/>
</Box>