fix inputan edit menu: desa, ekonomi, inovasi, keamanan, kesehatan, landing-page, & lingkungan

This commit is contained in:
2025-09-30 21:41:26 +08:00
parent c2f1ab8179
commit 63054cedf0
67 changed files with 3056 additions and 2989 deletions

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client'; 'use client';
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman'; import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
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';
@@ -23,10 +24,9 @@ function EditKategoriPengumuman() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({ name: '' });
name: editState.update.form.name || '',
});
// Load data awal sekali aja
useEffect(() => { useEffect(() => {
const loadKategori = async () => { const loadKategori = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -35,9 +35,7 @@ function EditKategoriPengumuman() {
try { try {
const data = await editState.update.load(id); const data = await editState.update.load(id);
if (data) { if (data) {
setFormData({ setFormData({ name: data.name || '' });
name: data.name || '',
});
} }
} catch (error) { } catch (error) {
console.error('Error loading kategori Pengumuman:', error); console.error('Error loading kategori Pengumuman:', error);
@@ -48,8 +46,16 @@ function EditKategoriPengumuman() {
loadKategori(); loadKategori();
}, [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 di sini
editState.update.form = { editState.update.form = {
...editState.update.form, ...editState.update.form,
name: formData.name, name: formData.name,
@@ -97,9 +103,7 @@ function EditKategoriPengumuman() {
label="Nama Kategori Pengumuman" label="Nama Kategori Pengumuman"
placeholder="Masukkan nama kategori Pengumuman" placeholder="Masukkan nama kategori Pengumuman"
value={formData.name} value={formData.name}
onChange={(e) => onChange={(e) => handleChange('name', e.target.value)}
setFormData({ ...formData, name: e.target.value })
}
required required
/> />

View File

@@ -28,16 +28,16 @@ function EditPengumuman() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
judul: editState.pengumuman.edit.form.judul || "", judul: "",
deskripsi: editState.pengumuman.edit.form.deskripsi || "", deskripsi: "",
categoryPengumumanId: categoryPengumumanId: "",
editState.pengumuman.edit.form.categoryPengumumanId || "", content: "",
content: editState.pengumuman.edit.form.content || "",
}); });
// Load pengumuman by id saat pertama kali // Load kategori & pengumuman by id saat pertama kali
useEffect(() => { useEffect(() => {
editState.category.findMany.load(); editState.category.findMany.load();
const loadpengumuman = async () => { const loadpengumuman = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -61,9 +61,13 @@ function EditPengumuman() {
loadpengumuman(); loadpengumuman();
}, [params?.id]); }, [params?.id]);
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// update global state // update global state hanya sekali pas submit
editState.pengumuman.edit.form = { editState.pengumuman.edit.form = {
...editState.pengumuman.edit.form, ...editState.pengumuman.edit.form,
...formData, ...formData,
@@ -108,26 +112,22 @@ function EditPengumuman() {
<TextInput <TextInput
label="Judul Pengumuman" label="Judul Pengumuman"
placeholder="Masukkan judul" placeholder="Masukkan judul"
defaultValue={formData.judul} value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })} onChange={(e) => handleChange("judul", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Deskripsi Singkat" label="Deskripsi Singkat"
placeholder="Masukkan deskripsi" placeholder="Masukkan deskripsi"
defaultValue={formData.deskripsi} value={formData.deskripsi}
onChange={(e) => onChange={(e) => handleChange("deskripsi", e.target.value)}
setFormData({ ...formData, deskripsi: e.target.value })
}
required required
/> />
<Select <Select
value={formData.categoryPengumumanId} value={formData.categoryPengumumanId}
onChange={(val) => onChange={(val) => handleChange("categoryPengumumanId", val || "")}
setFormData({ ...formData, categoryPengumumanId: val || "" })
}
label="Kategori" label="Kategori"
placeholder="Pilih kategori" placeholder="Pilih kategori"
data={ data={
@@ -150,9 +150,7 @@ function EditPengumuman() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.content} value={formData.content}
onChange={(htmlContent) => onChange={(htmlContent) => handleChange("content", htmlContent)}
setFormData({ ...formData, content: htmlContent })
}
/> />
</Box> </Box>

View File

@@ -24,9 +24,10 @@ function EditKategoriPotensi() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nama: editState.update.form.nama || '', nama: '',
}); });
// Load data dari backend -> isi ke formData lokal
useEffect(() => { useEffect(() => {
const loadKategori = async () => { const loadKategori = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -48,8 +49,16 @@ function EditKategoriPotensi() {
loadKategori(); loadKategori();
}, [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 pas submit
editState.update.form = { editState.update.form = {
...editState.update.form, ...editState.update.form,
nama: formData.nama, nama: formData.nama,
@@ -68,7 +77,12 @@ function EditKategoriPotensi() {
<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>
@@ -89,12 +103,12 @@ function EditKategoriPotensi() {
<TextInput <TextInput
label="Nama Kategori Potensi" label="Nama Kategori Potensi"
placeholder="Masukkan nama kategori potensi" placeholder="Masukkan nama kategori potensi"
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} onChange={(e) => handleChange('nama', e.currentTarget.value)}
required required
/> />
<Group justify="right"> <Group justify="flex-end">
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
radius="md" radius="md"

View File

@@ -40,8 +40,14 @@ function EditPotensi() {
imageId: "", imageId: "",
}); });
// handle input changes
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
useEffect(() => { useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load(); potensiDesaState.kategoriPotensi.findMany.load();
const loadPotensi = async () => { const loadPotensi = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -72,22 +78,27 @@ function EditPotensi() {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
potensiState.edit.form = { let imageId = formData.imageId;
...potensiState.edit.form,
...formData,
};
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");
} }
potensiState.edit.form.imageId = uploaded.id; imageId = uploaded.id;
} }
potensiState.edit.form = {
...formData,
imageId,
};
await potensiState.edit.update(); await potensiState.edit.update();
toast.success("Potensi berhasil diperbarui!"); toast.success("Potensi berhasil diperbarui!");
router.push("/admin/desa/potensi/list-potensi"); router.push("/admin/desa/potensi/list-potensi");
@@ -101,7 +112,12 @@ function EditPotensi() {
<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>
@@ -122,22 +138,22 @@ function EditPotensi() {
<TextInput <TextInput
label="Judul Potensi" label="Judul Potensi"
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
/> />
<TextInput <TextInput
label="Deskripsi Singkat" label="Deskripsi Singkat"
placeholder="Masukkan deskripsi" placeholder="Masukkan deskripsi"
defaultValue={formData.deskripsi} value={formData.deskripsi}
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })} onChange={(e) => handleChange("deskripsi", e.target.value)}
required required
/> />
<Select <Select
value={formData.kategoriId} value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })} onChange={(val) => handleChange("kategoriId", val || "")}
label="Kategori" label="Kategori"
placeholder="Pilih kategori" placeholder="Pilih kategori"
data={ data={
@@ -164,7 +180,9 @@ function EditPotensi() {
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"
@@ -172,7 +190,11 @@ function EditPotensi() {
> >
<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} />
@@ -214,7 +236,9 @@ function EditPotensi() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.content} value={formData.content}
onChange={(htmlContent) => setFormData({ ...formData, content: htmlContent })} onChange={(htmlContent) =>
handleChange("content", htmlContent)
}
/> />
</Box> </Box>

View File

@@ -26,15 +26,17 @@ function EditPerbekelDariMasaKeMasa() {
const state = useProxy(stateProfileDesa.mantanPerbekel); const state = useProxy(stateProfileDesa.mantanPerbekel);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
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({
nama: state.update.form.nama || '', nama: '',
daerah: state.update.form.daerah || '', daerah: '',
periode: state.update.form.periode || '', periode: '',
imageId: state.update.form.imageId || '' imageId: ''
}); });
// load data pertama kali
useEffect(() => { useEffect(() => {
const loadFoto = async () => { const loadFoto = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -48,7 +50,9 @@ function EditPerbekelDariMasaKeMasa() {
periode: data.periode || '', periode: data.periode || '',
imageId: data.imageId || '' imageId: data.imageId || ''
}); });
if (data?.imageGalleryFoto?.link) setPreviewImage(data.imageGalleryFoto.link); if (data?.imageGalleryFoto?.link) {
setPreviewImage(data.imageGalleryFoto.link);
}
} }
} catch (error) { } catch (error) {
console.error('Error loading foto:', error); console.error('Error loading foto:', error);
@@ -58,8 +62,17 @@ function EditPerbekelDariMasaKeMasa() {
loadFoto(); loadFoto();
}, [params?.id]); }, [params?.id]);
// helper ubah state formData
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// update global state hanya sekali pas submit
state.update.form = { ...state.update.form, ...formData }; state.update.form = { ...state.update.form, ...formData };
if (file) { if (file) {
@@ -106,8 +119,8 @@ function EditPerbekelDariMasaKeMasa() {
<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
/> />
@@ -161,7 +174,7 @@ function EditPerbekelDariMasaKeMasa() {
objectFit: 'contain', objectFit: 'contain',
border: `1px solid ${colors['blue-button']}`, border: `1px solid ${colors['blue-button']}`,
}} }}
loading='lazy' loading="lazy"
/> />
</Box> </Box>
)} )}
@@ -170,16 +183,16 @@ function EditPerbekelDariMasaKeMasa() {
<TextInput <TextInput
label="Daerah" label="Daerah"
placeholder="Masukkan daerah" placeholder="Masukkan daerah"
defaultValue={formData.daerah} value={formData.daerah}
onChange={(e) => setFormData({ ...formData, daerah: e.target.value })} onChange={(e) => handleChange('daerah', e.target.value)}
required required
/> />
<TextInput <TextInput
label="Periode" label="Periode"
placeholder="Masukkan periode" placeholder="Masukkan periode"
defaultValue={formData.periode} value={formData.periode}
onChange={(e) => setFormData({ ...formData, periode: e.target.value })} onChange={(e) => handleChange('periode', e.target.value)}
required required
/> />

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'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 { import {
@@ -29,13 +29,13 @@ function EditAPBDesa() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
tahun: apbState.update.form.tahun || '', tahun: '',
pendapatanIds: apbState.update.form.pendapatanIds || [], pendapatanIds: [] as string[],
belanjaIds: apbState.update.form.belanjaIds || [], belanjaIds: [] as string[],
pembiayaanIds: apbState.update.form.pembiayaanIds || [], pembiayaanIds: [] as string[],
}); });
// Load APB desa by id // Load APB desa by id → hanya update formData, bukan global state
useEffect(() => { useEffect(() => {
const loadAPBdesa = async () => { const loadAPBdesa = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -45,7 +45,7 @@ function EditAPBDesa() {
const data = await apbState.update.load(id); const data = await apbState.update.load(id);
if (data) { if (data) {
setFormData({ setFormData({
tahun: data.tahun || 0, tahun: String(data.tahun || ''),
pendapatanIds: data.pendapatan?.map((p: any) => p.id) || [], pendapatanIds: data.pendapatan?.map((p: any) => p.id) || [],
belanjaIds: data.belanja?.map((b: any) => b.id) || [], belanjaIds: data.belanja?.map((b: any) => b.id) || [],
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [], pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
@@ -60,8 +60,13 @@ function EditAPBDesa() {
loadAPBdesa(); loadAPBdesa();
}, [params?.id]); }, [params?.id]);
const handleChange = (field: keyof typeof formData, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// update global state cuma pas submit
apbState.update.form = { apbState.update.form = {
...apbState.update.form, ...apbState.update.form,
tahun: Number(formData.tahun), tahun: Number(formData.tahun),
@@ -111,10 +116,8 @@ function EditAPBDesa() {
{/* Tahun */} {/* Tahun */}
<TextInput <TextInput
type="number" type="number"
defaultValue={formData.tahun} value={formData.tahun}
onChange={(e) => onChange={(e) => handleChange("tahun", e.target.value)}
setFormData({ ...formData, tahun: e.target.value })
}
label={<Text fz="sm" fw="bold">Tahun</Text>} label={<Text fz="sm" fw="bold">Tahun</Text>}
placeholder="Masukkan tahun anggaran" placeholder="Masukkan tahun anggaran"
required required
@@ -123,23 +126,17 @@ function EditAPBDesa() {
{/* Selects */} {/* Selects */}
<SelectPendapatan <SelectPendapatan
selectedIds={formData.pendapatanIds} selectedIds={formData.pendapatanIds}
onSelectionChange={(ids) => onSelectionChange={(ids) => handleChange("pendapatanIds", ids)}
setFormData({ ...formData, pendapatanIds: ids })
}
/> />
<SelectBelanja <SelectBelanja
selectedIds={formData.belanjaIds} selectedIds={formData.belanjaIds}
onSelectionChange={(ids) => onSelectionChange={(ids) => handleChange("belanjaIds", ids)}
setFormData({ ...formData, belanjaIds: ids })
}
/> />
<SelectPembiayaan <SelectPembiayaan
selectedIds={formData.pembiayaanIds} selectedIds={formData.pembiayaanIds}
onSelectionChange={(ids) => onSelectionChange={(ids) => handleChange("pembiayaanIds", ids)}
setFormData({ ...formData, pembiayaanIds: ids })
}
/> />
{/* Save Button */} {/* Save Button */}
@@ -164,7 +161,13 @@ function EditAPBDesa() {
/* --- Sub Components --- */ /* --- Sub Components --- */
function SelectPendapatan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) { function SelectPendapatan({
selectedIds,
onSelectionChange,
}: {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}) {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan); const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
useShallowEffect(() => { useShallowEffect(() => {
@@ -192,7 +195,13 @@ function EditAPBDesa() {
); );
} }
function SelectBelanja({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) { function SelectBelanja({
selectedIds,
onSelectionChange,
}: {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}) {
const belanjaState = useProxy(PendapatanAsliDesa.belanja); const belanjaState = useProxy(PendapatanAsliDesa.belanja);
useShallowEffect(() => { useShallowEffect(() => {
@@ -220,7 +229,13 @@ function EditAPBDesa() {
); );
} }
function SelectPembiayaan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) { function SelectPembiayaan({
selectedIds,
onSelectionChange,
}: {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}) {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan); const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
useShallowEffect(() => { useShallowEffect(() => {

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'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 { import {
@@ -24,12 +25,16 @@ function EditBelanja() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: belanjaState.update.form.name || '', name: '',
value: belanjaState.update.form.value || '', value: '',
}); });
// format angka ke rupiah
const formatRupiah = (value: number | string) => { const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); const number =
typeof value === 'number'
? value
: Number(value.replace(/\D/g, '')) || 0;
return new Intl.NumberFormat('id-ID', { return new Intl.NumberFormat('id-ID', {
style: 'currency', style: 'currency',
currency: 'IDR', currency: 'IDR',
@@ -37,8 +42,9 @@ function EditBelanja() {
}).format(number); }).format(number);
}; };
// buang semua simbol jadi angka murni
const unformatRupiah = (value: string) => { const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, '')); return Number(value.replace(/\D/g, '')) || 0;
}; };
useEffect(() => { useEffect(() => {
@@ -51,7 +57,7 @@ function EditBelanja() {
if (data) { if (data) {
setFormData({ setFormData({
name: data.name || '', name: data.name || '',
value: data.value || '', value: String(data.value || ''),
}); });
} }
} catch (error) { } catch (error) {
@@ -69,7 +75,7 @@ function EditBelanja() {
...belanjaState.update.form, ...belanjaState.update.form,
name: formData.name, name: formData.name,
value: Number(formData.value), value: Number(formData.value),
} };
await belanjaState.update.update(); await belanjaState.update.update();
toast.success("Jenis Belanja berhasil diperbarui!"); toast.success("Jenis Belanja berhasil diperbarui!");
@@ -78,7 +84,7 @@ function EditBelanja() {
console.error("Error updating jenis belanja:", error); console.error("Error updating jenis belanja:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis belanja"); toast.error("Terjadi kesalahan saat memperbarui jenis belanja");
} }
} };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -112,19 +118,21 @@ function EditBelanja() {
<TextInput <TextInput
label="Nama Jenis Belanja" label="Nama Jenis Belanja"
placeholder="Masukkan nama jenis belanja" placeholder="Masukkan nama jenis belanja"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
required required
/> />
<TextInput <TextInput
label="Nilai" label="Nilai"
placeholder="Masukkan nilai" placeholder="Masukkan nilai"
defaultValue={formatRupiah(formData.value)} value={formData.value ? formatRupiah(formData.value) : ''}
onChange={(e) => { onChange={(e) => {
const raw = e.currentTarget.value; const raw = e.currentTarget.value;
const cleanValue = unformatRupiah(raw); const cleanValue = unformatRupiah(raw);
setFormData({ ...formData, value: cleanValue }); setFormData({ ...formData, value: String(cleanValue) });
}} }}
required required
/> />

View File

@@ -24,12 +24,15 @@ function EditPembiayaan() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: pembiayaanState.update.form.name || '', name: '',
value: pembiayaanState.update.form.value || '', value: '',
}); });
const formatRupiah = (value: number | string) => { const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.toString().replace(/\D/g, '')); const number =
typeof value === 'number'
? value
: Number(value.toString().replace(/\D/g, '')) || 0;
return new Intl.NumberFormat('id-ID', { return new Intl.NumberFormat('id-ID', {
style: 'currency', style: 'currency',
currency: 'IDR', currency: 'IDR',
@@ -38,7 +41,7 @@ function EditPembiayaan() {
}; };
const unformatRupiah = (value: string) => { const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, '')); return Number(value.replace(/\D/g, '')) || 0;
}; };
useEffect(() => { useEffect(() => {
@@ -51,7 +54,7 @@ function EditPembiayaan() {
if (data) { if (data) {
setFormData({ setFormData({
name: data.name || '', name: data.name || '',
value: data.value || '', value: String(data.value || ''),
}); });
} }
} catch (error) { } catch (error) {
@@ -68,7 +71,7 @@ function EditPembiayaan() {
pembiayaanState.update.form = { pembiayaanState.update.form = {
...pembiayaanState.update.form, ...pembiayaanState.update.form,
name: formData.name, name: formData.name,
value: Number(formData.value), value: unformatRupiah(formData.value),
}; };
await pembiayaanState.update.update(); await pembiayaanState.update.update();
@@ -82,7 +85,7 @@ function EditPembiayaan() {
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan Back Button */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow> <Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button <Button
@@ -112,19 +115,21 @@ function EditPembiayaan() {
<TextInput <TextInput
label="Nama Jenis Pembiayaan" label="Nama Jenis Pembiayaan"
placeholder="Masukkan nama jenis pembiayaan" placeholder="Masukkan nama jenis pembiayaan"
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
/> />
<TextInput <TextInput
label="Nilai" label="Nilai"
placeholder="Masukkan nilai" placeholder="Masukkan nilai"
defaultValue={formatRupiah(formData.value)} value={formData.value ? formatRupiah(formData.value) : ''}
onChange={(e) => { onChange={(e) => {
const raw = e.currentTarget.value; const raw = e.currentTarget.value;
const cleanValue = unformatRupiah(raw); const cleanValue = unformatRupiah(raw);
setFormData({ ...formData, value: cleanValue }); setFormData((prev) => ({ ...prev, value: String(cleanValue) }));
}} }}
required required
/> />

View File

@@ -24,12 +24,16 @@ function EditPendapatan() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: pendapatanState.update.form.name || '', name: '',
value: pendapatanState.update.form.value || '', value: '',
}); });
// helper format
const formatRupiah = (value: number | string) => { const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.toString().replace(/\D/g, '')); const number = typeof value === 'number'
? value
: Number(value.toString().replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', { return new Intl.NumberFormat('id-ID', {
style: 'currency', style: 'currency',
currency: 'IDR', currency: 'IDR',
@@ -39,6 +43,7 @@ function EditPendapatan() {
const unformatRupiah = (value: string) => Number(value.replace(/\D/g, '')); const unformatRupiah = (value: string) => Number(value.replace(/\D/g, ''));
// load data once
useEffect(() => { useEffect(() => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -48,8 +53,8 @@ function EditPendapatan() {
const data = await pendapatanState.update.load(id); const data = await pendapatanState.update.load(id);
if (data) { if (data) {
setFormData({ setFormData({
name: data.name || '', name: data.name ?? '',
value: data.value || '', value: data.value?.toString() ?? '',
}); });
} }
} catch (error) { } catch (error) {
@@ -61,6 +66,13 @@ function EditPendapatan() {
loadPendapatan(); loadPendapatan();
}, [params?.id]); }, [params?.id]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
pendapatanState.update.form = { pendapatanState.update.form = {
@@ -110,19 +122,19 @@ function EditPendapatan() {
<TextInput <TextInput
label="Nama Jenis Pendapatan" label="Nama Jenis Pendapatan"
placeholder="Masukkan nama jenis pendapatan" placeholder="Masukkan nama jenis pendapatan"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange('name', e.target.value)}
required required
/> />
<TextInput <TextInput
label="Nilai" label="Nilai"
placeholder="Masukkan nilai" placeholder="Masukkan nilai"
defaultValue={formatRupiah(formData.value)} value={formData.value ? formatRupiah(formData.value) : ''}
onChange={(e) => { onChange={(e) => {
const raw = e.currentTarget.value; const raw = e.currentTarget.value;
const cleanValue = unformatRupiah(raw); const cleanValue = unformatRupiah(raw).toString();
setFormData({ ...formData, value: cleanValue }); handleChange('value', cleanValue);
}} }}
required required
/> />

View File

@@ -10,15 +10,21 @@ 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';
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan'; import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
interface FormData {
pekerjaan: string;
lakiLaki: number;
perempuan: number;
}
function EditDemografiPekerjaan() { function EditDemografiPekerjaan() {
const router = useRouter(); const router = useRouter();
const params = useParams() as { id: string }; const params = useParams() as { id: string };
@@ -26,19 +32,27 @@ function EditDemografiPekerjaan() {
const id = params.id; const id = params.id;
const [formData, setFormData] = useState<FormData>({
pekerjaan: '',
lakiLaki: 0,
perempuan: 0,
});
// Load data sekali waktu
useEffect(() => { useEffect(() => {
if (!id) return; if (!id) return;
stateDemografi.update.id = id; stateDemografi.update.id = id;
stateDemografi.findUnique stateDemografi.findUnique
.load(id) .load(id)
.then(() => { .then(() => {
const data = stateDemografi.findUnique.data; const data = stateDemografi.findUnique.data;
if (data) { if (data) {
stateDemografi.update.form = { setFormData({
pekerjaan: String(data.pekerjaan || ''), pekerjaan: String(data.pekerjaan || ''),
lakiLaki: Number(data.lakiLaki || 0), lakiLaki: Number(data.lakiLaki || 0),
perempuan: Number(data.perempuan || 0), perempuan: Number(data.perempuan || 0),
}; });
} }
}) })
.catch((error) => { .catch((error) => {
@@ -47,9 +61,22 @@ function EditDemografiPekerjaan() {
}); });
}, [id]); }, [id]);
const handleChange =
(field: keyof FormData) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({
...prev,
[field]:
field === 'lakiLaki' || field === 'perempuan'
? Number(e.currentTarget.value)
: e.currentTarget.value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateDemografi.update.id = id; stateDemografi.update.id = id;
stateDemografi.update.form = { ...formData };
await stateDemografi.update.submit(); await stateDemografi.update.submit();
toast.success('Data berhasil diperbarui'); toast.success('Data berhasil diperbarui');
router.push('/admin/ekonomi/demografi-pekerjaan'); router.push('/admin/ekonomi/demografi-pekerjaan');
@@ -91,10 +118,8 @@ function EditDemografiPekerjaan() {
<TextInput <TextInput
label="Pekerjaan" label="Pekerjaan"
placeholder="Masukkan jenis pekerjaan" placeholder="Masukkan jenis pekerjaan"
defaultValue={stateDemografi.update.form.pekerjaan} value={formData.pekerjaan}
onChange={(e) => onChange={handleChange('pekerjaan')}
(stateDemografi.update.form.pekerjaan = e.currentTarget.value)
}
required required
/> />
@@ -102,12 +127,8 @@ function EditDemografiPekerjaan() {
label="Jumlah Pekerja Laki-laki" label="Jumlah Pekerja Laki-laki"
type="number" type="number"
placeholder="Masukkan jumlah pekerja laki-laki" placeholder="Masukkan jumlah pekerja laki-laki"
defaultValue={stateDemografi.update.form.lakiLaki} value={formData.lakiLaki}
onChange={(e) => onChange={handleChange('lakiLaki')}
(stateDemografi.update.form.lakiLaki = Number(
e.currentTarget.value
))
}
required required
/> />
@@ -115,12 +136,8 @@ function EditDemografiPekerjaan() {
label="Jumlah Pekerja Perempuan" label="Jumlah Pekerja Perempuan"
type="number" type="number"
placeholder="Masukkan jumlah pekerja perempuan" placeholder="Masukkan jumlah pekerja perempuan"
defaultValue={stateDemografi.update.form.perempuan} value={formData.perempuan}
onChange={(e) => onChange={handleChange('perempuan')}
(stateDemografi.update.form.perempuan = Number(
e.currentTarget.value
))
}
required required
/> />

View File

@@ -3,10 +3,19 @@
import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin'; import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title, Group, Tooltip } from '@mantine/core'; import {
Box,
Button,
Paper,
Stack,
TextInput,
Title,
Group,
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 } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -17,6 +26,13 @@ function EditJumlahPendudukMiskin() {
const id = params.id; const id = params.id;
// 🔹 State lokal untuk form
const [formData, setFormData] = useState({
year: 0,
totalPoorPopulation: 0,
});
// 🔹 Load data awal dari backend
useEffect(() => { useEffect(() => {
if (!id) return; if (!id) return;
@@ -25,10 +41,10 @@ function EditJumlahPendudukMiskin() {
await stateJPM.findUnique.load(id); await stateJPM.findUnique.load(id);
const data = stateJPM.findUnique.data; const data = stateJPM.findUnique.data;
if (data) { if (data) {
stateJPM.update.form = { setFormData({
year: data.year || 0, year: data.year || 0,
totalPoorPopulation: data.totalPoorPopulation || 0, totalPoorPopulation: data.totalPoorPopulation || 0,
}; });
} }
} catch (error) { } catch (error) {
console.error('Gagal memuat data:', error); console.error('Gagal memuat data:', error);
@@ -39,9 +55,21 @@ function EditJumlahPendudukMiskin() {
loadData(); loadData();
}, [id]); }, [id]);
// 🔹 Handler input controlled
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({
...prev,
[field]: Number(value),
}));
};
// 🔹 Submit form
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateJPM.update.id = id; stateJPM.update.id = id;
// update global state cuma saat submit
stateJPM.update.form = { ...formData };
await stateJPM.update.submit(); await stateJPM.update.submit();
toast.success('Data jumlah penduduk miskin berhasil diperbarui!'); toast.success('Data jumlah penduduk miskin berhasil diperbarui!');
router.push('/admin/ekonomi/jumlah-penduduk-miskin'); router.push('/admin/ekonomi/jumlah-penduduk-miskin');
@@ -55,7 +83,12 @@ function EditJumlahPendudukMiskin() {
<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>
@@ -78,10 +111,8 @@ function EditJumlahPendudukMiskin() {
placeholder="Masukkan tahun" placeholder="Masukkan tahun"
type="number" type="number"
required required
defaultValue={stateJPM.update.form.year} value={formData.year}
onChange={(val) => { onChange={(e) => handleChange('year', e.currentTarget.value)}
stateJPM.update.form.year = Number(val.currentTarget.value);
}}
/> />
<TextInput <TextInput
@@ -89,10 +120,10 @@ function EditJumlahPendudukMiskin() {
placeholder="Masukkan jumlah penduduk miskin" placeholder="Masukkan jumlah penduduk miskin"
type="number" type="number"
required required
defaultValue={stateJPM.update.form.totalPoorPopulation} value={formData.totalPoorPopulation}
onChange={(val) => { onChange={(e) =>
stateJPM.update.form.totalPoorPopulation = Number(val.currentTarget.value); handleChange('totalPoorPopulation', e.currentTarget.value)
}} }
/> />
<Group justify="right"> <Group justify="right">

View File

@@ -1,11 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'; 'use client';
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur'; import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
/* eslint-disable react-hooks/exhaustive-deps */
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 } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditGrafikBerdasarkanPendidikan() { function EditGrafikBerdasarkanPendidikan() {
@@ -14,34 +14,59 @@ function EditGrafikBerdasarkanPendidikan() {
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan); const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
const id = params.id; const id = params.id;
// state lokal untuk form
const [formData, setFormData] = useState({
SD: '',
SMP: '',
SMA: '',
D3: '',
S1: '',
});
useEffect(() => { useEffect(() => {
if (id) { if (id) {
stategrafik.findUnique.load(id).then(() => { stategrafik.findUnique.load(id).then(() => {
const data = stategrafik.findUnique.data; const data = stategrafik.findUnique.data;
if (data) { if (data) {
stategrafik.update.form = { setFormData({
SD: data.SD || '', SD: data.SD || '',
SMP: data.SMP || '', SMP: data.SMP || '',
SMA: data.SMA || '', SMA: data.SMA || '',
D3: data.D3 || '', D3: data.D3 || '',
S1: data.S1 || '', S1: data.S1 || '',
}; });
} }
}); });
} }
}, [id]); }, [id]);
const handleChange = (field: keyof typeof formData) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({
...prev,
[field]: e.currentTarget.value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
stategrafik.update.id = id; stategrafik.update.id = id;
stategrafik.update.form = { ...formData }; // update global state pas submit aja
await stategrafik.update.submit(); await stategrafik.update.submit();
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan'); router.push(
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan'
);
}; };
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>
@@ -63,36 +88,36 @@ function EditGrafikBerdasarkanPendidikan() {
label="SD" label="SD"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.SD} value={formData.SD}
onChange={(val) => (stategrafik.update.form.SD = val.currentTarget.value)} onChange={handleChange('SD')}
/> />
<TextInput <TextInput
label="SMP" label="SMP"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.SMP} value={formData.SMP}
onChange={(val) => (stategrafik.update.form.SMP = val.currentTarget.value)} onChange={handleChange('SMP')}
/> />
<TextInput <TextInput
label="SMA" label="SMA"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.SMA} value={formData.SMA}
onChange={(val) => (stategrafik.update.form.SMA = val.currentTarget.value)} onChange={handleChange('SMA')}
/> />
<TextInput <TextInput
label="D3" label="D3"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.D3} value={formData.D3}
onChange={(val) => (stategrafik.update.form.D3 = val.currentTarget.value)} onChange={handleChange('D3')}
/> />
<TextInput <TextInput
label="S1" label="S1"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.S1} value={formData.S1}
onChange={(val) => (stategrafik.update.form.S1 = val.currentTarget.value)} onChange={handleChange('S1')}
/> />
<Group justify="right"> <Group justify="right">

View File

@@ -2,39 +2,70 @@
'use client'; 'use client';
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur'; import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title, Group, Tooltip } from '@mantine/core'; import {
Box,
Button,
Paper,
Stack,
TextInput,
Title,
Group,
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 } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() { function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
const router = useRouter(); const router = useRouter();
const params = useParams() as { id: string }; const params = useParams() as { id: string };
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur); const stategrafik = useProxy(
grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur
);
const id = params.id; const id = params.id;
// ✅ state lokal, controlled
const [formData, setFormData] = useState({
usia18_25: '',
usia26_35: '',
usia36_45: '',
usia46_keatas: '',
});
// load data dari global state -> masukin ke local state
useEffect(() => { useEffect(() => {
if (id) { if (id) {
stategrafik.findUnique.load(id).then(() => { stategrafik.findUnique.load(id).then(() => {
const data = stategrafik.findUnique.data; const data = stategrafik.findUnique.data;
if (data) { if (data) {
stategrafik.update.form = { setFormData({
usia18_25: data.usia18_25 || '', usia18_25: data.usia18_25 || '',
usia26_35: data.usia26_35 || '', usia26_35: data.usia26_35 || '',
usia36_45: data.usia36_45 || '', usia36_45: data.usia36_45 || '',
usia46_keatas: data.usia46_keatas || '', usia46_keatas: data.usia46_keatas || '',
}; });
} }
}); });
} }
}, [id]); }, [id]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// ✅ baru update global state pas submit
stategrafik.update.id = id; stategrafik.update.id = id;
stategrafik.update.form = { ...formData };
await stategrafik.update.submit(); await stategrafik.update.submit();
toast.success('Data grafik berhasil diperbarui!'); toast.success('Data grafik berhasil diperbarui!');
router.push( router.push(
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia' '/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia'
@@ -49,7 +80,12 @@ function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
<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>
@@ -71,40 +107,32 @@ function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
label="Usia 18 - 25" label="Usia 18 - 25"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.usia18_25} value={formData.usia18_25}
onChange={(val) => { onChange={(e) => handleChange('usia18_25', e.currentTarget.value)}
stategrafik.update.form.usia18_25 = val.currentTarget.value;
}}
required required
/> />
<TextInput <TextInput
label="Usia 26 - 35" label="Usia 26 - 35"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.usia26_35} value={formData.usia26_35}
onChange={(val) => { onChange={(e) => handleChange('usia26_35', e.currentTarget.value)}
stategrafik.update.form.usia26_35 = val.currentTarget.value;
}}
required required
/> />
<TextInput <TextInput
label="Usia 36 - 45" label="Usia 36 - 45"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.usia36_45} value={formData.usia36_45}
onChange={(val) => { onChange={(e) => handleChange('usia36_45', e.currentTarget.value)}
stategrafik.update.form.usia36_45 = val.currentTarget.value;
}}
required required
/> />
<TextInput <TextInput
label="Usia 46 +" label="Usia 46 +"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.usia46_keatas} value={formData.usia46_keatas}
onChange={(val) => { onChange={(e) => handleChange('usia46_keatas', e.currentTarget.value)}
stategrafik.update.form.usia46_keatas = val.currentTarget.value;
}}
required required
/> />

View File

@@ -2,10 +2,21 @@
'use client'; 'use client';
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran'; import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Select, NumberInput } from '@mantine/core'; import {
Box,
Button,
Group,
Paper,
Stack,
Text,
TextInput,
Title,
Select,
NumberInput,
} 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';
@@ -24,16 +35,20 @@ function EditDetailDataPengangguran() {
}); });
// Hitung total & perubahan otomatis // Hitung total & perubahan otomatis
const calculateTotalAndChange = async () => { const calculateTotalAndChange = useCallback(
const total = formData.educatedUnemployment + formData.uneducatedUnemployment; async (data: typeof formData) => {
const total = data.educatedUnemployment + data.uneducatedUnemployment;
let percentageChange = 0; let percentageChange = 0;
const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']; const monthOrder = [
const currentMonthIndex = monthOrder.indexOf(formData.month); 'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des',
];
const currentMonthIndex = monthOrder.indexOf(data.month);
if (currentMonthIndex !== -1) { if (currentMonthIndex !== -1) {
let prevMonthIndex = currentMonthIndex - 1; let prevMonthIndex = currentMonthIndex - 1;
let prevYear = formData.year; let prevYear = data.year;
if (prevMonthIndex < 0) { if (prevMonthIndex < 0) {
prevMonthIndex = 11; prevMonthIndex = 11;
@@ -41,23 +56,36 @@ function EditDetailDataPengangguran() {
} }
const prevMonth = monthOrder[prevMonthIndex]; const prevMonth = monthOrder[prevMonthIndex];
const prevData = await stateDetail.findByMonthYear.load({ month: prevMonth, year: prevYear }); const prevData = await stateDetail.findByMonthYear.load({
month: prevMonth,
year: prevYear,
});
if (prevData && prevData.totalUnemployment > 0) { if (prevData && prevData.totalUnemployment > 0) {
const change = ((total - prevData.totalUnemployment) / prevData.totalUnemployment) * 100; const change =
((total - prevData.totalUnemployment) /
prevData.totalUnemployment) *
100;
percentageChange = parseFloat(change.toFixed(1)); percentageChange = parseFloat(change.toFixed(1));
} }
} }
return { total, percentageChange }; return { total, percentageChange };
}; },
[stateDetail.findByMonthYear]
);
const updateFormData = async (updates: Partial<typeof formData>) => { const updateFormData = async (updates: Partial<typeof formData>) => {
const newData = { ...formData, ...updates }; const newData = { ...formData, ...updates };
const { total, percentageChange } = await calculateTotalAndChange(); const { total, percentageChange } = await calculateTotalAndChange(newData);
setFormData({ ...newData, totalUnemployment: total, percentageChange }); setFormData({
...newData,
totalUnemployment: total,
percentageChange,
});
}; };
// Load detail hanya sekali
useEffect(() => { useEffect(() => {
const loadDetail = async () => { const loadDetail = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -68,45 +96,39 @@ function EditDetailDataPengangguran() {
const data = stateDetail.findUnique.data; const data = stateDetail.findUnique.data;
if (data) { if (data) {
const yearValue =
// Convert year from Date to number if needed data.year && typeof data.year === 'object' && 'getFullYear' in data.year
const yearValue = data.year && typeof data.year === 'object' && 'getFullYear' in data.year
? (data.year as Date).getFullYear() ? (data.year as Date).getFullYear()
: Number(data.year); : Number(data.year);
// Set the ID for update stateDetail.update.id = id; // set ID untuk update
stateDetail.update.id = id;
// Update Valtio state with converted year
stateDetail.update.form = {
...data,
year: yearValue,
percentageChange: data.percentageChange || 0 // Ensure it's always a number
};
// Update local formData with converted year
setFormData({ setFormData({
month: data.month, month: data.month,
year: yearValue, year: yearValue,
totalUnemployment: data.totalUnemployment, totalUnemployment: data.totalUnemployment,
educatedUnemployment: data.educatedUnemployment, educatedUnemployment: data.educatedUnemployment,
uneducatedUnemployment: data.uneducatedUnemployment, uneducatedUnemployment: data.uneducatedUnemployment,
percentageChange: data.percentageChange || 0, // Ensure it's always a number percentageChange: data.percentageChange || 0,
}); });
} }
} catch (error) { } catch (error) {
console.error("Error loading detail:", error); console.error('Error loading detail:', error);
toast.error("Gagal memuat data detail"); toast.error('Gagal memuat data detail');
} }
}; };
loadDetail(); loadDetail();
}, [params?.id]); }, [params?.id, stateDetail.findUnique]);
const handleSubmit = async () => { const handleSubmit = async () => {
const { total, percentageChange } = await calculateTotalAndChange(); const { total, percentageChange } = await calculateTotalAndChange(formData);
try { try {
stateDetail.update.form = { ...formData, totalUnemployment: total, percentageChange }; stateDetail.update.form = {
...formData,
totalUnemployment: total,
percentageChange,
};
const success = await stateDetail.update.submit(); const success = await stateDetail.update.submit();
if (success) { if (success) {
toast.success('Detail data pengangguran berhasil diperbarui!'); toast.success('Detail data pengangguran berhasil diperbarui!');
@@ -121,7 +143,12 @@ function EditDetailDataPengangguran() {
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md"> <Group mb="md">
<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>
<Title order={4} ml="sm"> <Title order={4} ml="sm">
@@ -129,36 +156,57 @@ function EditDetailDataPengangguran() {
</Title> </Title>
</Group> </Group>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="lg" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}> <Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md"> <Stack gap="md">
<Select <Select
label="Bulan" label="Bulan"
data={['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']} data={[
'Jan','Feb','Mar','Apr','Mei','Jun',
'Jul','Agu','Sep','Okt','Nov','Des',
]}
value={formData.month} value={formData.month}
onChange={(val) => updateFormData({ month: val || '' })} onChange={(val) => updateFormData({ month: val || '' })}
/> />
<NumberInput <NumberInput
label="Tahun" label="Tahun"
defaultValue={formData.year} value={formData.year}
onChange={(val) => updateFormData({ year: Number(val) })} onChange={(val) => updateFormData({ year: Number(val) })}
required required
/> />
<TextInput <TextInput
label="Pengangguran Terdidik" label="Pengangguran Terdidik"
type="number" type="number"
defaultValue={formData.educatedUnemployment} value={formData.educatedUnemployment}
onChange={(val) => updateFormData({ educatedUnemployment: Number(val.currentTarget.value) || 0 })} onChange={(val) =>
updateFormData({ educatedUnemployment: Number(val.currentTarget.value) || 0 })
}
required required
/> />
<TextInput <TextInput
label="Pengangguran Tidak Terdidik" label="Pengangguran Tidak Terdidik"
type="number" type="number"
defaultValue={formData.uneducatedUnemployment} value={formData.uneducatedUnemployment}
onChange={(val) => updateFormData({ uneducatedUnemployment: Number(val.currentTarget.value) || 0 })} onChange={(val) =>
updateFormData({ uneducatedUnemployment: Number(val.currentTarget.value) || 0 })
}
required required
/> />
<Text fz="sm" fw={500}>Total Otomatis: {formData.totalUnemployment}</Text> <Text fz="sm" fw={500}>
<Text fz="sm" fw={500}>Perubahan Otomatis: {formData.percentageChange !== null ? `${formData.percentageChange}%` : '-'}</Text> Total Otomatis: {formData.totalUnemployment}
</Text>
<Text fz="sm" fw={500}>
Perubahan Otomatis:{' '}
{formData.percentageChange !== null
? `${formData.percentageChange}%`
: '-'}
</Text>
<Group justify="right"> <Group justify="right">
<Button <Button
@@ -181,4 +229,3 @@ function EditDetailDataPengangguran() {
} }
export default EditDetailDataPengangguran; export default EditDetailDataPengangguran;

View File

@@ -26,15 +26,16 @@ function EditLowonganKerja() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
posisi: lowonganKerjaState.update.form.posisi, posisi: '',
namaPerusahaan: lowonganKerjaState.update.form.namaPerusahaan, namaPerusahaan: '',
lokasi: lowonganKerjaState.update.form.lokasi, lokasi: '',
tipePekerjaan: lowonganKerjaState.update.form.tipePekerjaan, tipePekerjaan: '',
gaji: lowonganKerjaState.update.form.gaji, gaji: '',
deskripsi: lowonganKerjaState.update.form.deskripsi, deskripsi: '',
kualifikasi: lowonganKerjaState.update.form.kualifikasi, kualifikasi: '',
}); });
// load data sekali aja ketika mount / id berubah
useEffect(() => { useEffect(() => {
const loadLowongan = async () => { const loadLowongan = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -62,14 +63,17 @@ function EditLowonganKerja() {
loadLowongan(); loadLowongan();
}, [params?.id]); }, [params?.id]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
lowonganState.update.id = params?.id as string; lowonganState.update.id = params?.id as string;
lowonganState.update.form = { ...formData };
lowonganState.update.form = {
...lowonganState.update.form,
...formData,
};
await lowonganState.update.update(); await lowonganState.update.update();
toast.success("Lowongan kerja berhasil diperbarui!"); toast.success("Lowongan kerja berhasil diperbarui!");
@@ -107,40 +111,40 @@ function EditLowonganKerja() {
<TextInput <TextInput
label="Posisi" label="Posisi"
placeholder="Masukkan posisi" placeholder="Masukkan posisi"
defaultValue={formData.posisi} value={formData.posisi}
onChange={(e) => setFormData({ ...formData, posisi: e.target.value })} onChange={(e) => handleChange("posisi", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Nama Perusahaan" label="Nama Perusahaan"
placeholder="Masukkan nama perusahaan" placeholder="Masukkan nama perusahaan"
defaultValue={formData.namaPerusahaan} value={formData.namaPerusahaan}
onChange={(e) => setFormData({ ...formData, namaPerusahaan: e.target.value })} onChange={(e) => handleChange("namaPerusahaan", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Lokasi" label="Lokasi"
placeholder="Masukkan lokasi" placeholder="Masukkan lokasi"
defaultValue={formData.lokasi} value={formData.lokasi}
onChange={(e) => setFormData({ ...formData, lokasi: e.target.value })} onChange={(e) => handleChange("lokasi", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Tipe Pekerjaan" label="Tipe Pekerjaan"
placeholder="Masukkan tipe pekerjaan" placeholder="Masukkan tipe pekerjaan"
defaultValue={formData.tipePekerjaan} value={formData.tipePekerjaan}
onChange={(e) => setFormData({ ...formData, tipePekerjaan: e.target.value })} onChange={(e) => handleChange("tipePekerjaan", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Gaji (per bulan)" label="Gaji (per bulan)"
placeholder="Masukkan gaji" placeholder="Masukkan gaji"
defaultValue={formData.gaji} value={formData.gaji}
onChange={(e) => setFormData({ ...formData, gaji: e.target.value })} onChange={(e) => handleChange("gaji", e.target.value)}
required required
/> />
@@ -150,7 +154,7 @@ function EditLowonganKerja() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })} onChange={(val) => handleChange("deskripsi", val)}
/> />
</Box> </Box>
@@ -160,7 +164,7 @@ function EditLowonganKerja() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.kualifikasi} value={formData.kualifikasi}
onChange={(val) => setFormData({ ...formData, kualifikasi: val })} onChange={(val) => handleChange("kualifikasi", val)}
/> />
</Box> </Box>

View File

@@ -25,9 +25,8 @@ function EditKategoriProduk() {
const id = params?.id as string; const id = params?.id as string;
const statePasar = useProxy(pasarDesaState.kategoriProduk); const statePasar = useProxy(pasarDesaState.kategoriProduk);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({ nama: '' });
nama: '', const [loading, setLoading] = useState(true);
});
useEffect(() => { useEffect(() => {
const loadKategoriProduk = async () => { const loadKategoriProduk = async () => {
@@ -37,21 +36,30 @@ function EditKategoriProduk() {
const data = await statePasar.edit.load(id); const data = await statePasar.edit.load(id);
if (data) { if (data) {
// pastikan id-nya masuk ke state edit // simpan id ke state global hanya untuk referensi
statePasar.edit.id = id; statePasar.edit.id = id;
setFormData({
nama: data.nama || '', // simpan data ke state lokal
}); setFormData({ nama: data.nama || '' });
} }
} catch (error) { } catch (error) {
console.error('Error loading kategori produk:', error); console.error('Error loading kategori produk:', error);
toast.error('Gagal memuat data kategori produk'); toast.error('Gagal memuat data kategori produk');
} finally {
setLoading(false);
} }
}; };
loadKategoriProduk(); loadKategoriProduk();
}, [id]); }, [id]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
if (!formData.nama.trim()) { if (!formData.nama.trim()) {
@@ -59,10 +67,8 @@ function EditKategoriProduk() {
return; return;
} }
statePasar.edit.form = { // update global state hanya saat submit
nama: formData.nama.trim(), statePasar.edit.form = { nama: formData.nama.trim() };
};
if (!statePasar.edit.id) { if (!statePasar.edit.id) {
statePasar.edit.id = id; // fallback statePasar.edit.id = id; // fallback
} }
@@ -79,12 +85,21 @@ function EditKategoriProduk() {
} }
}; };
if (loading) {
return <Text>Loading...</Text>;
}
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan tombol back */} {/* Header dengan tombol back */}
<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>
@@ -104,10 +119,11 @@ function EditKategoriProduk() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
name="nama"
label={<Text fw="bold" fz="sm">Nama Kategori Produk</Text>} label={<Text fw="bold" fz="sm">Nama Kategori Produk</Text>}
placeholder="Masukkan nama kategori produk" placeholder="Masukkan nama kategori produk"
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} onChange={handleChange}
required required
/> />

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa'; import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -24,6 +24,15 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
type FormData = {
nama: string;
harga: number;
alamatUsaha: string;
imageId: string;
rating: number;
kategoriId: string[];
};
function EditPasarDesa() { function EditPasarDesa() {
const pasarState = useProxy(pasarDesaState); const pasarState = useProxy(pasarDesaState);
const router = useRouter(); const router = useRouter();
@@ -31,17 +40,19 @@ function EditPasarDesa() {
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<FormData>({
nama: pasarState.pasarDesa.edit.form.nama || '', nama: '',
harga: pasarState.pasarDesa.edit.form.harga || 0, harga: 0,
alamatUsaha: pasarState.pasarDesa.edit.form.alamatUsaha || '', alamatUsaha: '',
imageId: pasarState.pasarDesa.edit.form.imageId || '', imageId: '',
rating: pasarState.pasarDesa.edit.form.rating || 0, rating: 0,
kategoriId: pasarState.pasarDesa.edit.form.kategoriId || [], kategoriId: [],
}); });
// load data awal
useEffect(() => { useEffect(() => {
pasarState.kategoriProduk.findMany.load(); pasarState.kategoriProduk.findMany.load();
const loadPasarDesa = async () => { const loadPasarDesa = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -70,19 +81,27 @@ function EditPasarDesa() {
loadPasarDesa(); loadPasarDesa();
}, [params?.id]); }, [params?.id]);
const handleChange = (key: keyof FormData, value: any) => {
setFormData((prev) => ({ ...prev, [key]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
pasarState.pasarDesa.edit.form = { ...pasarState.pasarDesa.edit.form, ...formData }; // upload image kalau ada file baru
let imageId = formData.imageId;
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) return toast.error('Gagal upload gambar'); if (!uploaded?.id) return toast.error('Gagal upload gambar');
imageId = uploaded.id;
pasarState.pasarDesa.edit.form.imageId = uploaded.id;
} }
// update global state hanya saat submit
pasarState.pasarDesa.edit.form = {
...formData,
imageId,
};
await pasarState.pasarDesa.edit.update(); await pasarState.pasarDesa.edit.update();
toast.success('Pasar desa berhasil diperbarui!'); toast.success('Pasar desa berhasil diperbarui!');
router.push('/admin/ekonomi/pasar-desa/produk-pasar-desa'); router.push('/admin/ekonomi/pasar-desa/produk-pasar-desa');
@@ -114,6 +133,7 @@ function EditPasarDesa() {
style={{ border: '1px solid #e0e0e0' }} style={{ border: '1px solid #e0e0e0' }}
> >
<Stack gap="md"> <Stack gap="md">
{/* Dropzone upload */}
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Gambar Produk Gambar Produk
@@ -170,11 +190,12 @@ function EditPasarDesa() {
)} )}
</Box> </Box>
{/* Controlled Inputs */}
<TextInput <TextInput
label="Nama Produk" label="Nama Produk"
placeholder="Masukkan nama produk" placeholder="Masukkan nama produk"
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} onChange={(e) => handleChange('nama', e.target.value)}
required required
/> />
@@ -182,8 +203,8 @@ function EditPasarDesa() {
type="number" type="number"
label="Harga Produk" label="Harga Produk"
placeholder="Masukkan harga produk" placeholder="Masukkan harga produk"
defaultValue={formData.harga} value={formData.harga}
onChange={(e) => setFormData({ ...formData, harga: Number(e.target.value) })} onChange={(e) => handleChange('harga', Number(e.target.value))}
required required
/> />
@@ -194,16 +215,16 @@ function EditPasarDesa() {
step={0.1} step={0.1}
label="Rating Produk" label="Rating Produk"
placeholder="Masukkan rating produk (0-5)" placeholder="Masukkan rating produk (0-5)"
defaultValue={formData.rating} value={formData.rating}
onChange={(e) => setFormData({ ...formData, rating: Number(e.target.value) })} onChange={(e) => handleChange('rating', Number(e.target.value))}
required required
/> />
<TextInput <TextInput
label="Alamat Usaha" label="Alamat Usaha"
placeholder="Masukkan alamat usaha" placeholder="Masukkan alamat usaha"
defaultValue={formData.alamatUsaha} value={formData.alamatUsaha}
onChange={(e) => setFormData({ ...formData, alamatUsaha: e.target.value })} onChange={(e) => handleChange('alamatUsaha', e.target.value)}
required required
/> />
@@ -211,7 +232,7 @@ function EditPasarDesa() {
label="Kategori Produk" label="Kategori Produk"
placeholder="Pilih kategori produk" placeholder="Pilih kategori produk"
value={formData.kategoriId} value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val })} onChange={(val) => handleChange('kategoriId', val)}
data={ data={
pasarState.kategoriProduk.findMany.data?.map((v) => ({ pasarState.kategoriProduk.findMany.data?.map((v) => ({
value: v.id, value: v.id,

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
@@ -19,22 +18,45 @@ 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 } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
type FormData = {
nama: string;
deskripsi: string;
icon: string;
statistik: {
tahun: string;
jumlah: string;
};
};
function EditProgramKemiskinan() { function EditProgramKemiskinan() {
const router = useRouter(); const router = useRouter();
const params = useParams() as { id: string }; const params = useParams() as { id: string };
const stateProgram = useProxy(programKemiskinanState); const stateProgram = useProxy(programKemiskinanState);
const id = params.id; const id = params.id;
const [formData, setFormData] = useState<FormData>({
nama: '',
deskripsi: '',
icon: '',
statistik: {
tahun: '',
jumlah: '',
},
});
// load data ke local state sekali aja
useEffect(() => { useEffect(() => {
if (id) { if (id) {
stateProgram.findUnique.load(id).then(() => { stateProgram.findUnique
.load(id)
.then(() => {
const data = stateProgram.findUnique.data; const data = stateProgram.findUnique.data;
if (data) { if (data) {
stateProgram.update.form = { setFormData({
nama: data.nama || '', nama: data.nama || '',
deskripsi: data.deskripsi || '', deskripsi: data.deskripsi || '',
icon: data.icon || '', icon: data.icon || '',
@@ -42,30 +64,49 @@ function EditProgramKemiskinan() {
tahun: data.statistik?.tahun?.toString() || '', tahun: data.statistik?.tahun?.toString() || '',
jumlah: data.statistik?.jumlah?.toString() || '', jumlah: data.statistik?.jumlah?.toString() || '',
}, },
};
}
}).catch((err) => {
console.error("Error load data:", err);
toast.error("Gagal mengambil data program");
}); });
} }
}, [id]); })
.catch((err) => {
console.error('Error load data:', err);
toast.error('Gagal mengambil data program');
});
}
}, [id, stateProgram.findUnique]);
const handleChange = (field: keyof FormData, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleStatistikChange = (field: keyof FormData['statistik'], value: string) => {
setFormData((prev) => ({
...prev,
statistik: {
...prev.statistik,
[field]: value,
},
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateProgram.update.id = id; stateProgram.update.id = id;
stateProgram.update.form = formData;
await stateProgram.update.update(); await stateProgram.update.update();
toast.success("Program berhasil diperbarui!"); toast.success('Program berhasil diperbarui!');
router.push('/admin/ekonomi/program-kemiskinan'); router.push('/admin/ekonomi/program-kemiskinan');
} catch (error) { } catch (error) {
console.error("Error update program:", error); console.error('Error update program:', error);
toast.error("Terjadi kesalahan saat memperbarui program"); toast.error('Terjadi kesalahan saat memperbarui program');
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan tombol kembali */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow> <Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button <Button
@@ -92,10 +133,8 @@ function EditProgramKemiskinan() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={stateProgram.update.form.nama} value={formData.nama}
onChange={(e) => { onChange={(e) => handleChange('nama', e.target.value)}
stateProgram.update.form.nama = e.target.value;
}}
label={<Text fw="bold" fz="sm">Judul Program</Text>} label={<Text fw="bold" fz="sm">Judul Program</Text>}
placeholder="Masukkan judul program" placeholder="Masukkan judul program"
required required
@@ -106,10 +145,8 @@ function EditProgramKemiskinan() {
Deskripsi Deskripsi
</Text> </Text>
<EditEditor <EditEditor
value={stateProgram.update.form.deskripsi} value={formData.deskripsi}
onChange={(val) => { onChange={(val) => handleChange('deskripsi', val)}
stateProgram.update.form.deskripsi = val;
}}
/> />
</Box> </Box>
@@ -118,10 +155,8 @@ function EditProgramKemiskinan() {
Ikon Program Kreatif Desa Ikon Program Kreatif Desa
</Text> </Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={stateProgram.update.form.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={(val) => handleChange('icon', val)}
stateProgram.update.form.icon = value;
}}
/> />
</Box> </Box>
@@ -131,10 +166,8 @@ function EditProgramKemiskinan() {
</Text> </Text>
<TextInput <TextInput
type="number" type="number"
defaultValue={stateProgram.update.form.statistik.jumlah} value={formData.statistik.jumlah}
onChange={(e) => { onChange={(e) => handleStatistikChange('jumlah', e.target.value)}
stateProgram.update.form.statistik.jumlah = e.target.value;
}}
label="Jumlah Masyarakat Miskin" label="Jumlah Masyarakat Miskin"
placeholder="Masukkan jumlah masyarakat miskin" placeholder="Masukkan jumlah masyarakat miskin"
required required
@@ -142,10 +175,8 @@ function EditProgramKemiskinan() {
<TextInput <TextInput
type="number" type="number"
defaultValue={stateProgram.update.form.statistik.tahun} value={formData.statistik.tahun}
onChange={(e) => { onChange={(e) => handleStatistikChange('tahun', e.target.value)}
stateProgram.update.form.statistik.tahun = e.target.value;
}}
label="Tahun" label="Tahun"
placeholder="Masukkan tahun" placeholder="Masukkan tahun"
required required

View File

@@ -16,7 +16,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 } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
@@ -28,28 +28,46 @@ function EditSektorUnggulanDesa() {
const id = params.id; const id = params.id;
// state lokal buat form
const [formData, setFormData] = useState({
name: '',
description: '',
value: 0,
});
// Load data saat komponen mount // Load data saat komponen mount
useEffect(() => { useEffect(() => {
if (id) { if (id) {
stateGrafik.findUnique.load(id).then(() => { stateGrafik.findUnique
.load(id)
.then(() => {
const data = stateGrafik.findUnique.data; const data = stateGrafik.findUnique.data;
if (data) { if (data) {
stateGrafik.update.form = { setFormData({
name: data.name || '', name: data.name || '',
description: data.description || '', description: data.description || '',
value: data.value || 0, value: data.value || 0,
}; });
} }
}).catch((err) => { })
.catch((err) => {
console.error('Error load sektor unggulan:', err); console.error('Error load sektor unggulan:', err);
toast.error('Gagal mengambil data sektor unggulan'); toast.error('Gagal mengambil data sektor unggulan');
}); });
} }
}, [id]); }, [id]);
const handleChange =
(field: keyof typeof formData) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = field === 'value' ? Number(e.currentTarget.value) : e.currentTarget.value;
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateGrafik.update.id = id; stateGrafik.update.id = id;
stateGrafik.update.form = { ...formData }; // update global pas submit
await stateGrafik.update.submit(); await stateGrafik.update.submit();
toast.success('Sektor unggulan berhasil diperbarui!'); toast.success('Sektor unggulan berhasil diperbarui!');
router.push('/admin/ekonomi/sektor-unggulan-desa'); router.push('/admin/ekonomi/sektor-unggulan-desa');
@@ -89,10 +107,8 @@ function EditSektorUnggulanDesa() {
<TextInput <TextInput
label="Nama Sektor Unggulan" label="Nama Sektor Unggulan"
placeholder="Masukkan nama sektor unggulan" placeholder="Masukkan nama sektor unggulan"
defaultValue={stateGrafik.update.form.name} value={formData.name}
onChange={(val) => { onChange={handleChange('name')}
stateGrafik.update.form.name = val.currentTarget.value;
}}
required required
/> />
<Box> <Box>
@@ -100,20 +116,18 @@ function EditSektorUnggulanDesa() {
Konten Konten
</Text> </Text>
<EditEditor <EditEditor
value={stateGrafik.update.form.description} value={formData.description}
onChange={(htmlContent) => { onChange={(htmlContent) =>
stateGrafik.update.form.description = htmlContent; setFormData((prev) => ({ ...prev, description: htmlContent }))
}} }
/> />
</Box> </Box>
<TextInput <TextInput
label="Jumlah" label="Jumlah"
type="number" type="number"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={stateGrafik.update.form.value} value={formData.value}
onChange={(val) => { onChange={handleChange('value')}
stateGrafik.update.form.value = Number(val.currentTarget.value);
}}
required required
/> />

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -20,36 +19,49 @@ export default function EditHubunganOrganisasi() {
tipe: '', tipe: '',
}); });
// load data awal sekali aja
useEffect(() => { useEffect(() => {
strukturorganisasiState.pegawai.findMany.load(); strukturorganisasiState.pegawai.findMany.load();
if (id) { if (id) {
state.edit.load(id).then(data => { (async () => {
const data = await state.edit.load(id);
if (data) { if (data) {
setForm({ setForm({
atasanId: data.atasanId, atasanId: data.atasanId ?? '',
bawahanId: data.bawahanId, bawahanId: data.bawahanId ?? '',
tipe: data.tipe || '', tipe: data.tipe ?? '',
}); });
} }
}); })();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]); }, [id]);
const handleChange = (field: keyof typeof form, value: string) => {
setForm(prev => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!form.atasanId || !form.bawahanId) { if (!form.atasanId || !form.bawahanId) {
toast.warn("Atasan dan bawahan harus diisi"); toast.warn('Atasan dan bawahan harus diisi');
return; return;
} }
// update global state cuma pas submit
state.edit.id = id; state.edit.id = id;
state.edit.form = form; state.edit.form = form;
const result = await state.edit.update(); const result = await state.edit.update();
if (result) { if (result) {
toast.success("Data berhasil diperbarui"); toast.success('Data berhasil diperbarui');
router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/hubungan-organisasi'); router.push(
'/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/hubungan-organisasi'
);
} }
}; };
@@ -58,35 +70,45 @@ export default function EditHubunganOrganisasi() {
<Paper p="md" w={{ base: '100%', md: '50%' }}> <Paper p="md" w={{ base: '100%', md: '50%' }}>
<Stack> <Stack>
<Title order={3}>Edit Hubungan Organisasi</Title> <Title order={3}>Edit Hubungan Organisasi</Title>
<Select <Select
label="Atasan" label="Atasan"
placeholder="Pilih atasan" placeholder="Pilih atasan"
searchable searchable
data={pegawaiList?.map(p => ({ data={
pegawaiList?.map(p => ({
value: p.id, value: p.id,
label: p.namaLengkap, label: p.namaLengkap,
})) || []} })) || []
}
value={form.atasanId} value={form.atasanId}
onChange={(val) => setForm({ ...form, atasanId: val || '' })} onChange={val => handleChange('atasanId', val || '')}
/> />
<Select <Select
label="Bawahan" label="Bawahan"
placeholder="Pilih bawahan" placeholder="Pilih bawahan"
searchable searchable
data={pegawaiList?.map(p => ({ data={
pegawaiList?.map(p => ({
value: p.id, value: p.id,
label: p.namaLengkap, label: p.namaLengkap,
})) || []} })) || []
}
value={form.bawahanId} value={form.bawahanId}
onChange={(val) => setForm({ ...form, bawahanId: val || '' })} onChange={val => handleChange('bawahanId', val || '')}
/> />
<TextInput <TextInput
label="Tipe" label="Tipe"
placeholder="Contoh: langsung_melapor" placeholder="Contoh: langsung_melapor"
defaultValue={form.tipe} value={form.tipe}
onChange={(e) => setForm({ ...form, tipe: e.currentTarget.value })} onChange={e => handleChange('tipe', e.currentTarget.value)}
/> />
<Button onClick={handleSubmit} color="blue">Simpan</Button>
<Button onClick={handleSubmit} color="blue">
Simpan
</Button>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import strukturorganisasiState from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi'; import strukturorganisasiState from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi';
@@ -41,20 +42,28 @@ interface PegawaiFormData {
export default function EditPegawai() { export default function EditPegawai() {
const router = useRouter(); const router = useRouter();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [previewImage, setPreviewImage] = useState<PreviewImage | string | null>(null);
const stateOrganisasi = useProxy(strukturorganisasiState.pegawai); const stateOrganisasi = useProxy(strukturorganisasiState.pegawai);
const [previewImage, setPreviewImage] = useState<PreviewImage | string | null>(null);
const [formData, setFormData] = useState<PegawaiFormData>({ const [formData, setFormData] = useState<PegawaiFormData>({
namaLengkap: "", namaLengkap: '',
gelarAkademik: "", gelarAkademik: '',
imageId: "", imageId: '',
tanggalMasuk: "", tanggalMasuk: '',
email: "", email: '',
telepon: "", telepon: '',
alamat: "", alamat: '',
posisiId: "", posisiId: '',
isActive: true, isActive: true,
}); });
// helper buat update state formData
const updateForm = (field: keyof PegawaiFormData, value: any) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
// Format date to YYYY-MM-DD for date input // Format date to YYYY-MM-DD for date input
const formatDateForInput = (dateString: string) => { const formatDateForInput = (dateString: string) => {
@@ -65,32 +74,29 @@ export default function EditPegawai() {
useEffect(() => { useEffect(() => {
strukturorganisasiState.posisiOrganisasi.findMany.load(); strukturorganisasiState.posisiOrganisasi.findMany.load();
const loadPegawai = async () => { const loadPegawai = async () => {
try { try {
const data = await stateOrganisasi.edit.load(id); const data = await stateOrganisasi.edit.load(id);
if (data) { if (data) {
setFormData({ setFormData({
namaLengkap: data.namaLengkap || "", namaLengkap: data.namaLengkap || '',
gelarAkademik: data.gelarAkademik || "", gelarAkademik: data.gelarAkademik || '',
imageId: data.imageId || "", imageId: data.imageId || '',
tanggalMasuk: data.tanggalMasuk || "", tanggalMasuk: data.tanggalMasuk || '',
email: data.email || "", email: data.email || '',
telepon: data.telepon || "", telepon: data.telepon || '',
alamat: data.alamat || "", alamat: data.alamat || '',
posisiId: data.posisiId || "", posisiId: data.posisiId || '',
isActive: data.isActive ?? true, // pakai nullish coalescing isActive: data.isActive ?? true,
}); });
if (data.image?.link) { setPreviewImage(data.image?.link || null);
setPreviewImage(data.image.link);
} else {
setPreviewImage(null);
}
} }
} catch (error) { } catch (error) {
console.error("Error loading pegawai:", error); console.error('Error loading pegawai:', error);
toast.error( toast.error(
error instanceof Error ? error.message : "Gagal mengambil data pegawai" error instanceof Error ? error.message : 'Gagal mengambil data pegawai'
); );
} }
}; };
@@ -106,19 +112,17 @@ export default function EditPegawai() {
} }
stateOrganisasi.edit.form = { stateOrganisasi.edit.form = {
...formData,
namaLengkap: formData.namaLengkap.trim(), namaLengkap: formData.namaLengkap.trim(),
gelarAkademik: formData.gelarAkademik.trim(), gelarAkademik: formData.gelarAkademik.trim(),
imageId: formData.imageId ? formData.imageId.trim() : "", imageId: formData.imageId ? formData.imageId.trim() : '',
tanggalMasuk: formData.tanggalMasuk.trim(), tanggalMasuk: formData.tanggalMasuk.trim(),
email: formData.email.trim(), email: formData.email.trim(),
telepon: formData.telepon.trim(), telepon: formData.telepon.trim(),
alamat: formData.alamat.trim(), alamat: formData.alamat.trim(),
posisiId: formData.posisiId.trim(), posisiId: formData.posisiId.trim(),
isActive: formData.isActive,
}; };
if (id && !stateOrganisasi.edit.id) { if (id && !stateOrganisasi.edit.id) {
stateOrganisasi.edit.id = id; stateOrganisasi.edit.id = id;
} }
@@ -126,67 +130,90 @@ export default function EditPegawai() {
const success = await stateOrganisasi.edit.submit(); const success = await stateOrganisasi.edit.submit();
if (success) { if (success) {
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai"); router.push(
'/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai'
);
} }
} catch (error) { } catch (error) {
console.error("Error updating pegawai:", error); console.error('Error updating pegawai:', error);
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data pegawai"); toast.error(
error instanceof Error ? error.message : 'Gagal memperbarui data pegawai'
);
} }
}; };
return ( return (
<Box> <Box>
<Box mb={10}> <Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}> <Button onClick={() => router.back()} variant="subtle" color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} /> <IconArrowBack color={colors['blue-button']} size={25} />
</Button> </Button>
</Box> </Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}> <Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}> <Stack gap={'xs'}>
<Title order={4}>Edit Data Pegawai</Title> <Title order={4}>Edit Data Pegawai</Title>
<TextInput <TextInput
label="Nama Lengkap" label="Nama Lengkap"
placeholder="Masukkan nama lengkap" placeholder="Masukkan nama lengkap"
defaultValue={formData.namaLengkap} value={formData.namaLengkap}
onChange={(e) => setFormData({ ...formData, namaLengkap: e.target.value })} onChange={(e) => updateForm('namaLengkap', e.target.value)}
/> />
<TextInput <TextInput
label="Gelar Akademik" label="Gelar Akademik"
placeholder="Contoh: S.Kom" placeholder="Contoh: S.Kom"
defaultValue={formData.gelarAkademik} value={formData.gelarAkademik}
onChange={(e) => setFormData({ ...formData, gelarAkademik: e.target.value })} onChange={(e) => updateForm('gelarAkademik', e.target.value)}
/> />
<Box> <Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text> <Text fz={'md'} fw={'bold'}>
Gambar
</Text>
<Box> <Box>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {
const file = files[0]; // Hanya ambil file pertama const file = files[0];
if (file) { if (file) {
setPreviewImage({ setPreviewImage({
file, file,
preview: URL.createObjectURL(file) preview: URL.createObjectURL(file),
}); });
} }
}} }}
maxSize={5 * 1024 ** 2} // 5MB maxSize={5 * 1024 ** 2}
accept={{ accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp'] 'image/*': ['.jpeg', '.jpg', '.png', '.webp'],
}} }}
> >
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}> <Group
justify="center"
gap="xl"
mih={220}
style={{ pointerEvents: 'none' }}
>
<Dropzone.Accept> <Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} /> <IconUpload
size={52}
color="var(--mantine-color-blue-6)"
stroke={1.5}
/>
</Dropzone.Accept> </Dropzone.Accept>
<Dropzone.Reject> <Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} /> <IconX
size={52}
color="var(--mantine-color-red-6)"
stroke={1.5}
/>
</Dropzone.Reject> </Dropzone.Reject>
<Dropzone.Idle> <Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} /> <IconPhoto
size={52}
color="var(--mantine-color-dimmed)"
stroke={1.5}
/>
</Dropzone.Idle> </Dropzone.Idle>
<div> <div>
@@ -194,14 +221,20 @@ export default function EditPegawai() {
Drag images here or click to select files Drag images here or click to select files
</Text> </Text>
<Text size="sm" c="dimmed" inline mt={7}> <Text size="sm" c="dimmed" inline mt={7}>
Attach as many files as you like, each file should not exceed 5mb Attach as many files as you like, each file should not
exceed 5mb
</Text> </Text>
</div> </div>
</Group> </Group>
</Dropzone> </Dropzone>
{previewImage && ( {previewImage && (
<Image <Image
src={typeof previewImage === 'string' ? previewImage : previewImage?.preview} src={
typeof previewImage === 'string'
? previewImage
: previewImage?.preview
}
alt="Preview" alt="Preview"
width={280} width={280}
height={180} height={180}
@@ -213,65 +246,65 @@ export default function EditPegawai() {
)} )}
</Box> </Box>
</Box> </Box>
<TextInput <TextInput
label="Tanggal Masuk" label="Tanggal Masuk"
type="date" type="date"
placeholder="Contoh: 2022-01-01" placeholder="Contoh: 2022-01-01"
defaultValue={formatDateForInput(formData.tanggalMasuk)} value={formatDateForInput(formData.tanggalMasuk)}
onChange={(e) => setFormData({ ...formData, tanggalMasuk: e.target.value })} onChange={(e) => updateForm('tanggalMasuk', e.target.value)}
/> />
<TextInput <TextInput
label="Email" label="Email"
placeholder="Contoh: email@example.com" placeholder="Contoh: email@example.com"
defaultValue={formData.email} value={formData.email}
onChange={(e) => (formData.email = e.currentTarget.value)} onChange={(e) => updateForm('email', e.currentTarget.value)}
/> />
<TextInput <TextInput
label="Telepon" label="Telepon"
placeholder="Contoh: 08123456789" placeholder="Contoh: 08123456789"
defaultValue={formData.telepon} value={formData.telepon}
onChange={(e) => (formData.telepon = e.currentTarget.value)} onChange={(e) => updateForm('telepon', e.currentTarget.value)}
/> />
<TextInput <TextInput
label="Alamat" label="Alamat"
placeholder="Contoh: Jl. Contoh No. 1" placeholder="Contoh: Jl. Contoh No. 1"
defaultValue={formData.alamat} value={formData.alamat}
onChange={(e) => (formData.alamat = e.currentTarget.value)} onChange={(e) => updateForm('alamat', e.currentTarget.value)}
/> />
<Select <Select
label="Posisi" label="Posisi"
placeholder="Pilih posisi" placeholder="Pilih posisi"
data={ data={
strukturorganisasiState.posisiOrganisasi.findMany.data?.map((p) => ({ strukturorganisasiState.posisiOrganisasi.findMany.data?.map(
value: p.id, // harus string (p) => ({
value: p.id,
label: p.nama, label: p.nama,
})) || [] })
) || []
} }
value={formData.posisiId} value={formData.posisiId}
onChange={(value) => { onChange={(value) => {
if (value !== null) { if (value !== null) updateForm('posisiId', value);
setFormData({ ...formData, posisiId: value }); // value harus string
}
}} }}
/> />
<Select <Select
label="Status Pegawai" label="Status Pegawai"
data={[ data={[
{ value: 'true', label: 'Aktif' }, { value: 'true', label: 'Aktif' },
{ value: 'false', label: 'Tidak Aktif' }, { value: 'false', label: 'Tidak Aktif' },
]} ]}
value={String(formData.isActive)} // 'true' atau 'false' value={String(formData.isActive)}
onChange={(val) => { onChange={(val) => updateForm('isActive', val === 'true')}
setFormData({ ...formData, isActive: val === 'true' });
}}
/> />
<Group> <Group>
<Button <Button onClick={handleSubmit} color="blue">
onClick={handleSubmit}
color="blue"
>
Simpan Simpan
</Button> </Button>
</Group> </Group>

View File

@@ -32,6 +32,7 @@ function EditPosisiOrganisasi() {
hierarki: 0, hierarki: 0,
}); });
// Load data awal sekali saja
useEffect(() => { useEffect(() => {
const loadPosisiOrganisasi = async () => { const loadPosisiOrganisasi = async () => {
if (!id) return; if (!id) return;
@@ -42,9 +43,9 @@ function EditPosisiOrganisasi() {
if (data) { if (data) {
stateOrganisasi.edit.id = id; stateOrganisasi.edit.id = id;
setFormData({ setFormData({
nama: data.nama || '', nama: data.nama ?? '',
deskripsi: data.deskripsi || '', deskripsi: data.deskripsi ?? '',
hierarki: data.hierarki || 0, hierarki: data.hierarki ?? 0,
}); });
} }
} catch (error) { } catch (error) {
@@ -56,6 +57,10 @@ function EditPosisiOrganisasi() {
loadPosisiOrganisasi(); loadPosisiOrganisasi();
}, [id]); }, [id]);
const handleChange = (field: string, value: string | number) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
if (!formData.nama.trim()) { if (!formData.nama.trim()) {
@@ -63,6 +68,7 @@ function EditPosisiOrganisasi() {
return; return;
} }
// update global state HANYA saat submit
stateOrganisasi.edit.form = { stateOrganisasi.edit.form = {
nama: formData.nama.trim(), nama: formData.nama.trim(),
deskripsi: formData.deskripsi.trim(), deskripsi: formData.deskripsi.trim(),
@@ -114,10 +120,8 @@ function EditPosisiOrganisasi() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => onChange={(e) => handleChange('nama', e.target.value)}
setFormData({ ...formData, nama: e.target.value })
}
label="Nama Posisi Organisasi" label="Nama Posisi Organisasi"
placeholder="Masukkan nama posisi organisasi" placeholder="Masukkan nama posisi organisasi"
required required
@@ -130,19 +134,16 @@ function EditPosisiOrganisasi() {
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => onChange={(htmlContent) =>
setFormData({ ...formData, deskripsi: htmlContent }) handleChange('deskripsi', htmlContent)
} }
/> />
</Box> </Box>
<TextInput <TextInput
type="number" type="number"
defaultValue={formData.hierarki} value={formData.hierarki}
onChange={(e) => onChange={(e) =>
setFormData({ handleChange('hierarki', parseInt(e.target.value) || 0)
...formData,
hierarki: parseInt(e.target.value) || 0,
})
} }
label="Hierarki" label="Hierarki"
placeholder="Masukkan hierarki" placeholder="Masukkan hierarki"

View File

@@ -12,18 +12,22 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditPenghargaan() { function EditDigitalSmartVillage() {
const stateDesaDigital = useProxy(desaDigitalState) const stateDesaDigital = useProxy(desaDigitalState);
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: stateDesaDigital.findUnique.data?.name || '',
deskripsi: stateDesaDigital.findUnique.data?.deskripsi || '',
imageId: stateDesaDigital.findUnique.data?.imageId || '',
})
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
// ✅ hanya lokal state untuk form
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
imageId: '',
});
// load data sekali saat mount
useEffect(() => { useEffect(() => {
const loadPenghargaan = async () => { const loadPenghargaan = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -53,12 +57,11 @@ function EditPenghargaan() {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// ✅ update global state hanya saat submit
stateDesaDigital.edit.form = { stateDesaDigital.edit.form = {
...stateDesaDigital.edit.form, ...stateDesaDigital.edit.form,
name: formData.name, ...formData,
deskripsi: formData.deskripsi, };
imageId: formData.imageId,
}
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 });
@@ -78,7 +81,7 @@ function EditPenghargaan() {
console.error("Error updating desa digital smart village:", error); console.error("Error updating desa digital smart village:", error);
toast.error("Terjadi kesalahan saat memperbarui desa digital smart village"); toast.error("Terjadi kesalahan saat memperbarui desa digital smart village");
} }
} };
return ( return (
<Box> <Box>
@@ -87,28 +90,31 @@ function EditPenghargaan() {
<IconArrowBack color={colors["blue-button"]} size={30} /> <IconArrowBack color={colors["blue-button"]} size={30} />
</Button> </Button>
</Box> </Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}> <Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Title order={3}>Edit Desa Digital Smart Village</Title> <Title order={3}>Edit Desa Digital Smart Village</Title>
{/* ✅ controlled input */}
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>} label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
placeholder="masukkan judul" placeholder="masukkan judul"
/> />
<Box> <Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text> <Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama const selectedFile = files[0];
if (selectedFile) { if (selectedFile) {
setFile(selectedFile); setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview setPreviewImage(URL.createObjectURL(selectedFile));
} }
}} }}
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' }}>
@@ -133,7 +139,6 @@ function EditPenghargaan() {
</Group> </Group>
</Dropzone> </Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && ( {previewImage && (
<Box mt="sm"> <Box mt="sm">
<Image <Image
@@ -151,14 +156,14 @@ function EditPenghargaan() {
</Box> </Box>
)} )}
</Box> </Box>
</Box>
<Box> <Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text> <Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
{/* ✅ controlled editor */}
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
stateDesaDigital.edit.form.deskripsi = htmlContent;
}} }}
/> />
</Box> </Box>
@@ -170,4 +175,4 @@ function EditPenghargaan() {
); );
} }
export default EditPenghargaan; export default EditDigitalSmartVillage;

View File

@@ -30,17 +30,19 @@ function EditInfoTeknologiTepatGuna() {
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: stateInfoTekno.findUnique.data?.name || '', name: '',
deskripsi: stateInfoTekno.findUnique.data?.deskripsi || '', deskripsi: '',
imageId: stateInfoTekno.findUnique.data?.imageId || '', imageId: '',
}); });
// Load data pertama kali
useEffect(() => { useEffect(() => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
const loadPenghargaan = async () => { const loadData = async () => {
try { try {
const data = await stateInfoTekno.edit.load(id); const data = await stateInfoTekno.edit.load(id);
if (data) { if (data) {
@@ -58,16 +60,19 @@ function EditInfoTeknologiTepatGuna() {
} }
}; };
loadPenghargaan(); loadData();
}, [params?.id]); }, [params?.id]);
// Submit form
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// sync local → global pas submit
stateInfoTekno.edit.form = { stateInfoTekno.edit.form = {
...stateInfoTekno.edit.form, ...stateInfoTekno.edit.form,
...formData, ...formData,
}; };
// upload file kalau ada
if (file) { if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({
file, file,
@@ -119,8 +124,8 @@ function EditInfoTeknologiTepatGuna() {
<TextInput <TextInput
label="Judul" label="Judul"
placeholder="Masukkan judul info teknologi tepat guna" placeholder="Masukkan judul info teknologi tepat guna"
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
/> />
@@ -188,10 +193,9 @@ function EditInfoTeknologiTepatGuna() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
stateInfoTekno.edit.form.deskripsi = htmlContent; }
}}
/> />
</Box> </Box>

View File

@@ -27,14 +27,14 @@ function EditKolaborasiInovasi() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: kolaborasiState.update.form.name || "", name: "",
deskripsi: kolaborasiState.update.form.deskripsi || "", deskripsi: "",
tahun: kolaborasiState.update.form.tahun || "", tahun: "",
slug: kolaborasiState.update.form.slug || "", slug: "",
kolaborator: kolaborasiState.update.form.kolaborator || "", kolaborator: "",
}); });
// Load data // Load data awal dari server
useEffect(() => { useEffect(() => {
const loadKolaborasi = async () => { const loadKolaborasi = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -44,11 +44,11 @@ function EditKolaborasiInovasi() {
const data = await kolaborasiState.update.load(id); const data = await kolaborasiState.update.load(id);
if (data) { if (data) {
setFormData({ setFormData({
name: data.name || "", name: data.name ?? "",
deskripsi: data.deskripsi || "", deskripsi: data.deskripsi ?? "",
tahun: data.tahun || "", tahun: data.tahun?.toString() ?? "",
slug: data.slug || "", slug: data.slug ?? "",
kolaborator: data.kolaborator || "", kolaborator: data.kolaborator ?? "",
}); });
} }
} catch (error) { } catch (error) {
@@ -60,6 +60,7 @@ function EditKolaborasiInovasi() {
loadKolaborasi(); loadKolaborasi();
}, [params?.id]); }, [params?.id]);
// Handler submit → baru update global state
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
kolaborasiState.update.form = { kolaborasiState.update.form = {
@@ -70,6 +71,7 @@ function EditKolaborasiInovasi() {
slug: formData.slug, slug: formData.slug,
kolaborator: formData.kolaborator, kolaborator: formData.kolaborator,
}; };
await kolaborasiState.update.submit(); await kolaborasiState.update.submit();
toast.success("Kolaborasi inovasi berhasil diperbarui!"); toast.success("Kolaborasi inovasi berhasil diperbarui!");
router.push("/admin/inovasi/kolaborasi-inovasi"); router.push("/admin/inovasi/kolaborasi-inovasi");
@@ -79,6 +81,11 @@ function EditKolaborasiInovasi() {
} }
}; };
// Handler input (biar lebih DRY)
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
return ( return (
<Box px={{ base: "sm", md: "lg" }} py="md"> <Box px={{ base: "sm", md: "lg" }} py="md">
<Group mb="md"> <Group mb="md">
@@ -104,32 +111,32 @@ function EditKolaborasiInovasi() {
<TextInput <TextInput
label="Nama Kolaborasi" label="Nama Kolaborasi"
placeholder="Masukkan nama kolaborasi" placeholder="Masukkan nama kolaborasi"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange("name", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Deskripsi Singkat" label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat" placeholder="Masukkan deskripsi singkat"
defaultValue={formData.slug} value={formData.slug}
onChange={(e) => setFormData({ ...formData, slug: e.target.value })} onChange={(e) => handleChange("slug", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Tahun" label="Tahun"
placeholder="Masukkan tahun" placeholder="Masukkan tahun"
defaultValue={formData.tahun} value={formData.tahun}
onChange={(e) => setFormData({ ...formData, tahun: e.target.value })} onChange={(e) => handleChange("tahun", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Kolaborator" label="Kolaborator"
placeholder="Masukkan nama kolaborator" placeholder="Masukkan nama kolaborator"
defaultValue={formData.kolaborator} value={formData.kolaborator}
onChange={(e) => setFormData({ ...formData, kolaborator: e.target.value })} onChange={(e) => handleChange("kolaborator", e.target.value)}
required required
/> />
@@ -139,10 +146,7 @@ function EditKolaborasiInovasi() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) => handleChange("deskripsi", htmlContent)}
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
kolaborasiState.update.form.deskripsi = htmlContent;
}}
/> />
</Box> </Box>

View File

@@ -33,17 +33,22 @@ function EditMitraKolaborasi() {
const state = useProxy(mitraKolaborasi); const state = useProxy(mitraKolaborasi);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
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);
// Local form state (controlled)
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: state.update.form.name || '', name: '',
imageId: state.update.form.imageId || '', imageId: '',
}); });
// Load data ke state lokal sekali saja
useEffect(() => { useEffect(() => {
const loadFoto = 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 state.update.load(id); const data = await state.update.load(id);
if (data) { if (data) {
@@ -51,25 +56,31 @@ function EditMitraKolaborasi() {
name: data.name || '', name: data.name || '',
imageId: data.imageId || '', imageId: data.imageId || '',
}); });
if (data?.image?.link) { if (data?.image?.link) {
setPreviewImage(data.image.link); setPreviewImage(data.image.link);
} }
} }
} catch (error) { } catch (error) {
console.error('Error loading foto:', error); console.error('Error loading data:', error);
toast.error('Gagal memuat data foto'); toast.error('Gagal memuat data mitra');
} }
}; };
loadFoto();
loadData();
}, [params?.id]); }, [params?.id]);
const handleChange = (key: string, value: string) => {
setFormData((prev) => ({
...prev,
[key]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
state.update.form = { // upload file jika ada
...state.update.form, let imageId = formData.imageId;
name: formData.name,
imageId: formData.imageId,
};
if (file) { if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({
file, file,
@@ -79,8 +90,16 @@ function EditMitraKolaborasi() {
if (!uploaded?.id) { if (!uploaded?.id) {
return toast.error('Gagal upload gambar'); return toast.error('Gagal upload gambar');
} }
state.update.form.imageId = uploaded.id; imageId = uploaded.id;
} }
// update global state hanya saat submit
state.update.form = {
...state.update.form,
name: formData.name,
imageId,
};
await state.update.update(); await state.update.update();
toast.success('Mitra berhasil diperbarui!'); toast.success('Mitra berhasil diperbarui!');
router.push('/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi'); router.push('/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi');
@@ -118,8 +137,8 @@ function EditMitraKolaborasi() {
<TextInput <TextInput
label="Nama Mitra" label="Nama Mitra"
placeholder="Masukkan nama mitra" placeholder="Masukkan nama mitra"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange('name', e.target.value)}
required required
/> />

View File

@@ -11,24 +11,28 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditJenisLayanan() { function EditJenisLayanan() {
const state = useProxy(layananonlineDesa.jenisLayanan) const state = useProxy(layananonlineDesa.jenisLayanan);
const router = useRouter() const router = useRouter();
const params = useParams() const params = useParams();
const [formData, setFormData] = useState({
nama: state.edit.form.nama,
deskripsi: state.edit.form.deskripsi,
})
// state lokal untuk form
const [formData, setFormData] = useState({
nama: "",
deskripsi: "",
});
// load data dari backend ke local state
useEffect(() => { useEffect(() => {
const loadJenisLayanan = async () => { const loadJenisLayanan = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
try { try {
const data = await state.edit.load(id); const data = await state.edit.load(id);
if (data) { if (data) {
setFormData({ setFormData({
nama: data.nama, nama: data.nama ?? "",
deskripsi: data.deskripsi, deskripsi: data.deskripsi ?? "",
}); });
} }
} catch (error) { } catch (error) {
@@ -36,24 +40,26 @@ function EditJenisLayanan() {
toast.error("Gagal memuat data jenis layanan"); toast.error("Gagal memuat data jenis layanan");
} }
}; };
loadJenisLayanan(); loadJenisLayanan();
}, [params?.id]); }, [params?.id]);
// submit update → baru sync ke global state
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
state.edit.form = { state.edit.form = {
...state.edit.form, ...state.edit.form,
nama: formData.nama, ...formData,
deskripsi: formData.deskripsi, };
}
await state.edit.update() await state.edit.update();
toast.success("Jenis layanan berhasil diperbarui!") toast.success("Jenis layanan berhasil diperbarui!");
router.push("/admin/inovasi/layanan-online-desa/jenis-layanan") router.push("/admin/inovasi/layanan-online-desa/jenis-layanan");
} catch (error) { } catch (error) {
console.error("Error updating jenis layanan:", error); console.error("Error updating jenis layanan:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis layanan"); toast.error("Terjadi kesalahan saat memperbarui jenis layanan");
} }
} };
return ( return (
<Box> <Box>
@@ -62,27 +68,33 @@ function EditJenisLayanan() {
<IconArrowBack color={colors['blue-button']} size={25} /> <IconArrowBack color={colors['blue-button']} size={25} />
</Button> </Button>
</Box> </Box>
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}> <Paper bg={colors["white-1"]} p="md" w={{ base: "100%", md: "50%" }}>
<Stack gap="xs">
<Title order={3}>Edit Jenis Layanan</Title> <Title order={3}>Edit Jenis Layanan</Title>
<TextInput <TextInput
defaultValue={formData.nama} value={formData.nama}
onChange={(val) => { onChange={(e) =>
setFormData({ ...formData, nama: val.target.value }); setFormData((prev) => ({ ...prev, nama: e.target.value }))
}} }
label={<Text fz={"sm"} fw={"bold"}>Nama Jenis Layanan</Text>} label={<Text fz="sm" fw="bold">Nama Jenis Layanan</Text>}
placeholder="masukkan nama jenis layanan" placeholder="masukkan nama jenis layanan"
/> />
<Box> <Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text> <Text fz="sm" fw="bold">Deskripsi</Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) =>
setFormData({ ...formData, deskripsi: htmlContent }); setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
}} }
/> />
</Box> </Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan
</Button>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>

View File

@@ -10,7 +10,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';
@@ -25,9 +25,10 @@ function EditJenisPengaduan() {
const state = useProxy(layananonlineDesa.jenisPengaduan); const state = useProxy(layananonlineDesa.jenisPengaduan);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nama: "", nama: '',
}); });
// Load data sekali aja
useEffect(() => { useEffect(() => {
const loadJenisPengaduan = async () => { const loadJenisPengaduan = async () => {
if (!id) return; if (!id) return;
@@ -36,51 +37,58 @@ function EditJenisPengaduan() {
const data = await state.edit.load(id); const data = await state.edit.load(id);
if (data) { if (data) {
// pastikan id-nya masuk ke state edit state.edit.id = id; // inject id ke state global (hanya sekali)
state.edit.id = id;
setFormData({ setFormData({
nama: data.nama || '', nama: data.nama || '',
}); });
} }
} catch (error) { } catch (error) {
console.error("Error loading jenis pengaduan:", error); console.error('Error loading jenis pengaduan:', error);
toast.error("Gagal memuat data jenis pengaduan"); toast.error('Gagal memuat data jenis pengaduan');
} }
}; };
loadJenisPengaduan(); loadJenisPengaduan();
}, [id]); }, [id]);
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try {
if (!formData.nama.trim()) { if (!formData.nama.trim()) {
toast.error('Nama jenis pengaduan tidak boleh kosong'); toast.error('Nama jenis pengaduan tidak boleh kosong');
return; return;
} }
// Update ke global state HANYA pas submit
state.edit.form = { state.edit.form = {
nama: formData.nama.trim(), nama: formData.nama.trim(),
}; };
// Safety check tambahan: pastikan ID tidak kosong // Safety fallback kalau ID belum ada
if (!state.edit.id) { if (!state.edit.id) {
state.edit.id = id; // fallback state.edit.id = id;
} }
try {
const success = await state.edit.update(); const success = await state.edit.update();
if (success) { if (success) {
router.push("/admin/inovasi/layanan-online-desa/jenis-pengaduan"); router.push('/admin/inovasi/layanan-online-desa/jenis-pengaduan');
} }
} catch (error) { } catch (error) {
console.error("Error updating jenis pengaduan:", error); console.error('Error updating jenis pengaduan:', error);
// toast akan ditampilkan dari fungsi update // toast ditangani di dalam state.update
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header + tombol back */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow> <Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button <Button
@@ -97,7 +105,7 @@ function EditJenisPengaduan() {
</Title> </Title>
</Group> </Group>
{/* Card Form */} {/* Form */}
<Paper <Paper
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '50%' }}
bg={colors['white-1']} bg={colors['white-1']}
@@ -108,8 +116,8 @@ function EditJenisPengaduan() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} onChange={(e) => handleChange('nama', e.target.value)}
label="Nama Jenis Pengaduan" label="Nama Jenis Pengaduan"
placeholder="Masukkan nama jenis pengaduan" placeholder="Masukkan nama jenis pengaduan"
required required

View File

@@ -41,6 +41,7 @@ function EditProgramKreatifDesa() {
icon: '', icon: '',
}); });
// Load data hanya sekali berdasarkan params.id
useEffect(() => { useEffect(() => {
const loadProgramKreatif = async () => { const loadProgramKreatif = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -50,17 +51,11 @@ function EditProgramKreatifDesa() {
const data = await stateProgramKreatif.update.load(id); const data = await stateProgramKreatif.update.load(id);
if (data) { if (data) {
stateProgramKreatif.update.id = id; stateProgramKreatif.update.id = id;
stateProgramKreatif.update.form = {
name: data.name,
slug: data.slug,
deskripsi: data.deskripsi,
icon: data.icon,
};
setFormData({ setFormData({
name: data.name, name: data.name || '',
slug: data.slug, slug: data.slug || '',
deskripsi: data.deskripsi, deskripsi: data.deskripsi || '',
icon: data.icon, icon: data.icon || '',
}); });
} }
} catch (error) { } catch (error) {
@@ -72,10 +67,15 @@ function EditProgramKreatifDesa() {
loadProgramKreatif(); loadProgramKreatif();
}, [params?.id]); }, [params?.id]);
const handleChange =
(field: keyof FormProgramKreatif) =>
(value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateProgramKreatif.update.form = { stateProgramKreatif.update.form = {
...stateProgramKreatif.update.form,
name: formData.name.trim(), name: formData.name.trim(),
deskripsi: formData.deskripsi.trim(), deskripsi: formData.deskripsi.trim(),
slug: formData.slug.trim(), slug: formData.slug.trim(),
@@ -85,7 +85,7 @@ function EditProgramKreatifDesa() {
router.push('/admin/inovasi/program-kreatif-desa'); router.push('/admin/inovasi/program-kreatif-desa');
} catch (error) { } catch (error) {
console.error('Error updating program kreatif:', error); console.error('Error updating program kreatif:', error);
toast.error('Gagal memuat data program kreatif'); toast.error('Gagal menyimpan program kreatif');
} }
}; };
@@ -93,7 +93,12 @@ function EditProgramKreatifDesa() {
<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>
@@ -114,16 +119,16 @@ function EditProgramKreatifDesa() {
<TextInput <TextInput
label="Nama Program Kreatif Desa" label="Nama Program Kreatif Desa"
placeholder="Masukkan nama program kreatif desa" placeholder="Masukkan nama program kreatif desa"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange('name')(e.target.value)}
required required
/> />
<TextInput <TextInput
label="Deskripsi Singkat Program Kreatif Desa" label="Deskripsi Singkat Program Kreatif Desa"
placeholder="Masukkan deskripsi singkat program kreatif desa" placeholder="Masukkan deskripsi singkat program kreatif desa"
defaultValue={formData.slug} value={formData.slug}
onChange={(e) => setFormData({ ...formData, slug: e.target.value })} onChange={(e) => handleChange('slug')(e.target.value)}
required required
/> />
@@ -133,10 +138,7 @@ function EditProgramKreatifDesa() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={handleChange('deskripsi')}
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
stateProgramKreatif.update.form.deskripsi = htmlContent;
}}
/> />
</Box> </Box>
@@ -146,10 +148,7 @@ function EditProgramKreatifDesa() {
</Text> </Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={formData.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={handleChange('icon')}
setFormData((prev) => ({ ...prev, icon: value }));
stateProgramKreatif.update.form.icon = value;
}}
/> />
</Box> </Box>

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client"; "use client";
import { import {
@@ -40,12 +39,12 @@ function EditKeamananLingkungan() {
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: keamananState.edit.form.name || "", name: "",
deskripsi: keamananState.edit.form.deskripsi || "", deskripsi: "",
imageId: keamananState.edit.form.imageId || "", imageId: "",
}); });
// Load data by id // Load data sekali pas mount
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -71,16 +70,16 @@ function EditKeamananLingkungan() {
}; };
loadData(); loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params?.id]); }, [params?.id]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
keamananState.edit.form = { let imageId = formData.imageId;
...keamananState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
};
if (file) { if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({
@@ -93,9 +92,17 @@ function EditKeamananLingkungan() {
return toast.error("Gagal upload gambar"); return toast.error("Gagal upload gambar");
} }
keamananState.edit.form.imageId = uploaded.id; imageId = uploaded.id;
} }
// update global state hanya sekali pas submit
keamananState.edit.form = {
...keamananState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
imageId,
};
await keamananState.edit.update(); await keamananState.edit.update();
toast.success("Keamanan Lingkungan berhasil diperbarui!"); toast.success("Keamanan Lingkungan berhasil diperbarui!");
router.push("/admin/keamanan/keamanan-lingkungan-pecalang-patwal"); router.push("/admin/keamanan/keamanan-lingkungan-pecalang-patwal");
@@ -194,10 +201,8 @@ function EditKeamananLingkungan() {
)} )}
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
onChange={(e) => onChange={(e) => handleChange("name", e.target.value)}
setFormData({ ...formData, name: e.target.value })
}
label="Judul Keamanan Lingkungan" label="Judul Keamanan Lingkungan"
placeholder="Masukkan judul" placeholder="Masukkan judul"
required required
@@ -209,10 +214,7 @@ function EditKeamananLingkungan() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) => handleChange("deskripsi", htmlContent)}
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
keamananState.edit.form.deskripsi = htmlContent;
}}
/> />
</Box> </Box>

View File

@@ -27,11 +27,12 @@ function EditKontakItem() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: kontakState.update.form.nama || '', name: '',
nomorTelepon: kontakState.update.form.nomorTelepon || '', nomorTelepon: '',
icon: kontakState.update.form.icon || '', icon: '',
}); });
// Load data sekali dari global state
useEffect(() => { useEffect(() => {
const loadKontakDarurat = async () => { const loadKontakDarurat = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -55,14 +56,23 @@ function EditKontakItem() {
loadKontakDarurat(); loadKontakDarurat();
}, [params?.id]); }, [params?.id]);
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// Update global state sekali pas submit
kontakState.update.form = { kontakState.update.form = {
...kontakState.update.form, ...kontakState.update.form,
nama: formData.name, nama: formData.name,
nomorTelepon: formData.nomorTelepon, nomorTelepon: formData.nomorTelepon,
icon: formData.icon, icon: formData.icon,
}; };
await kontakState.update.update(); await kontakState.update.update();
toast.success('Kontak Darurat berhasil diperbarui!'); toast.success('Kontak Darurat berhasil diperbarui!');
router.push('/admin/keamanan/kontak-darurat/kontak-darurat-item'); router.push('/admin/keamanan/kontak-darurat/kontak-darurat-item');
@@ -99,16 +109,16 @@ function EditKontakItem() {
<TextInput <TextInput
label="Nama Kontak" label="Nama Kontak"
placeholder="Masukkan nama kontak" placeholder="Masukkan nama kontak"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange('name', e.target.value)}
required required
/> />
<TextInput <TextInput
label="Nomor Telepon" label="Nomor Telepon"
placeholder="Masukkan nomor telepon" placeholder="Masukkan nomor telepon"
defaultValue={formData.nomorTelepon} value={formData.nomorTelepon}
onChange={(e) => setFormData({ ...formData, nomorTelepon: e.target.value })} onChange={(e) => handleChange('nomorTelepon', e.target.value)}
required required
/> />
@@ -118,10 +128,7 @@ function EditKontakItem() {
</Text> </Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={formData.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={(value) => handleChange('icon', value)}
setFormData((prev) => ({ ...prev, icon: value }));
kontakState.update.form.icon = value;
}}
/> />
</Box> </Box>

View File

@@ -15,40 +15,40 @@ import {
Text, Text,
TextInput, TextInput,
Title, Title,
Tooltip Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import { import { IconArrowBack } from "@tabler/icons-react";
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";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { useProxy } from "valtio/utils"; import { useProxy } from "valtio/utils";
function EditKontakDaruratKeamanan() { function EditKontakDaruratKeamanan() {
const [isLoading, setIsLoading] = useState(true);
const router = useRouter(); const router = useRouter();
const kontakState = useProxy(kontakDarurat.kontakDaruratKeamananState);
const params = useParams(); const params = useParams();
const kontakState = useProxy(kontakDarurat.kontakDaruratKeamananState);
const [isLoading, setIsLoading] = useState(true);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: kontakState.update.form.nama || "", name: "",
icon: kontakState.update.form.icon || "", icon: "" as IconKey | "",
kategoriId: kontakState.update.form.kategoriId || [], kategoriId: [] as string[],
}); });
// Load data // Load data dari backend
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
await kontakDarurat.kontakDaruratItem.findMany.load(); await kontakDarurat.kontakDaruratItem.findMany.load();
const id = params?.id as string; const id = params?.id as string;
if (id) { if (id) {
const data = await kontakState.update.load(id); const data = await kontakState.update.load(id);
if (data) { if (data) {
setFormData({ setFormData({
name: data.nama || "", name: data.nama || "",
icon: data.icon || "", icon: (data.icon as IconKey) || "",
kategoriId: data.kategoriId || [], kategoriId: data.kategoriId || [],
}); });
} }
@@ -63,6 +63,7 @@ function EditKontakDaruratKeamanan() {
loadData(); loadData();
}, [params?.id]); }, [params?.id]);
// Handle submit // Handle submit
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
@@ -72,6 +73,7 @@ function EditKontakDaruratKeamanan() {
icon: formData.icon, icon: formData.icon,
kategoriId: formData.kategoriId, kategoriId: formData.kategoriId,
}; };
await kontakState.update.update(); await kontakState.update.update();
toast.success("Kontak Darurat berhasil diperbarui!"); toast.success("Kontak Darurat berhasil diperbarui!");
router.push("/admin/keamanan/kontak-darurat"); router.push("/admin/keamanan/kontak-darurat");
@@ -110,43 +112,54 @@ function EditKontakDaruratKeamanan() {
style={{ border: "1px solid #e0e0e0" }} style={{ border: "1px solid #e0e0e0" }}
> >
<Stack gap="md"> <Stack gap="md">
{/* Nama kategori */} {/* Nama Kontak */}
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) =>
setFormData((prev) => ({ ...prev, name: e.target.value }))
}
label="Nama Kontak Darurat" label="Nama Kontak Darurat"
placeholder="Masukkan nama kontak darurat" placeholder="Masukkan nama kontak darurat"
required required
/> />
{/* MultiSelect */}
<MultiSelect <MultiSelect
value={formData.kategoriId} value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val })} onChange={(val) =>
setFormData((prev) => ({ ...prev, kategoriId: val }))
}
label={<Text fw={"bold"} fz={"sm"}>Kontak Item</Text>} label={<Text fw={"bold"} fz={"sm"}>Kontak Item</Text>}
placeholder={isLoading ? "Memuat data..." : "Pilih kontak item"} placeholder={isLoading ? "Memuat data..." : "Pilih kontak item"}
data={ data={
Array.isArray(kontakDarurat.kontakDaruratItem.findMany.data) Array.isArray(kontakDarurat.kontakDaruratItem.findMany.data)
? kontakDarurat.kontakDaruratItem.findMany.data.map((v) => ({ ? kontakDarurat.kontakDaruratItem.findMany.data.map((v) => ({
value: v.id, value: v.id,
label: v.nama label: v.nama,
})) }))
: [] : []
} }
clearable clearable
searchable searchable
required required
error={!formData.kategoriId.length ? "Pilih minimal satu kategori" : undefined} error={
!formData.kategoriId.length
? "Pilih minimal satu kategori"
: undefined
}
disabled={isLoading} disabled={isLoading}
/> />
{/* Icon Select */}
<Box> <Box>
<Text fz={"sm"} fw={"bold"}>Ikon Program Kreatif Desa</Text> <Text fz={"sm"} fw={"bold"}>
Ikon Program Kreatif Desa
</Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={formData.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={(value) =>
setFormData((prev) => ({ ...prev, icon: value })); setFormData((prev) => ({ ...prev, icon: value }))
kontakState.update.form.icon = value; }
}}
/> />
</Box> </Box>

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik'; import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
@@ -30,13 +28,20 @@ function EditLaporanPublik() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState<{
judul: stateLaporan.edit.form.judul || '', judul: string;
lokasi: stateLaporan.edit.form.lokasi || '', lokasi: string;
tanggalWaktu: stateLaporan.edit.form.tanggalWaktu || '', tanggalWaktu: string;
status: stateLaporan.edit.form.status || '', status: Status;
penanganan: stateLaporan.edit.form.penanganan || '', penanganan: string;
kronologi: stateLaporan.edit.form.kronologi || '', kronologi: string;
}>({
judul: '',
lokasi: '',
tanggalWaktu: '',
status: 'Proses', // Default status
penanganan: '',
kronologi: '',
}); });
useEffect(() => { useEffect(() => {
@@ -52,7 +57,7 @@ function EditLaporanPublik() {
lokasi: data.lokasi || '', lokasi: data.lokasi || '',
tanggalWaktu: data.tanggalWaktu || '', tanggalWaktu: data.tanggalWaktu || '',
status: data.status || '', status: data.status || '',
penanganan: data.penanganan?.map((p: any) => p.deskripsi)[0] || '', penanganan: data.penanganan?.[0]?.deskripsi || '',
kronologi: data.kronologi || '', kronologi: data.kronologi || '',
}); });
} }
@@ -63,18 +68,17 @@ function EditLaporanPublik() {
}; };
loadLaporanPublik(); loadLaporanPublik();
}, [params?.id]); }, [params?.id, stateLaporan.edit]);
const handleChange = (field: string, value: string | Status) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateLaporan.edit.form = { stateLaporan.edit.form = {
...stateLaporan.edit.form, ...stateLaporan.edit.form,
judul: formData.judul, ...formData,
lokasi: formData.lokasi,
tanggalWaktu: formData.tanggalWaktu,
status: formData.status,
penanganan: formData.penanganan,
kronologi: formData.kronologi,
}; };
await stateLaporan.edit.update(); await stateLaporan.edit.update();
@@ -111,16 +115,16 @@ function EditLaporanPublik() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.judul} value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })} onChange={(e) => handleChange('judul', e.target.value)}
label={<Text fw="bold" fz="sm">Judul Laporan Publik</Text>} label={<Text fw="bold" fz="sm">Judul Laporan Publik</Text>}
placeholder="Masukkan judul laporan publik" placeholder="Masukkan judul laporan publik"
required required
/> />
<TextInput <TextInput
defaultValue={formData.lokasi} value={formData.lokasi}
onChange={(e) => setFormData({ ...formData, lokasi: e.target.value })} onChange={(e) => handleChange('lokasi', e.target.value)}
label={<Text fw="bold" fz="sm">Lokasi Laporan Publik</Text>} label={<Text fw="bold" fz="sm">Lokasi Laporan Publik</Text>}
placeholder="Masukkan lokasi laporan publik" placeholder="Masukkan lokasi laporan publik"
required required
@@ -129,15 +133,20 @@ function EditLaporanPublik() {
<DateTimePicker <DateTimePicker
label="Tanggal & Waktu Laporan Publik" label="Tanggal & Waktu Laporan Publik"
value={formData.tanggalWaktu ? new Date(formData.tanggalWaktu) : null} value={formData.tanggalWaktu ? new Date(formData.tanggalWaktu) : null}
onChange={(val) => onChange={(value: string | null) => {
setFormData({ ...formData, tanggalWaktu: val ? val.toString() : '' }) if (value) {
const date = new Date(value);
handleChange('tanggalWaktu', date.toISOString());
} else {
handleChange('tanggalWaktu', '');
} }
}}
required required
/> />
<Select <Select
value={formData.status} value={formData.status}
onChange={(e) => setFormData({ ...formData, status: e as Status })} onChange={(val) => handleChange('status', val as Status)}
label={<Text fw="bold" fz="sm">Status Laporan Publik</Text>} label={<Text fw="bold" fz="sm">Status Laporan Publik</Text>}
placeholder="Pilih status laporan publik" placeholder="Pilih status laporan publik"
data={[ data={[
@@ -152,10 +161,7 @@ function EditLaporanPublik() {
<Text fw="bold" fz="sm" mb={6}>Kronologi Laporan Publik</Text> <Text fw="bold" fz="sm" mb={6}>Kronologi Laporan Publik</Text>
<EditEditor <EditEditor
value={formData.kronologi} value={formData.kronologi}
onChange={(htmlContent) => { onChange={(htmlContent) => handleChange('kronologi', htmlContent)}
setFormData((prev) => ({ ...prev, kronologi: htmlContent }));
stateLaporan.edit.form.kronologi = htmlContent;
}}
/> />
</Box> </Box>
@@ -163,10 +169,7 @@ function EditLaporanPublik() {
<Text fw="bold" fz="sm" mb={6}>Penanganan Laporan Publik</Text> <Text fw="bold" fz="sm" mb={6}>Penanganan Laporan Publik</Text>
<EditEditor <EditEditor
value={formData.penanganan} value={formData.penanganan}
onChange={(htmlContent) => { onChange={(htmlContent) => handleChange('penanganan', htmlContent)}
setFormData((prev) => ({ ...prev, penanganan: htmlContent }));
stateLaporan.edit.form.penanganan = htmlContent;
}}
/> />
</Box> </Box>

View File

@@ -12,7 +12,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';
@@ -32,6 +32,7 @@ function EditPencegahanKriminalitas() {
linkVideo: '', linkVideo: '',
}); });
// load data hanya sekali pas id berubah
useEffect(() => { useEffect(() => {
const loadKriminalitas = async () => { const loadKriminalitas = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -41,10 +42,10 @@ function EditPencegahanKriminalitas() {
const data = await kriminalitasState.update.load(id); const data = await kriminalitasState.update.load(id);
if (data) { if (data) {
setFormData({ setFormData({
judul: data.judul || '', judul: data.judul ?? '',
deskripsi: data.deskripsi || '', deskripsi: data.deskripsi ?? '',
deskripsiSingkat: data.deskripsiSingkat || '', deskripsiSingkat: data.deskripsiSingkat ?? '',
linkVideo: data.linkVideo || '', linkVideo: data.linkVideo ?? '',
}); });
} }
} catch (error) { } catch (error) {
@@ -58,6 +59,12 @@ function EditPencegahanKriminalitas() {
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo); const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
const handleChange =
(field: keyof typeof formData) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({ ...prev, [field]: e.target.value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
const converted = convertYoutubeUrlToEmbed(formData.linkVideo); const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
if (!converted) { if (!converted) {
@@ -66,16 +73,13 @@ function EditPencegahanKriminalitas() {
} }
try { try {
// Update the form data first // update global state saat submit
kriminalitasState.update.form = { kriminalitasState.update.form = {
...kriminalitasState.update.form,
judul: formData.judul, judul: formData.judul,
deskripsi: formData.deskripsi, deskripsi: formData.deskripsi,
deskripsiSingkat: formData.deskripsiSingkat, deskripsiSingkat: formData.deskripsiSingkat,
linkVideo: formData.linkVideo, linkVideo: formData.linkVideo,
}; };
// Set the ID and then call update
kriminalitasState.update.id = params?.id as string; kriminalitasState.update.id = params?.id as string;
await kriminalitasState.update.update(); await kriminalitasState.update.update();
@@ -119,18 +123,16 @@ function EditPencegahanKriminalitas() {
<TextInput <TextInput
label="Judul" label="Judul"
placeholder="Masukkan judul" placeholder="Masukkan judul"
defaultValue={formData.judul} value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })} onChange={handleChange('judul')}
required required
/> />
<TextInput <TextInput
label="Deskripsi Singkat" label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat" placeholder="Masukkan deskripsi singkat"
defaultValue={formData.deskripsiSingkat} value={formData.deskripsiSingkat}
onChange={(e) => onChange={handleChange('deskripsiSingkat')}
setFormData({ ...formData, deskripsiSingkat: e.target.value })
}
required required
/> />
@@ -141,7 +143,7 @@ function EditPencegahanKriminalitas() {
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => onChange={(val) =>
setFormData({ ...formData, deskripsi: val }) setFormData((prev) => ({ ...prev, deskripsi: val }))
} }
/> />
</Box> </Box>
@@ -150,10 +152,8 @@ function EditPencegahanKriminalitas() {
<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) => onChange={handleChange('linkVideo')}
setFormData({ ...formData, linkVideo: e.currentTarget.value })
}
required required
/> />
{embedLink && ( {embedLink && (

View File

@@ -52,6 +52,7 @@ function EditPolsekTerdekat() {
layananPolsekId: "", layananPolsekId: "",
}); });
// load data untuk form edit
useEffect(() => { useEffect(() => {
const loadPolsekTerdekat = async () => { const loadPolsekTerdekat = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -82,21 +83,6 @@ function EditPolsekTerdekat() {
loadPolsekTerdekat(); loadPolsekTerdekat();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => {
try {
polsekState.edit.form = {
...polsekState.edit.form,
...formData,
};
await polsekState.edit.update();
toast.success("Polsek terdekat berhasil diperbarui!");
router.push("/admin/keamanan/polsek-terdekat");
} catch (error) {
console.error("Error updating polsek terdekat:", error);
toast.error("Gagal memperbarui data polsek terdekat");
}
};
const fetchLayanan = async () => { const fetchLayanan = async () => {
try { try {
const res = await fetch("/api/keamanan/layanan-polsek/find-many"); const res = await fetch("/api/keamanan/layanan-polsek/find-many");
@@ -198,6 +184,22 @@ function EditPolsekTerdekat() {
fetchLayanan(); fetchLayanan();
}, []); }, []);
const handleChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
try {
polsekState.edit.form = { ...formData }; // update global state hanya di sini
await polsekState.edit.update();
toast.success("Polsek terdekat berhasil diperbarui!");
router.push("/admin/keamanan/polsek-terdekat");
} catch (error) {
console.error("Error updating polsek terdekat:", error);
toast.error("Gagal memperbarui data polsek terdekat");
}
};
return ( return (
<Box px={{ base: "sm", md: "lg" }} py="md"> <Box px={{ base: "sm", md: "lg" }} py="md">
{/* Modal Tambah */} {/* Modal Tambah */}
@@ -273,88 +275,59 @@ function EditPolsekTerdekat() {
<Stack gap="md"> <Stack gap="md">
{/* Input fields */} {/* Input fields */}
<TextInput <TextInput
defaultValue={formData.nama} value={formData.nama}
onChange={(val) => onChange={(e) => handleChange("nama", e.currentTarget.value)}
setFormData({ ...formData, nama: val.target.value })
}
label="Nama Polsek Terdekat" label="Nama Polsek Terdekat"
placeholder="Masukkan nama Polsek Terdekat" placeholder="Masukkan nama Polsek Terdekat"
required required
/> />
<TextInput <TextInput
defaultValue={formData.jarakKeDesa} value={formData.jarakKeDesa}
onChange={(val) => onChange={(e) => handleChange("jarakKeDesa", e.currentTarget.value)}
setFormData({ ...formData, jarakKeDesa: val.target.value })
}
label="Jarak Polsek Terdekat" label="Jarak Polsek Terdekat"
placeholder="Masukkan jarak Polsek Terdekat"
/> />
<TextInput <TextInput
defaultValue={formData.alamat} value={formData.alamat}
onChange={(val) => onChange={(e) => handleChange("alamat", e.currentTarget.value)}
setFormData({ ...formData, alamat: val.target.value })
}
label="Alamat Polsek Terdekat" label="Alamat Polsek Terdekat"
placeholder="Masukkan alamat Polsek Terdekat"
/> />
<TextInput <TextInput
defaultValue={formData.nomorTelepon} value={formData.nomorTelepon}
onChange={(val) => onChange={(e) => handleChange("nomorTelepon", e.currentTarget.value)}
setFormData({ ...formData, nomorTelepon: val.target.value })
}
label="Nomor Telepon" label="Nomor Telepon"
placeholder="Masukkan nomor telepon Polsek Terdekat"
/> />
<TextInput <TextInput
defaultValue={formData.jamOperasional} value={formData.jamOperasional}
onChange={(val) => onChange={(e) => handleChange("jamOperasional", e.currentTarget.value)}
setFormData({ ...formData, jamOperasional: val.target.value })
}
label="Jam Operasional" label="Jam Operasional"
placeholder="Masukkan jam operasional Polsek Terdekat"
/> />
<TextInput <TextInput
defaultValue={formData.embedMapUrl} value={formData.embedMapUrl}
onChange={(val) => onChange={(e) => handleChange("embedMapUrl", e.currentTarget.value)}
setFormData({ ...formData, embedMapUrl: val.target.value })
}
label="Embed Map URL" label="Embed Map URL"
placeholder="Masukkan embed map url"
/> />
<TextInput <TextInput
defaultValue={formData.namaTempatMaps} value={formData.namaTempatMaps}
onChange={(val) => onChange={(e) => handleChange("namaTempatMaps", e.currentTarget.value)}
setFormData({ ...formData, namaTempatMaps: val.target.value })
}
label="Nama Tempat Maps" label="Nama Tempat Maps"
placeholder="Masukkan nama tempat di maps"
/> />
<TextInput <TextInput
defaultValue={formData.alamatMaps} value={formData.alamatMaps}
onChange={(val) => onChange={(e) => handleChange("alamatMaps", e.currentTarget.value)}
setFormData({ ...formData, alamatMaps: val.target.value })
}
label="Alamat Maps" label="Alamat Maps"
placeholder="Masukkan alamat di maps"
/> />
<TextInput <TextInput
defaultValue={formData.linkPetunjukArah} value={formData.linkPetunjukArah}
onChange={(val) => onChange={(e) => handleChange("linkPetunjukArah", e.currentTarget.value)}
setFormData({ ...formData, linkPetunjukArah: val.target.value })
}
label="Link Petunjuk Arah" label="Link Petunjuk Arah"
placeholder="Masukkan link petunjuk arah"
/> />
{/* Dropdown Layanan */}
<Select <Select
label="Layanan Polsek" label="Layanan Polsek"
placeholder="Pilih layanan polsek" placeholder="Pilih layanan polsek"
data={layananOptions} data={layananOptions}
value={polsekState.create.form.layananPolsekId} value={formData.layananPolsekId}
onChange={(val) => { onChange={(val) => handleChange("layananPolsekId", val || "")}
polsekState.create.form.layananPolsekId = val || "";
}}
/> />
<Button <Button
variant="light" variant="light"

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client"; "use client";
import { import {
@@ -39,9 +38,9 @@ function EditTipsKeamanan() {
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: keamananState.update.form.judul || "", judul: "",
deskripsi: keamananState.update.form.deskripsi || "", deskripsi: "",
imageId: keamananState.update.form.imageId || "", imageId: "",
}); });
// Load data saat pertama kali // Load data saat pertama kali
@@ -70,14 +69,12 @@ function EditTipsKeamanan() {
}; };
loadData(); loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
keamananState.update.form = { let imageId = formData.imageId;
...keamananState.update.form,
...formData,
};
if (file) { if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({
@@ -87,10 +84,14 @@ function EditTipsKeamanan() {
const uploaded = res.data?.data; const uploaded = res.data?.data;
if (!uploaded?.id) return toast.error("Gagal upload gambar"); if (!uploaded?.id) return toast.error("Gagal upload gambar");
imageId = uploaded.id;
keamananState.update.form.imageId = uploaded.id;
} }
keamananState.update.form = {
...formData,
imageId,
};
await keamananState.update.update(); await keamananState.update.update();
toast.success("Tips Keamanan berhasil diperbarui!"); toast.success("Tips Keamanan berhasil diperbarui!");
router.push("/admin/keamanan/tips-keamanan"); router.push("/admin/keamanan/tips-keamanan");
@@ -105,7 +106,12 @@ function EditTipsKeamanan() {
{/* Header */} {/* Header */}
<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>
@@ -137,7 +143,9 @@ function EditTipsKeamanan() {
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"
@@ -145,7 +153,11 @@ function EditTipsKeamanan() {
> >
<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} />
@@ -199,8 +211,10 @@ function EditTipsKeamanan() {
<TextInput <TextInput
label="Nama Tips Keamanan" label="Nama Tips Keamanan"
placeholder="Masukkan nama tips keamanan" placeholder="Masukkan nama tips keamanan"
defaultValue={formData.judul} value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })} onChange={(e) =>
setFormData((prev) => ({ ...prev, judul: e.target.value }))
}
required required
/> />
@@ -211,10 +225,9 @@ function EditTipsKeamanan() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
keamananState.update.form.deskripsi = htmlContent; }
}}
/> />
</Box> </Box>

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
@@ -15,7 +15,7 @@ import {
Text, Text,
TextInput, TextInput,
Title, Title,
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';
@@ -44,32 +44,18 @@ function EditArtikelKesehatan() {
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<ArtikelKesehatanFormBase>({ const [formData, setFormData] = useState<ArtikelKesehatanFormBase>({
title: stateArtikelKesehatan.edit.form.title, title: '',
content: stateArtikelKesehatan.edit.form.content, content: '',
imageId: stateArtikelKesehatan.edit.form.imageId, imageId: '',
introduction: { content: stateArtikelKesehatan.edit.form.introduction?.content }, introduction: { content: '' },
symptom: { symptom: { title: '', content: '' },
title: stateArtikelKesehatan.edit.form.symptom?.title, prevention: { title: '', content: '' },
content: stateArtikelKesehatan.edit.form.symptom?.content firstAid: { title: '', content: '' },
}, mythVsFact: { title: '', mitos: '', fakta: '' },
prevention: { doctorSign: { content: '' },
title: stateArtikelKesehatan.edit.form.prevention?.title,
content: stateArtikelKesehatan.edit.form.prevention?.content
},
firstAid: {
title: stateArtikelKesehatan.edit.form.firstAid?.title,
content: stateArtikelKesehatan.edit.form.firstAid?.content
},
mythVsFact: {
title: stateArtikelKesehatan.edit.form.mythVsFact?.title,
mitos: stateArtikelKesehatan.edit.form.mythVsFact?.mitos,
fakta: stateArtikelKesehatan.edit.form.mythVsFact?.fakta
},
doctorSign: {
content: stateArtikelKesehatan.edit.form.doctorSign?.content
}
}); });
// Load data artikel
useEffect(() => { useEffect(() => {
const loadArtikelKesehatan = async () => { const loadArtikelKesehatan = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -78,74 +64,89 @@ function EditArtikelKesehatan() {
try { try {
await stateArtikelKesehatan.edit.load(id); await stateArtikelKesehatan.edit.load(id);
const { form } = stateArtikelKesehatan.edit; const { form } = stateArtikelKesehatan.edit;
if (form) { if (!form) return;
setFormData({ setFormData({
title: form.title, title: form.title || '',
content: form.content, content: form.content || '',
imageId: form.imageId || '',
introduction: { content: form.introduction?.content || '' }, introduction: { content: form.introduction?.content || '' },
imageId: form.imageId, symptom: { title: form.symptom?.title || '', content: form.symptom?.content || '' },
symptom: { prevention: { title: form.prevention?.title || '', content: form.prevention?.content || '' },
title: form.symptom?.title || '', firstAid: { title: form.firstAid?.title || '', content: form.firstAid?.content || '' },
content: form.symptom?.content || ''
},
prevention: {
title: form.prevention?.title || '',
content: form.prevention?.content || ''
},
firstAid: {
title: form.firstAid?.title || '',
content: form.firstAid?.content || ''
},
mythVsFact: { mythVsFact: {
title: form.mythVsFact?.title || '', title: form.mythVsFact?.title || '',
mitos: form.mythVsFact?.mitos || '', mitos: form.mythVsFact?.mitos || '',
fakta: form.mythVsFact?.fakta || '' fakta: form.mythVsFact?.fakta || '',
}, },
doctorSign: { doctorSign: { content: form.doctorSign?.content || '' },
content: form.doctorSign?.content || ''
}
}); });
if (form?.imageId) { if (form.imageId) {
setPreviewImage(`${process.env.NEXT_PUBLIC_API_URL}/file/${form.imageId}`); setPreviewImage(`${process.env.NEXT_PUBLIC_API_URL}/file/${form.imageId}`);
} }
}
} catch (error) { } catch (error) {
console.error("Error loading artikel kesehatan:", error); console.error('Error loading artikel kesehatan:', error);
toast.error("Gagal memuat data artikel kesehatan"); toast.error('Gagal memuat data artikel kesehatan');
} }
}; };
loadArtikelKesehatan(); loadArtikelKesehatan();
}, [params?.id]); }, [params?.id]);
const handleFileChange = (files: File[]) => {
const selectedFile = files[0];
if (!selectedFile) return;
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// Copy formData ke global state
stateArtikelKesehatan.edit.form = { ...formData }; stateArtikelKesehatan.edit.form = { ...formData };
// Upload gambar kalau ada
if (file) { if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
file,
name: file.name,
});
const uploaded = res.data?.data; const uploaded = res.data?.data;
if (!uploaded?.id) return toast.error('Gagal upload gambar');
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
stateArtikelKesehatan.edit.form.imageId = uploaded.id; stateArtikelKesehatan.edit.form.imageId = uploaded.id;
} }
const success = await stateArtikelKesehatan.edit.submit(); const success = await stateArtikelKesehatan.edit.submit();
if (success) { if (success) {
toast.success("Artikel kesehatan berhasil diperbarui!"); toast.success('Artikel kesehatan berhasil diperbarui!');
router.push("/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan"); router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan');
} }
} catch (error) { } catch (error) {
console.error("Error updating artikel kesehatan:", error); console.error('Error updating artikel kesehatan:', error);
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data artikel kesehatan"); toast.error(error instanceof Error ? error.message : 'Gagal memperbarui data artikel kesehatan');
} }
}; };
const InputText = ({
label,
value,
onChange,
placeholder,
required,
}: {
label: string;
value: string;
onChange: (v: string) => void;
placeholder?: string;
required?: boolean;
}) => (
<TextInput
label={label}
value={value}
placeholder={placeholder}
onChange={(e) => onChange(e.target.value)}
required={required}
/>
);
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
@@ -170,34 +171,31 @@ function EditArtikelKesehatan() {
style={{ border: '1px solid #e0e0e0' }} style={{ border: '1px solid #e0e0e0' }}
> >
<Stack gap="md"> <Stack gap="md">
<TextInput {/* Judul */}
<InputText
label="Judul" label="Judul"
value={formData.title}
onChange={(value) => setFormData((prev) => ({ ...prev, title: value }))}
placeholder="Masukkan judul artikel" placeholder="Masukkan judul artikel"
defaultValue={formData.title}
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
required required
/> />
{/* Gambar */}
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Gambar Berita Gambar Berita
</Text> </Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={handleFileChange}
const selectedFile = files[0]; onReject={() => toast.error('File tidak valid, gunakan format gambar')}
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
}
}}
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"
p="xl" p="xl"
> >
<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} />
@@ -215,64 +213,57 @@ function EditArtikelKesehatan() {
</Stack> </Stack>
</Group> </Group>
</Dropzone> </Dropzone>
{previewImage && ( {previewImage && (
<Box mt="sm" style={{ display: "flex", justifyContent: "center" }}> <Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
<Image <Image
src={previewImage} src={previewImage}
alt="Preview Gambar" alt="Preview Gambar"
radius="md" radius="md"
style={{ style={{
maxHeight: 220, maxHeight: 220,
objectFit: "contain", objectFit: 'contain',
border: `1px solid ${colors["blue-button"]}`, border: `1px solid ${colors['blue-button']}`,
}} }}
loading="lazy" loading="lazy"
/> />
</Box> </Box>
)} )}
</Box> </Box>
<TextInput
{/* Konten */}
<InputText
label="Deskripsi" label="Deskripsi"
value={formData.content}
onChange={(value) => setFormData((prev) => ({ ...prev, content: value }))}
placeholder="Masukkan deskripsi artikel" placeholder="Masukkan deskripsi artikel"
defaultValue={formData.content}
onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
required required
/> />
<TextInput
{/* Pendahuluan */}
<InputText
label="Pendahuluan" label="Pendahuluan"
placeholder="Masukkan pendahuluan" value={formData.introduction.content}
defaultValue={formData.introduction.content} onChange={(value) =>
onChange={(e) => setFormData((prev) => ({ ...prev, introduction: { content: value } }))
setFormData(prev => ({
...prev,
introduction: { ...prev.introduction, content: e.target.value }
}))
} }
placeholder="Masukkan pendahuluan"
/> />
{/* Gejala */} {/* Gejala */}
<Box> <Box>
<Text fw="bold">Gejala</Text> <Text fw="bold">Gejala</Text>
<Stack gap="xs"> <Stack gap="xs">
<TextInput <InputText
label="Judul Gejala" label="Judul Gejala"
placeholder="Masukkan judul gejala" value={formData.symptom.title}
defaultValue={formData.symptom.title} onChange={(value) =>
onChange={(e) => setFormData((prev) => ({ ...prev, symptom: { ...prev.symptom, title: value } }))
setFormData(prev => ({
...prev,
symptom: { ...prev.symptom, title: e.target.value }
}))
} }
/> />
<EditEditor <EditEditor
value={formData.symptom.content} value={formData.symptom.content}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, symptom: { ...prev.symptom, content: value } }))
...prev,
symptom: { ...prev.symptom, content: e }
}))
} }
/> />
</Stack> </Stack>
@@ -281,23 +272,17 @@ function EditArtikelKesehatan() {
{/* Pencegahan */} {/* Pencegahan */}
<Box> <Box>
<Text fw="bold">Pencegahan</Text> <Text fw="bold">Pencegahan</Text>
<TextInput <InputText
label="Judul" label="Judul"
defaultValue={formData.prevention.title} value={formData.prevention.title}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, prevention: { ...prev.prevention, title: value } }))
...prev,
prevention: { ...prev.prevention, title: e.target.value }
}))
} }
/> />
<EditEditor <EditEditor
value={formData.prevention.content} value={formData.prevention.content}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, prevention: { ...prev.prevention, content: value } }))
...prev,
prevention: { ...prev.prevention, content: e }
}))
} }
/> />
</Box> </Box>
@@ -305,23 +290,17 @@ function EditArtikelKesehatan() {
{/* Pertolongan Pertama */} {/* Pertolongan Pertama */}
<Box> <Box>
<Text fw="bold">Pertolongan Pertama</Text> <Text fw="bold">Pertolongan Pertama</Text>
<TextInput <InputText
label="Judul" label="Judul"
defaultValue={formData.firstAid.title} value={formData.firstAid.title}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, firstAid: { ...prev.firstAid, title: value } }))
...prev,
firstAid: { ...prev.firstAid, title: e.target.value }
}))
} }
/> />
<EditEditor <EditEditor
value={formData.firstAid.content} value={formData.firstAid.content}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, firstAid: { ...prev.firstAid, content: value } }))
...prev,
firstAid: { ...prev.firstAid, content: e }
}))
} }
/> />
</Box> </Box>
@@ -329,48 +308,36 @@ function EditArtikelKesehatan() {
{/* Mitos vs Fakta */} {/* Mitos vs Fakta */}
<Box> <Box>
<Text fw="bold">Mitos vs Fakta</Text> <Text fw="bold">Mitos vs Fakta</Text>
<TextInput <InputText
label="Judul" label="Judul"
defaultValue={formData.mythVsFact.title} value={formData.mythVsFact.title}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, mythVsFact: { ...prev.mythVsFact, title: value } }))
...prev,
mythVsFact: { ...prev.mythVsFact, title: e.target.value }
}))
} }
/> />
<Text fw="500">Mitos</Text> <Text fw="500">Mitos</Text>
<EditEditor <EditEditor
value={formData.mythVsFact.mitos} value={formData.mythVsFact.mitos}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, mythVsFact: { ...prev.mythVsFact, mitos: value } }))
...prev,
mythVsFact: { ...prev.mythVsFact, mitos: e }
}))
} }
/> />
<Text fw="500">Fakta</Text> <Text fw="500">Fakta</Text>
<EditEditor <EditEditor
value={formData.mythVsFact.fakta} value={formData.mythVsFact.fakta}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, mythVsFact: { ...prev.mythVsFact, fakta: value } }))
...prev,
mythVsFact: { ...prev.mythVsFact, fakta: e }
}))
} }
/> />
</Box> </Box>
{/* Kapan harus ke dokter */} {/* Dokter */}
<Box> <Box>
<Text fw="bold">Kapan Harus Ke Dokter</Text> <Text fw="bold">Kapan Harus Ke Dokter</Text>
<EditEditor <EditEditor
value={formData.doctorSign.content} value={formData.doctorSign.content}
onChange={(e) => onChange={(value) =>
setFormData(prev => ({ setFormData((prev) => ({ ...prev, doctorSign: { content: value } }))
...prev,
doctorSign: { ...prev.doctorSign, content: e }
}))
} }
/> />
</Box> </Box>
@@ -384,7 +351,7 @@ function EditArtikelKesehatan() {
style={{ style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`, background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff', color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)' boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}} }}
> >
Simpan Simpan

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
@@ -43,89 +43,64 @@ interface FasilitasKesehatanFormBase {
} }
function EditFasilitasKesehatan() { function EditFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan); const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState<FasilitasKesehatanFormBase>({ const [formData, setFormData] = useState<FasilitasKesehatanFormBase>({
name: stateFasilitasKesehatan.edit.form.name || '', name: '',
informasiUmum: { informasiUmum: { fasilitas: '', alamat: '', jamOperasional: '' },
fasilitas: stateFasilitasKesehatan.edit.form.informasiUmum?.fasilitas || '', layananUnggulan: { content: '' },
alamat: stateFasilitasKesehatan.edit.form.informasiUmum?.alamat || '', dokterdanTenagaMedis: { name: '', specialist: '', jadwal: '' },
jamOperasional: stateFasilitasKesehatan.edit.form.informasiUmum?.jamOperasional || '', fasilitasPendukung: { content: '' },
}, prosedurPendaftaran: { content: '' },
layananUnggulan: { tarifDanLayanan: { layanan: '', tarif: '' },
content: stateFasilitasKesehatan.edit.form.layananUnggulan?.content || '',
},
dokterdanTenagaMedis: {
name: stateFasilitasKesehatan.edit.form.dokterdanTenagaMedis?.name || '',
specialist: stateFasilitasKesehatan.edit.form.dokterdanTenagaMedis?.specialist || '',
jadwal: stateFasilitasKesehatan.edit.form.dokterdanTenagaMedis?.jadwal || '',
},
fasilitasPendukung: {
content: stateFasilitasKesehatan.edit.form.fasilitasPendukung?.content || '',
},
prosedurPendaftaran: {
content: stateFasilitasKesehatan.edit.form.prosedurPendaftaran?.content || '',
},
tarifDanLayanan: {
layanan: stateFasilitasKesehatan.edit.form.tarifDanLayanan?.layanan || '',
tarif: stateFasilitasKesehatan.edit.form.tarifDanLayanan?.tarif || '',
},
}); });
// Helper untuk update nested state
const updateForm = <K extends keyof FasilitasKesehatanFormBase>(
key: K,
value: FasilitasKesehatanFormBase[K]
) => setFormData(prev => ({ ...prev, [key]: value }));
const updateNested = <
K extends keyof FasilitasKesehatanFormBase,
N extends keyof FasilitasKesehatanFormBase[K]
>(key: K, nestedKey: N, value: FasilitasKesehatanFormBase[K][N]) =>
setFormData(prev => ({
...prev,
[key]: { ...prev[key] as object, [nestedKey]: value },
}));
// Load data
useEffect(() => { useEffect(() => {
const loadFasilitasKesehatan = async () => { const load = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
try { try {
await stateFasilitasKesehatan.edit.load(id); await state.edit.load(id);
const { form } = stateFasilitasKesehatan.edit; const form = state.edit.form;
if (form) { if (form) setFormData(form as FasilitasKesehatanFormBase);
setFormData({ } catch (err) {
name: form.name, console.error(err);
informasiUmum: { toast.error('Gagal memuat data fasilitas kesehatan');
fasilitas: form.informasiUmum?.fasilitas || '',
alamat: form.informasiUmum?.alamat || '',
jamOperasional: form.informasiUmum?.jamOperasional || '',
},
layananUnggulan: { content: form.layananUnggulan?.content || '' },
dokterdanTenagaMedis: {
name: form.dokterdanTenagaMedis?.name || '',
specialist: form.dokterdanTenagaMedis?.specialist || '',
jadwal: form.dokterdanTenagaMedis?.jadwal || '',
},
fasilitasPendukung: { content: form.fasilitasPendukung?.content || '' },
prosedurPendaftaran: { content: form.prosedurPendaftaran?.content || '' },
tarifDanLayanan: {
layanan: form.tarifDanLayanan?.layanan || '',
tarif: form.tarifDanLayanan?.tarif || '',
},
});
}
} catch (error) {
console.error("Error loading fasilitas kesehatan:", error);
toast.error("Gagal memuat data fasilitas kesehatan");
} }
}; };
loadFasilitasKesehatan(); load();
}, [params?.id]); }, [params?.id]);
// Submit
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateFasilitasKesehatan.edit.form = { state.edit.form = { ...state.edit.form, ...formData };
...stateFasilitasKesehatan.edit.form, const success = await state.edit.submit();
...formData,
};
const success = await stateFasilitasKesehatan.edit.submit();
if (success) { if (success) {
toast.success("Fasilitas kesehatan berhasil diperbarui!"); toast.success('Fasilitas kesehatan berhasil diperbarui!');
router.push("/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan"); router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan');
} }
} catch (error) { } catch (err) {
console.error("Error updating fasilitas kesehatan:", error); console.error(err);
toast.error("Terjadi kesalahan saat memperbarui data fasilitas kesehatan"); toast.error('Terjadi kesalahan saat memperbarui data fasilitas kesehatan');
} }
}; };
@@ -156,145 +131,104 @@ function EditFasilitasKesehatan() {
<TextInput <TextInput
label="Nama Fasilitas Kesehatan" label="Nama Fasilitas Kesehatan"
placeholder="Masukkan nama fasilitas kesehatan" placeholder="Masukkan nama fasilitas kesehatan"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))} onChange={(e) => updateForm('name', e.target.value)}
required required
/> />
{/* Informasi Umum */} {/* Informasi Umum */}
<Box> <Box>
<Text fw="bold" mb={5}>Informasi Umum</Text> <Text fw="bold" mb={5}>
Informasi Umum
</Text>
<TextInput <TextInput
label="Fasilitas" label="Fasilitas"
defaultValue={formData.informasiUmum.fasilitas} value={formData.informasiUmum.fasilitas}
onChange={(e) => onChange={(e) => updateNested('informasiUmum', 'fasilitas', e.target.value)}
setFormData(prev => ({
...prev,
informasiUmum: { ...prev.informasiUmum, fasilitas: e.target.value },
}))
}
/> />
<TextInput <TextInput
label="Alamat" label="Alamat"
defaultValue={formData.informasiUmum.alamat} value={formData.informasiUmum.alamat}
onChange={(e) => onChange={(e) => updateNested('informasiUmum', 'alamat', e.target.value)}
setFormData(prev => ({
...prev,
informasiUmum: { ...prev.informasiUmum, alamat: e.target.value },
}))
}
/> />
<TextInput <TextInput
label="Jam Operasional" label="Jam Operasional"
defaultValue={formData.informasiUmum.jamOperasional} value={formData.informasiUmum.jamOperasional}
onChange={(e) => onChange={(e) => updateNested('informasiUmum', 'jamOperasional', e.target.value)}
setFormData(prev => ({
...prev,
informasiUmum: { ...prev.informasiUmum, jamOperasional: e.target.value },
}))
}
/> />
</Box> </Box>
{/* Layanan Unggulan */} {/* Layanan Unggulan */}
<Box> <Box>
<Text fw="bold" mb={5}>Layanan Unggulan</Text> <Text fw="bold" mb={5}>
Layanan Unggulan
</Text>
<EditEditor <EditEditor
value={formData.layananUnggulan.content} value={formData.layananUnggulan.content}
onChange={(e) => onChange={(v) => updateNested('layananUnggulan', 'content', v)}
setFormData(prev => ({
...prev,
layananUnggulan: { content: e },
}))
}
/> />
</Box> </Box>
{/* Dokter dan Tenaga Medis */} {/* Dokter dan Tenaga Medis */}
<Box> <Box>
<Text fw="bold" mb={5}>Dokter dan Tenaga Medis</Text> <Text fw="bold" mb={5}>
Dokter dan Tenaga Medis
</Text>
<TextInput <TextInput
label="Nama Dokter" label="Nama Dokter"
defaultValue={formData.dokterdanTenagaMedis.name} value={formData.dokterdanTenagaMedis.name}
onChange={(e) => onChange={(e) => updateNested('dokterdanTenagaMedis', 'name', e.target.value)}
setFormData(prev => ({
...prev,
dokterdanTenagaMedis: { ...prev.dokterdanTenagaMedis, name: e.target.value },
}))
}
/> />
<TextInput <TextInput
label="Specialist" label="Specialist"
defaultValue={formData.dokterdanTenagaMedis.specialist} value={formData.dokterdanTenagaMedis.specialist}
onChange={(e) => onChange={(e) =>
setFormData(prev => ({ updateNested('dokterdanTenagaMedis', 'specialist', e.target.value)
...prev,
dokterdanTenagaMedis: { ...prev.dokterdanTenagaMedis, specialist: e.target.value },
}))
} }
/> />
<TextInput <TextInput
label="Jadwal" label="Jadwal"
defaultValue={formData.dokterdanTenagaMedis.jadwal} value={formData.dokterdanTenagaMedis.jadwal}
onChange={(e) => onChange={(e) => updateNested('dokterdanTenagaMedis', 'jadwal', e.target.value)}
setFormData(prev => ({
...prev,
dokterdanTenagaMedis: { ...prev.dokterdanTenagaMedis, jadwal: e.target.value },
}))
}
/> />
</Box> </Box>
{/* Fasilitas Pendukung */} {/* Fasilitas Pendukung */}
<Box> <Box>
<Text fw="bold" mb={5}>Fasilitas Pendukung</Text> <Text fw="bold" mb={5}>
Fasilitas Pendukung
</Text>
<EditEditor <EditEditor
value={formData.fasilitasPendukung.content} value={formData.fasilitasPendukung.content}
onChange={(e) => onChange={(v) => updateNested('fasilitasPendukung', 'content', v)}
setFormData(prev => ({
...prev,
fasilitasPendukung: { content: e },
}))
}
/> />
</Box> </Box>
{/* Prosedur Pendaftaran */} {/* Prosedur Pendaftaran */}
<Box> <Box>
<Text fw="bold" mb={5}>Prosedur Pendaftaran</Text> <Text fw="bold" mb={5}>
Prosedur Pendaftaran
</Text>
<EditEditor <EditEditor
value={formData.prosedurPendaftaran.content} value={formData.prosedurPendaftaran.content}
onChange={(e) => onChange={(v) => updateNested('prosedurPendaftaran', 'content', v)}
setFormData(prev => ({
...prev,
prosedurPendaftaran: { content: e },
}))
}
/> />
</Box> </Box>
{/* Tarif dan Layanan */} {/* Tarif dan Layanan */}
<Box> <Box>
<Text fw="bold" mb={5}>Tarif dan Layanan</Text> <Text fw="bold" mb={5}>
Tarif dan Layanan
</Text>
<TextInput <TextInput
label="Tarif" label="Tarif"
defaultValue={formData.tarifDanLayanan.tarif} value={formData.tarifDanLayanan.tarif}
onChange={(e) => onChange={(e) => updateNested('tarifDanLayanan', 'tarif', e.target.value)}
setFormData(prev => ({
...prev,
tarifDanLayanan: { ...prev.tarifDanLayanan, tarif: e.target.value },
}))
}
/> />
<TextInput <TextInput
label="Layanan" label="Layanan"
defaultValue={formData.tarifDanLayanan.layanan} value={formData.tarifDanLayanan.layanan}
onChange={(e) => onChange={(e) => updateNested('tarifDanLayanan', 'layanan', e.target.value)}
setFormData(prev => ({
...prev,
tarifDanLayanan: { ...prev.tarifDanLayanan, layanan: e.target.value },
}))
}
/> />
</Box> </Box>
@@ -304,7 +238,7 @@ function EditFasilitasKesehatan() {
onClick={handleSubmit} onClick={handleSubmit}
radius="md" radius="md"
size="md" size="md"
loading={stateFasilitasKesehatan.edit.loading} loading={state.edit.loading}
style={{ style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`, background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff', color: '#fff',

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan'; import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
@@ -24,13 +25,14 @@ function EditGrafikHasilKepuasan() {
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nama: editState.update.form.nama || '', nama: '',
tanggal: editState.update.form.tanggal || '', tanggal: '',
jenisKelamin: editState.update.form.jenisKelamin || '', jenisKelamin: '',
alamat: editState.update.form.alamat || '', alamat: '',
penyakit: editState.update.form.penyakit || '', penyakit: '',
}); });
// Load data once
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -38,17 +40,15 @@ function EditGrafikHasilKepuasan() {
try { try {
const data = await editState.update.load(id); const data = await editState.update.load(id);
if (data) { if (data) setFormData({
setFormData({
nama: data.nama || '', nama: data.nama || '',
tanggal: data.tanggal || '', tanggal: data.tanggal || '',
jenisKelamin: data.jenisKelamin || '', jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '', alamat: data.alamat || '',
penyakit: data.penyakit || '', penyakit: data.penyakit || '',
}); });
} } catch (err) {
} catch (error) { console.error("Error loading grafik hasil kepuasan:", err);
console.error("Error loading grafik hasil kepuasan:", error);
toast.error("Gagal memuat data grafik hasil kepuasan"); toast.error("Gagal memuat data grafik hasil kepuasan");
} }
}; };
@@ -56,17 +56,19 @@ function EditGrafikHasilKepuasan() {
loadData(); loadData();
}, [params?.id]); }, [params?.id]);
// Generic handler for controlled inputs
const handleChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
editState.update.form = { editState.update.form = { ...editState.update.form, ...formData };
...editState.update.form,
...formData,
};
await editState.update.submit(); await editState.update.submit();
toast.success('Grafik hasil kepuasan berhasil diperbarui!'); toast.success('Grafik hasil kepuasan berhasil diperbarui!');
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan'); router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
} catch (error) { } catch (err) {
console.error('Error updating grafik hasil kepuasan:', error); console.error('Error updating grafik hasil kepuasan:', err);
toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan'); toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan');
} }
}; };
@@ -100,44 +102,17 @@ function EditGrafikHasilKepuasan() {
style={{ border: '1px solid #e0e0e0' }} style={{ border: '1px solid #e0e0e0' }}
> >
<Stack gap="md"> <Stack gap="md">
{(['nama','tanggal','jenisKelamin','alamat','penyakit'] as const).map((field) => (
<TextInput <TextInput
defaultValue={formData.nama} key={field}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} value={formData[field]}
label="Nama" onChange={(e) => handleChange(field, e.target.value)}
placeholder="Masukkan nama" type={field === 'tanggal' ? 'date' : 'text'}
required label={field === 'jenisKelamin' ? 'Jenis Kelamin' : field.charAt(0).toUpperCase() + field.slice(1)}
/> placeholder={`Masukkan ${field}`}
<TextInput
type="date"
defaultValue={formData.tanggal}
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
label="Tanggal"
placeholder="Masukkan tanggal"
required
/>
<TextInput
defaultValue={formData.jenisKelamin}
onChange={(e) =>
setFormData({ ...formData, jenisKelamin: e.target.value })
}
label="Jenis Kelamin"
placeholder="Masukkan jenis kelamin"
required
/>
<TextInput
defaultValue={formData.alamat}
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
label="Alamat"
placeholder="Masukkan alamat"
required
/>
<TextInput
defaultValue={formData.penyakit}
onChange={(e) => setFormData({ ...formData, penyakit: e.target.value })}
label="Penyakit"
placeholder="Masukkan penyakit"
required required
/> />
))}
<Group justify="right"> <Group justify="right">
<Button <Button

View File

@@ -4,17 +4,7 @@
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
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';
@@ -29,46 +19,49 @@ interface JadwalKegiatanFormBase {
waktu: string; waktu: string;
lokasi: string; lokasi: string;
}; };
deskripsiJadwalKegiatan: { deskripsiJadwalKegiatan: { deskripsi: string };
deskripsi: string; layananJadwalKegiatan: { content: string };
}; syaratKetentuanJadwalKegiatan: { content: string };
layananJadwalKegiatan: { dokumenJadwalKegiatan: { content: string };
content: string;
};
syaratKetentuanJadwalKegiatan: {
content: string;
};
dokumenJadwalKegiatan: {
content: string;
};
} }
const emptyForm = (): JadwalKegiatanFormBase => ({
content: '',
informasiJadwalKegiatan: { name: '', tanggal: '', waktu: '', lokasi: '' },
deskripsiJadwalKegiatan: { deskripsi: '' },
layananJadwalKegiatan: { content: '' },
syaratKetentuanJadwalKegiatan: { content: '' },
dokumenJadwalKegiatan: { content: '' },
});
function EditJadwalKegiatan() { function EditJadwalKegiatan() {
const stateJadwalKegiatan = useProxy(jadwalKegiatanState); const stateJadwalKegiatan = useProxy(jadwalKegiatanState);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState<JadwalKegiatanFormBase>({ const [formData, setFormData] = useState<JadwalKegiatanFormBase>(emptyForm());
content: stateJadwalKegiatan.edit.form.content || '',
informasiJadwalKegiatan: { // Helper untuk update nested state
name: stateJadwalKegiatan.edit.form.informasiJadwalKegiatan?.name || '', const updateNested = <
tanggal: stateJadwalKegiatan.edit.form.informasiJadwalKegiatan?.tanggal || '', K extends keyof JadwalKegiatanFormBase,
waktu: stateJadwalKegiatan.edit.form.informasiJadwalKegiatan?.waktu || '', N extends keyof JadwalKegiatanFormBase[K]
lokasi: stateJadwalKegiatan.edit.form.informasiJadwalKegiatan?.lokasi || '', >(
}, key: K,
deskripsiJadwalKegiatan: { subKey: N,
deskripsi: stateJadwalKegiatan.edit.form.deskripsiJadwalKegiatan?.deskripsi || '', value: string
}, ) => {
layananJadwalKegiatan: { setFormData(prev => ({
content: stateJadwalKegiatan.edit.form.layananJadwalKegiatan?.content || '', ...prev,
}, [key]: {
syaratKetentuanJadwalKegiatan: { ...(prev[key] as Record<string, unknown>),
content: stateJadwalKegiatan.edit.form.syaratKetentuanJadwalKegiatan?.content || '', [subKey]: value
}, }
dokumenJadwalKegiatan: { }));
content: stateJadwalKegiatan.edit.form.dokumenJadwalKegiatan?.content || '', };
},
}); const updateSimple = (key: keyof JadwalKegiatanFormBase, value: string) => {
setFormData(prev => ({ ...prev, [key]: value }));
};
useEffect(() => { useEffect(() => {
const loadJadwalKegiatan = async () => { const loadJadwalKegiatan = async () => {
@@ -80,25 +73,17 @@ function EditJadwalKegiatan() {
const { form } = stateJadwalKegiatan.edit; const { form } = stateJadwalKegiatan.edit;
if (form) { if (form) {
setFormData({ setFormData({
content: form.content, content: form.content || '',
informasiJadwalKegiatan: { informasiJadwalKegiatan: {
name: form.informasiJadwalKegiatan?.name || '', name: form.informasiJadwalKegiatan?.name || '',
tanggal: form.informasiJadwalKegiatan?.tanggal || '', tanggal: form.informasiJadwalKegiatan?.tanggal || '',
waktu: form.informasiJadwalKegiatan?.waktu || '', waktu: form.informasiJadwalKegiatan?.waktu || '',
lokasi: form.informasiJadwalKegiatan?.lokasi || '', lokasi: form.informasiJadwalKegiatan?.lokasi || '',
}, },
deskripsiJadwalKegiatan: { deskripsiJadwalKegiatan: { deskripsi: form.deskripsiJadwalKegiatan?.deskripsi || '' },
deskripsi: form.deskripsiJadwalKegiatan?.deskripsi || '', layananJadwalKegiatan: { content: form.layananJadwalKegiatan?.content || '' },
}, syaratKetentuanJadwalKegiatan: { content: form.syaratKetentuanJadwalKegiatan?.content || '' },
layananJadwalKegiatan: { dokumenJadwalKegiatan: { content: form.dokumenJadwalKegiatan?.content || '' },
content: form.layananJadwalKegiatan?.content || '',
},
syaratKetentuanJadwalKegiatan: {
content: form.syaratKetentuanJadwalKegiatan?.content || '',
},
dokumenJadwalKegiatan: {
content: form.dokumenJadwalKegiatan?.content || '',
},
}); });
} }
} catch (error) { } catch (error) {
@@ -111,16 +96,7 @@ function EditJadwalKegiatan() {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateJadwalKegiatan.edit.form = { stateJadwalKegiatan.edit.form = { ...stateJadwalKegiatan.edit.form, ...formData };
...stateJadwalKegiatan.edit.form,
content: formData.content,
informasiJadwalKegiatan: { ...formData.informasiJadwalKegiatan },
deskripsiJadwalKegiatan: { ...formData.deskripsiJadwalKegiatan },
layananJadwalKegiatan: { ...formData.layananJadwalKegiatan },
syaratKetentuanJadwalKegiatan: { ...formData.syaratKetentuanJadwalKegiatan },
dokumenJadwalKegiatan: { ...formData.dokumenJadwalKegiatan }
};
const success = await stateJadwalKegiatan.edit.submit(); const success = await stateJadwalKegiatan.edit.submit();
if (success) { if (success) {
toast.success("Jadwal kegiatan berhasil diperbarui!"); toast.success("Jadwal kegiatan berhasil diperbarui!");
@@ -160,8 +136,8 @@ function EditJadwalKegiatan() {
<TextInput <TextInput
label="Nama Jadwal Kegiatan" label="Nama Jadwal Kegiatan"
placeholder="Masukkan nama jadwal kegiatan" placeholder="Masukkan nama jadwal kegiatan"
defaultValue={formData.content} value={formData.content}
onChange={(e) => setFormData((prev) => ({ ...prev, content: e.target.value }))} onChange={(e) => updateSimple('content', e.target.value)}
/> />
{/* Deskripsi */} {/* Deskripsi */}
@@ -169,45 +145,22 @@ function EditJadwalKegiatan() {
<Text fz="sm" fw="bold">Deskripsi Jadwal Kegiatan</Text> <Text fz="sm" fw="bold">Deskripsi Jadwal Kegiatan</Text>
<EditEditor <EditEditor
value={formData.deskripsiJadwalKegiatan.deskripsi} value={formData.deskripsiJadwalKegiatan.deskripsi}
onChange={(val) => setFormData((prev) => ({ onChange={(val) => updateNested('deskripsiJadwalKegiatan', 'deskripsi', val)}
...prev,
deskripsiJadwalKegiatan: { deskripsi: val }
}))}
/> />
</Box> </Box>
{/* Informasi Jadwal */} {/* Informasi Jadwal */}
<Box> <Box>
<Text fz="md" fw="bold">Informasi Jadwal Kegiatan</Text> <Text fz="md" fw="bold">Informasi Jadwal Kegiatan</Text>
{(['name', 'tanggal', 'waktu', 'lokasi'] as const).map((field) => (
<TextInput <TextInput
label="Nama" key={field}
defaultValue={formData.informasiJadwalKegiatan.name} label={field === 'name' ? 'Nama' : field.charAt(0).toUpperCase() + field.slice(1)}
onChange={(e) => setFormData((prev) => ({ type={field === 'tanggal' ? 'date' : 'text'}
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, name: e.target.value } value={formData.informasiJadwalKegiatan[field]}
}))} onChange={(e) => updateNested('informasiJadwalKegiatan', field, e.target.value)}
/>
<TextInput
type="date"
label="Tanggal"
defaultValue={formData.informasiJadwalKegiatan.tanggal}
onChange={(e) => setFormData((prev) => ({
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, tanggal: e.target.value }
}))}
/>
<TextInput
label="Waktu"
defaultValue={formData.informasiJadwalKegiatan.waktu}
onChange={(e) => setFormData((prev) => ({
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, waktu: e.target.value }
}))}
/>
<TextInput
label="Lokasi"
defaultValue={formData.informasiJadwalKegiatan.lokasi}
onChange={(e) => setFormData((prev) => ({
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, lokasi: e.target.value }
}))}
/> />
))}
</Box> </Box>
{/* Layanan */} {/* Layanan */}
@@ -215,10 +168,7 @@ function EditJadwalKegiatan() {
<Text fz="md" fw="bold">Layanan Jadwal Kegiatan</Text> <Text fz="md" fw="bold">Layanan Jadwal Kegiatan</Text>
<EditEditor <EditEditor
value={formData.layananJadwalKegiatan.content} value={formData.layananJadwalKegiatan.content}
onChange={(val) => setFormData((prev) => ({ onChange={(val) => updateNested('layananJadwalKegiatan', 'content', val)}
...prev,
layananJadwalKegiatan: { content: val }
}))}
/> />
</Box> </Box>
@@ -227,10 +177,7 @@ function EditJadwalKegiatan() {
<Text fz="md" fw="bold">Syarat dan Ketentuan</Text> <Text fz="md" fw="bold">Syarat dan Ketentuan</Text>
<EditEditor <EditEditor
value={formData.syaratKetentuanJadwalKegiatan.content} value={formData.syaratKetentuanJadwalKegiatan.content}
onChange={(val) => setFormData((prev) => ({ onChange={(val) => updateNested('syaratKetentuanJadwalKegiatan', 'content', val)}
...prev,
syaratKetentuanJadwalKegiatan: { content: val }
}))}
/> />
</Box> </Box>
@@ -239,10 +186,7 @@ function EditJadwalKegiatan() {
<Text fz="md" fw="bold">Dokumen Yang Perlu Dibawa</Text> <Text fz="md" fw="bold">Dokumen Yang Perlu Dibawa</Text>
<EditEditor <EditEditor
value={formData.dokumenJadwalKegiatan.content} value={formData.dokumenJadwalKegiatan.content}
onChange={(val) => setFormData((prev) => ({ onChange={(val) => updateNested('dokumenJadwalKegiatan', 'content', val)}
...prev,
dokumenJadwalKegiatan: { content: val }
}))}
/> />
</Box> </Box>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran'; import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
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';
@@ -18,71 +19,58 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditKelahiran() { function EditKelahiran() {
const editState = useProxy(persentaseKelahiranKematian.kelahiran); const editState = useProxy(persentaseKelahiranKematian.kelahiran);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nama: editState.edit.form.nama || '', nama: '',
tanggal: editState.edit.form.tanggal || '', tanggal: '',
jenisKelamin: editState.edit.form.jenisKelamin || '', jenisKelamin: '',
alamat: editState.edit.form.alamat || '', alamat: '',
}); });
// Load data saat mount atau params.id berubah
useEffect(() => { useEffect(() => {
const loadKelahiran = async () => { const loadKelahiran = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
try { try {
const data = await editState.edit.load(id); const data = await editState.edit.load(id);
if (data) { if (data) setFormData({
setFormData({
nama: data.nama || '', nama: data.nama || '',
tanggal: data.tanggal || '', tanggal: data.tanggal || '',
jenisKelamin: data.jenisKelamin || '', jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '', alamat: data.alamat || ''
}); });
}
} catch (error) { } catch (error) {
console.error('Error loading data kelahiran:', error); console.error('Error loading data kelahiran:', error);
toast.error('Gagal memuat data kelahiran'); toast.error('Gagal memuat data kelahiran');
} }
}; };
loadKelahiran(); loadKelahiran();
}, [params?.id]); }, [params?.id]);
const handleChange = (key: keyof typeof formData, value: string) => {
setFormData((prev) => ({ ...prev, [key]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
editState.edit.form = { // Update global state hanya saat submit
...editState.edit.form, editState.edit.form = { ...editState.edit.form, ...formData };
nama: formData.nama,
tanggal: formData.tanggal,
jenisKelamin: formData.jenisKelamin,
alamat: formData.alamat,
};
await editState.edit.update(); await editState.edit.update();
toast.success('Data kelahiran berhasil diperbarui!'); toast.success('Data kelahiran berhasil diperbarui!');
router.push( router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran');
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran'
);
} catch (error) { } catch (error) {
console.error('Error updating data kelahiran:', error); console.error('Error updating data kelahiran:', error);
toast.error('Terjadi kesalahan saat memperbarui data kelahiran'); toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
@@ -97,7 +85,6 @@ function EditKelahiran() {
</Title> </Title>
</Group> </Group>
{/* Form */} {/* Form */}
<Paper <Paper
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '50%' }}
@@ -109,36 +96,35 @@ function EditKelahiran() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} onChange={(e) => handleChange('nama', e.target.value)}
label="Nama" label="Nama"
placeholder="Masukkan nama" placeholder="Masukkan nama"
required required
/> />
<TextInput <TextInput
type="date" type="date"
defaultValue={formData.tanggal} value={formData.tanggal}
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })} onChange={(e) => handleChange('tanggal', e.target.value)}
label="Tanggal" label="Tanggal"
placeholder="Masukkan tanggal" placeholder="Masukkan tanggal"
required required
/> />
<TextInput <TextInput
defaultValue={formData.jenisKelamin} value={formData.jenisKelamin}
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })} onChange={(e) => handleChange('jenisKelamin', e.target.value)}
label="Jenis Kelamin" label="Jenis Kelamin"
placeholder="Masukkan jenis kelamin" placeholder="Masukkan jenis kelamin"
required required
/> />
<TextInput <TextInput
defaultValue={formData.alamat} value={formData.alamat}
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })} onChange={(e) => handleChange('alamat', e.target.value)}
label="Alamat" label="Alamat"
placeholder="Masukkan alamat" placeholder="Masukkan alamat"
required required
/> />
<Group justify="right"> <Group justify="right">
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
@@ -159,5 +145,4 @@ function EditKelahiran() {
); );
} }
export default EditKelahiran; export default EditKelahiran;

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran'; import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -20,28 +21,25 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditKematian() { function EditKematian() {
const editState = useProxy(persentaseKelahiranKematian.kematian); const editState = useProxy(persentaseKelahiranKematian.kematian);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nama: editState.edit.form.nama || '', nama: '',
tanggal: editState.edit.form.tanggal || '', tanggal: '',
jenisKelamin: editState.edit.form.jenisKelamin || '', jenisKelamin: '',
alamat: editState.edit.form.alamat || '', alamat: '',
penyebab: editState.edit.form.penyebab || '', penyebab: '',
}); });
// Load data saat mount
useEffect(() => { useEffect(() => {
const loadData = 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 editState.edit.load(id); const data = await editState.edit.load(id);
if (data) { if (data) {
@@ -59,13 +57,16 @@ function EditKematian() {
} }
}; };
loadData(); loadData();
}, [params?.id]); }, [params?.id]);
const handleChange = (key: keyof typeof formData, value: string) => {
setFormData(prev => ({ ...prev, [key]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// Update global state saat submit
editState.edit.form = { ...editState.edit.form, ...formData }; editState.edit.form = { ...editState.edit.form, ...formData };
await editState.edit.update(); await editState.edit.update();
toast.success('Data kematian berhasil diperbarui!'); toast.success('Data kematian berhasil diperbarui!');
@@ -78,10 +79,9 @@ function EditKematian() {
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan tombol back */} {/* Header */}
<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">
@@ -93,8 +93,7 @@ function EditKematian() {
</Title> </Title>
</Group> </Group>
{/* Form Card */}
{/* Card Form */}
<Paper <Paper
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '50%' }}
bg={colors['white-1']} bg={colors['white-1']}
@@ -107,54 +106,46 @@ function EditKematian() {
<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
/> />
<TextInput <TextInput
type="date" type="date"
label="Tanggal" label="Tanggal"
placeholder="Masukkan tanggal" placeholder="Masukkan tanggal"
defaultValue={formData.tanggal} value={formData.tanggal}
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })} onChange={(e) => handleChange('tanggal', e.target.value)}
required required
/> />
<TextInput <TextInput
label="Jenis Kelamin" label="Jenis Kelamin"
placeholder="Masukkan jenis kelamin" placeholder="Masukkan jenis kelamin"
defaultValue={formData.jenisKelamin} value={formData.jenisKelamin}
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })} onChange={(e) => handleChange('jenisKelamin', 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
/> />
<Box> <Box>
<Text fz="sm" fw="bold" mb={6}> <Text fz="sm" fw="bold" mb={6}>
Penyebab Penyebab
</Text> </Text>
<EditEditor <EditEditor
value={formData.penyebab} value={formData.penyebab}
onChange={(htmlContent) => { onChange={(htmlContent) => handleChange('penyebab', htmlContent)}
setFormData((prev) => ({ ...prev, penyebab: htmlContent }));
editState.edit.form.penyebab = htmlContent;
}}
/> />
</Box> </Box>
<Group justify="right"> <Group justify="right">
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
@@ -175,5 +166,4 @@ function EditKematian() {
); );
} }
export default EditKematian; export default EditKematian;

View File

@@ -19,7 +19,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, ChangeEvent } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -28,15 +28,21 @@ function EditInfoWabahPenyakit() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({
name: '',
deskripsiSingkat: '',
deskripsi: '',
imageId: '',
});
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({
name: infoWabahPenyakitState.edit.form.name || '',
deskripsiSingkat: infoWabahPenyakitState.edit.form.deskripsiSingkat || '',
deskripsi: infoWabahPenyakitState.edit.form.deskripsiLengkap || '',
imageId: infoWabahPenyakitState.edit.form.imageId || '',
});
// Helper untuk update field formData
const updateField = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
// Load data edit
useEffect(() => { useEffect(() => {
const loadInfoWabahPenyakit = async () => { const loadInfoWabahPenyakit = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -52,12 +58,10 @@ function EditInfoWabahPenyakit() {
imageId: data.imageId || '', imageId: data.imageId || '',
}); });
if (data?.image?.link) { if (data.image?.link) setPreviewImage(data.image.link);
setPreviewImage(data.image.link);
}
} }
} catch (error) { } catch (error) {
console.error('Error loading info wabah penyakit:', error); console.error(error);
toast.error('Gagal memuat data info wabah penyakit'); toast.error('Gagal memuat data info wabah penyakit');
} }
}; };
@@ -67,34 +71,43 @@ function EditInfoWabahPenyakit() {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
let uploadedImageId = formData.imageId;
// Upload file 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');
uploadedImageId = uploaded.id;
}
// Update global state
infoWabahPenyakitState.edit.form = { infoWabahPenyakitState.edit.form = {
...infoWabahPenyakitState.edit.form, ...infoWabahPenyakitState.edit.form,
name: formData.name, name: formData.name,
deskripsiSingkat: formData.deskripsiSingkat, deskripsiSingkat: formData.deskripsiSingkat,
deskripsiLengkap: formData.deskripsi, deskripsiLengkap: formData.deskripsi,
imageId: formData.imageId, imageId: uploadedImageId,
}; };
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');
}
infoWabahPenyakitState.edit.form.imageId = uploaded.id;
}
await infoWabahPenyakitState.edit.update(); await infoWabahPenyakitState.edit.update();
toast.success('Info wabah penyakit berhasil diperbarui!'); toast.success('Info wabah penyakit berhasil diperbarui!');
router.push('/admin/kesehatan/info-wabah-penyakit'); router.push('/admin/kesehatan/info-wabah-penyakit');
} catch (error) { } catch (error) {
console.error('Error updating info wabah penyakit:', error); console.error(error);
toast.error('Terjadi kesalahan saat memperbarui info wabah penyakit'); toast.error('Terjadi kesalahan saat memperbarui info wabah penyakit');
} }
}; };
const handleDrop = (files: File[]) => {
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
}
};
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
@@ -120,16 +133,18 @@ function EditInfoWabahPenyakit() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e: ChangeEvent<HTMLInputElement>) => updateField('name', e.target.value)}
label="Judul" label="Judul"
placeholder="Masukkan judul" placeholder="Masukkan judul"
required required
/> />
<TextInput <TextInput
defaultValue={formData.deskripsiSingkat} value={formData.deskripsiSingkat}
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })} onChange={(e: ChangeEvent<HTMLInputElement>) =>
updateField('deskripsiSingkat', e.target.value)
}
label="Deskripsi Singkat" label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat" placeholder="Masukkan deskripsi singkat"
required required
@@ -141,7 +156,7 @@ function EditInfoWabahPenyakit() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })} onChange={(val) => updateField('deskripsi', val)}
/> />
</Box> </Box>
@@ -150,13 +165,7 @@ function EditInfoWabahPenyakit() {
Gambar Gambar
</Text> </Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={handleDrop}
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
}
}}
onReject={() => toast.error('File tidak valid.')} onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }} accept={{ 'image/*': [] }}

View File

@@ -1,5 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */ 'use client';
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat'; import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -23,7 +22,6 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditKontakDarurat() { function EditKontakDarurat() {
const kontakDaruratState = useProxy(kontakDarurat); const kontakDaruratState = useProxy(kontakDarurat);
const router = useRouter(); const router = useRouter();
@@ -32,11 +30,13 @@ function EditKontakDarurat() {
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: kontakDaruratState.edit.form.name || '', name: '',
deskripsi: kontakDaruratState.edit.form.deskripsi || '', deskripsi: '',
imageId: kontakDaruratState.edit.form.imageId || '', imageId: '',
}); });
const [loading, setLoading] = useState(true);
// Load data sekali saat mount
useEffect(() => { useEffect(() => {
const loadKontakDarurat = async () => { const loadKontakDarurat = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -50,39 +50,37 @@ function EditKontakDarurat() {
deskripsi: data.deskripsi || '', deskripsi: data.deskripsi || '',
imageId: data.imageId || '', imageId: data.imageId || '',
}); });
if (data?.image?.link) setPreviewImage(data.image.link);
if (data?.image?.link) {
setPreviewImage(data.image.link);
}
} }
} catch (error) { } catch (error) {
console.error("Error loading kontak darurat:", error); console.error("Error loading kontak darurat:", error);
toast.error("Gagal memuat data kontak darurat"); toast.error("Gagal memuat data kontak darurat");
} finally {
setLoading(false);
} }
}; };
loadKontakDarurat(); loadKontakDarurat();
}, [params?.id]); }, [params?.id, kontakDaruratState.edit]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
kontakDaruratState.edit.form = { let imageId = formData.imageId;
...kontakDaruratState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
};
// Upload file baru jika 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) return toast.error("Gagal upload gambar");
if (!uploaded?.id) { imageId = uploaded.id;
return toast.error("Gagal upload gambar");
} }
kontakDaruratState.edit.form.imageId = uploaded.id; // Update global state sekaligus submit
} kontakDaruratState.edit.form = {
...kontakDaruratState.edit.form,
...formData,
imageId,
};
await kontakDaruratState.edit.update(); await kontakDaruratState.edit.update();
toast.success("Kontak darurat berhasil diperbarui!"); toast.success("Kontak darurat berhasil diperbarui!");
@@ -93,9 +91,10 @@ function EditKontakDarurat() {
} }
}; };
if (loading) return <Text>Loading...</Text>;
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 variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
@@ -107,7 +106,6 @@ function EditKontakDarurat() {
</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']}
@@ -117,9 +115,10 @@ function EditKontakDarurat() {
style={{ border: '1px solid #e0e0e0' }} style={{ border: '1px solid #e0e0e0' }}
> >
<Stack gap="md"> <Stack gap="md">
{/* Controlled Input */}
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
label="Judul" label="Judul"
placeholder="Masukkan judul" placeholder="Masukkan judul"
required required
@@ -129,7 +128,7 @@ function EditKontakDarurat() {
<Text fz="sm" fw="bold">Deskripsi</Text> <Text fz="sm" fw="bold">Deskripsi</Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })} onChange={(val) => setFormData(prev => ({ ...prev, deskripsi: val }))}
/> />
</Box> </Box>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat'; import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -28,16 +29,19 @@ function EditPenangananDarurat() {
const router = useRouter(); const router = useRouter();
const params = useParams() const params = useParams()
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
imageId: '',
});
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 [loading, setLoading] = useState(true);
name: penangananDaruratState.edit.form.name || '',
deskripsi: penangananDaruratState.edit.form.deskripsi || '',
imageId: penangananDaruratState.edit.form.imageId || '',
})
// Load data satu kali saat component mount
useEffect(() => { useEffect(() => {
const loadPenangananDarurat = async () => { const loadData = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -48,49 +52,66 @@ function EditPenangananDarurat() {
name: data.name || '', name: data.name || '',
deskripsi: data.deskripsi || '', deskripsi: data.deskripsi || '',
imageId: data.imageId || '', imageId: data.imageId || '',
}) });
if (data?.image?.link) { if (data.image?.link) {
setPreviewImage(data.image.link); setPreviewImage(data.image.link);
} }
} }
} catch (error) { } catch (err) {
console.error('Error loading penanganan darurat:', error); console.error('Error loading penanganan darurat:', err);
toast.error('Gagal memuat data penanganan darurat'); toast.error('Gagal memuat data penanganan darurat');
} finally {
setLoading(false);
} }
} }
loadPenangananDarurat(); loadData();
}, [params?.id]) }, [params?.id]);
const handleChange = (key: keyof typeof formData, value: string) => {
setFormData(prev => ({ ...prev, [key]: value }));
};
const handleDrop = (files: File[]) => {
const selected = files[0];
if (!selected) return;
setFile(selected);
setPreviewImage(URL.createObjectURL(selected));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
penangananDaruratState.edit.form = { let imageId = formData.imageId;
...penangananDaruratState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
}
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");
imageId = uploaded.id;
} }
penangananDaruratState.edit.form.imageId = uploaded.id; // update global state sekali saat submit
} penangananDaruratState.edit.form = {
...penangananDaruratState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
imageId,
};
await penangananDaruratState.edit.update(); await penangananDaruratState.edit.update();
toast.success("Penanganan darurat berhasil diperbarui!"); toast.success("Penanganan darurat berhasil diperbarui!");
router.push("/admin/kesehatan/penanganan-darurat"); router.push("/admin/kesehatan/penanganan-darurat");
} catch (error) { } catch (err) {
console.error("Error updating penanganan darurat:", error); console.error("Error updating penanganan darurat:", err);
toast.error("Gagal memperbarui data penanganan darurat"); toast.error("Gagal memperbarui data penanganan darurat");
} }
} };
if (loading) return <Text>Loading...</Text>;
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -117,8 +138,8 @@ function EditPenangananDarurat() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange('name', e.target.value)}
label="Judul" label="Judul"
placeholder="Masukkan judul" placeholder="Masukkan judul"
required required
@@ -128,20 +149,14 @@ function EditPenangananDarurat() {
<Text fz="sm" fw="bold">Deskripsi</Text> <Text fz="sm" fw="bold">Deskripsi</Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })} onChange={(val) => handleChange('deskripsi', val)}
/> />
</Box> </Box>
<Box> <Box>
<Text fz="sm" fw="bold">Gambar</Text> <Text fz="sm" fw="bold">Gambar</Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={handleDrop}
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
}
}}
onReject={() => toast.error('File tidak valid.')} onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }} accept={{ 'image/*': [] }}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu'; import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -23,30 +24,27 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function EditPosyandu() { function EditPosyandu() {
const statePosyandu = useProxy(posyandustate); const statePosyandu = useProxy(posyandustate);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
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: statePosyandu.edit.form.name || '', name: '',
nomor: statePosyandu.edit.form.nomor || '', nomor: '',
deskripsi: statePosyandu.edit.form.deskripsi || '', deskripsi: '',
imageId: statePosyandu.edit.form.imageId || '', imageId: '',
jadwalPelayanan: statePosyandu.edit.form.jadwalPelayanan || '', jadwalPelayanan: '',
}); });
// Load data posyandu
useEffect(() => { useEffect(() => {
const loadPosyandu = async () => { const loadPosyandu = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
try { try {
const data = await statePosyandu.edit.load(id); const data = await statePosyandu.edit.load(id);
if (data) { if (data) {
@@ -57,53 +55,41 @@ function EditPosyandu() {
imageId: data.imageId || '', imageId: data.imageId || '',
jadwalPelayanan: data.jadwalPelayanan || '', jadwalPelayanan: data.jadwalPelayanan || '',
}); });
if (data?.image?.link) setPreviewImage(data.image.link);
if (data?.image?.link) {
setPreviewImage(data.image.link);
}
} }
} catch (error) { } catch (error) {
console.error("Error loading posyandu:", error); console.error('Error loading posyandu:', error);
toast.error("Gagal memuat data posyandu"); toast.error('Gagal memuat data posyandu');
} }
}; };
loadPosyandu(); loadPosyandu();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
statePosyandu.edit.form = { // Update global state hanya saat submit
...statePosyandu.edit.form, const updatedForm = { ...statePosyandu.edit.form, ...formData };
...formData,
};
// Upload file jika 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) return toast.error('Gagal upload gambar');
if (!uploaded?.id) { updatedForm.imageId = uploaded.id;
return toast.error("Gagal upload gambar");
} }
statePosyandu.edit.form = updatedForm;
statePosyandu.edit.form.imageId = uploaded.id;
}
await statePosyandu.edit.update(); await statePosyandu.edit.update();
toast.success("Posyandu berhasil diperbarui!");
router.push("/admin/kesehatan/posyandu"); toast.success('Posyandu berhasil diperbarui!');
router.push('/admin/kesehatan/posyandu');
} catch (error) { } catch (error) {
console.error("Error updating posyandu:", error); console.error('Error updating posyandu:', error);
toast.error("Terjadi kesalahan saat memperbarui posyandu"); toast.error('Terjadi kesalahan saat memperbarui posyandu');
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Tombol Back */} {/* Tombol Back */}
@@ -118,7 +104,6 @@ function EditPosyandu() {
</Title> </Title>
</Group> </Group>
{/* Card utama */} {/* Card utama */}
<Paper <Paper
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '50%' }}
@@ -169,7 +154,6 @@ function EditPosyandu() {
</Group> </Group>
</Dropzone> </Dropzone>
{previewImage && ( {previewImage && (
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}> <Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
<Image <Image
@@ -187,50 +171,45 @@ function EditPosyandu() {
)} )}
</Box> </Box>
{/* Input Form */} {/* Input Form */}
<TextInput <TextInput
label="Nama Posyandu" label="Nama Posyandu"
placeholder="Masukkan nama posyandu" placeholder="Masukkan nama posyandu"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required required
/> />
<TextInput <TextInput
label="Nomor Posyandu" label="Nomor Posyandu"
placeholder="Masukkan nomor posyandu" placeholder="Masukkan nomor posyandu"
defaultValue={formData.nomor} value={formData.nomor}
onChange={(e) => setFormData({ ...formData, nomor: e.target.value })} onChange={(e) => setFormData({ ...formData, nomor: e.target.value })}
required required
/> />
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}>Deskripsi Posyandu</Text> <Text fw="bold" fz="sm" mb={6}>
Deskripsi Posyandu
</Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) => setFormData({ ...formData, deskripsi: htmlContent })}
setFormData({ ...formData, deskripsi: htmlContent });
statePosyandu.edit.form.deskripsi = htmlContent;
}}
/> />
</Box> </Box>
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}>Jadwal Pelayanan</Text> <Text fw="bold" fz="sm" mb={6}>
Jadwal Pelayanan
</Text>
<EditEditor <EditEditor
value={formData.jadwalPelayanan} value={formData.jadwalPelayanan}
onChange={(htmlContent) => { onChange={(htmlContent) =>
setFormData({ ...formData, jadwalPelayanan: htmlContent }); setFormData({ ...formData, jadwalPelayanan: htmlContent })
statePosyandu.edit.form.jadwalPelayanan = htmlContent; }
}}
/> />
</Box> </Box>
{/* Tombol Submit */} {/* Tombol Submit */}
<Group justify="right"> <Group justify="right">
<Button <Button
@@ -252,5 +231,4 @@ function EditPosyandu() {
); );
} }
export default EditPosyandu; export default EditPosyandu;

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan'; import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -31,20 +31,22 @@ function EditProgramKesehatan() {
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: programKesehatanState.edit.form.name || '', name: '',
deskripsiSingkat: programKesehatanState.edit.form.deskripsiSingkat || '', deskripsiSingkat: '',
deskripsi: programKesehatanState.edit.form.deskripsi || '', deskripsi: '',
imageId: programKesehatanState.edit.form.imageId || '', imageId: '',
}); });
// Load data awal
useEffect(() => { useEffect(() => {
const loadProgramKesehatan = 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 programKesehatanState.edit.load(id); const data = await programKesehatanState.edit.load(id);
if (data) { if (!data) return;
setFormData({ setFormData({
name: data.name || '', name: data.name || '',
deskripsiSingkat: data.deskripsiSingkat || '', deskripsiSingkat: data.deskripsiSingkat || '',
@@ -52,52 +54,47 @@ function EditProgramKesehatan() {
imageId: data.imageId || '', imageId: data.imageId || '',
}); });
if (data?.image?.link) { if (data?.image?.link) setPreviewImage(data.image.link);
setPreviewImage(data.image.link); } catch (err) {
} console.error(err);
}
} catch (error) {
console.error('Error loading program kesehatan:', error);
toast.error('Gagal memuat data program kesehatan'); toast.error('Gagal memuat data program kesehatan');
} }
}; };
loadProgramKesehatan(); loadData();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { // Handler input controlled
try { const handleChange = (key: keyof typeof formData, value: string) => {
programKesehatanState.edit.form = { setFormData((prev) => ({ ...prev, [key]: value }));
...programKesehatanState.edit.form,
name: formData.name,
deskripsiSingkat: formData.deskripsiSingkat,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
}; };
// Submit form
const handleSubmit = async () => {
try {
const updatedForm = { ...programKesehatanState.edit.form, ...formData };
// Upload file 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) return toast.error('Gagal upload gambar');
if (!uploaded?.id) { updatedForm.imageId = uploaded.id;
return toast.error('Gagal upload gambar');
}
programKesehatanState.edit.form.imageId = uploaded.id;
} }
programKesehatanState.edit.form = updatedForm;
await programKesehatanState.edit.update(); await programKesehatanState.edit.update();
toast.success('Program kesehatan berhasil diperbarui!'); toast.success('Program kesehatan berhasil diperbarui!');
router.push('/admin/kesehatan/program-kesehatan'); router.push('/admin/kesehatan/program-kesehatan');
} catch (error) { } catch (err) {
console.error('Error updating program kesehatan:', error); console.error(err);
toast.error('Terjadi kesalahan saat memperbarui program kesehatan'); toast.error('Terjadi kesalahan saat memperbarui program kesehatan');
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan tombol back */}
<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">
@@ -109,7 +106,6 @@ function EditProgramKesehatan() {
</Title> </Title>
</Group> </Group>
{/* Card Form */}
<Paper <Paper
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '50%' }}
bg={colors['white-1']} bg={colors['white-1']}
@@ -119,21 +115,19 @@ function EditProgramKesehatan() {
style={{ border: '1px solid #e0e0e0' }} style={{ border: '1px solid #e0e0e0' }}
> >
<Stack gap="md"> <Stack gap="md">
{[
{ label: 'Judul', key: 'name', placeholder: 'Masukkan judul' },
{ label: 'Deskripsi Singkat', key: 'deskripsiSingkat', placeholder: 'Masukkan deskripsi singkat' },
].map((field) => (
<TextInput <TextInput
defaultValue={formData.name} key={field.key}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} label={field.label}
label="Judul" placeholder={field.placeholder}
placeholder="Masukkan judul" value={formData[field.key as keyof typeof formData]}
required onChange={(e) => handleChange(field.key as keyof typeof formData, e.target.value)}
/>
<TextInput
defaultValue={formData.deskripsiSingkat}
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
label="Deskripsi Singkat"
placeholder="Masukkan deskripsi singkat"
required required
/> />
))}
<Box> <Box>
<Text fz="sm" fw="bold" mb={6}> <Text fz="sm" fw="bold" mb={6}>
@@ -141,7 +135,7 @@ function EditProgramKesehatan() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })} onChange={(val) => handleChange('deskripsi', val)}
/> />
</Box> </Box>
@@ -152,10 +146,9 @@ function EditProgramKesehatan() {
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {
const selectedFile = files[0]; const selectedFile = files[0];
if (selectedFile) { if (!selectedFile) return;
setFile(selectedFile); setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); setPreviewImage(URL.createObjectURL(selectedFile));
}
}} }}
onReject={() => toast.error('File tidak valid.')} onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} maxSize={5 * 1024 ** 2}

View File

@@ -54,20 +54,11 @@ function EditPuskesmas() {
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<PuskesmasFormData>({ const [formData, setFormData] = useState<PuskesmasFormData>({
name: statePuskesmas.edit.form.name || '', name: '',
alamat: statePuskesmas.edit.form.alamat || '', alamat: '',
jam: { jam: { workDays: '', weekDays: '', holiday: '' },
workDays: statePuskesmas.edit.form.jam?.workDays || '', kontak: { kontakPuskesmas: '', email: '', facebook: '', kontakUGD: '' },
weekDays: statePuskesmas.edit.form.jam?.weekDays || '', imageId: '',
holiday: statePuskesmas.edit.form.jam?.holiday || '',
},
kontak: {
kontakPuskesmas: statePuskesmas.edit.form.kontak?.kontakPuskesmas || '',
email: statePuskesmas.edit.form.kontak?.email || '',
facebook: statePuskesmas.edit.form.kontak?.facebook || '',
kontakUGD: statePuskesmas.edit.form.kontak?.kontakUGD || '',
},
imageId: statePuskesmas.edit.form.imageId || '',
}); });
useEffect(() => { useEffect(() => {
@@ -183,7 +174,7 @@ function EditPuskesmas() {
label="Nama Puskesmas" label="Nama Puskesmas"
placeholder="Masukkan nama puskesmas" placeholder="Masukkan nama puskesmas"
name="name" name="name"
defaultValue={formData.name} value={formData.name}
onChange={handleInputChange} onChange={handleInputChange}
required required
/> />
@@ -192,7 +183,7 @@ function EditPuskesmas() {
label="Alamat" label="Alamat"
placeholder="Masukkan alamat" placeholder="Masukkan alamat"
name="alamat" name="alamat"
defaultValue={formData.alamat} value={formData.alamat}
onChange={handleInputChange} onChange={handleInputChange}
required required
/> />
@@ -200,7 +191,7 @@ function EditPuskesmas() {
<TextInput <TextInput
label="Jam Buka" label="Jam Buka"
placeholder="Masukkan jam buka" placeholder="Masukkan jam buka"
defaultValue={formData.jam.workDays} value={formData.jam.workDays}
onChange={(e) => handleNestedChange('jam', 'workDays', e.target.value)} onChange={(e) => handleNestedChange('jam', 'workDays', e.target.value)}
required required
/> />
@@ -208,7 +199,7 @@ function EditPuskesmas() {
<TextInput <TextInput
label="Jam Tutup" label="Jam Tutup"
placeholder="Masukkan jam tutup" placeholder="Masukkan jam tutup"
defaultValue={formData.jam.weekDays} value={formData.jam.weekDays}
onChange={(e) => handleNestedChange('jam', 'weekDays', e.target.value)} onChange={(e) => handleNestedChange('jam', 'weekDays', e.target.value)}
required required
/> />
@@ -216,7 +207,7 @@ function EditPuskesmas() {
<TextInput <TextInput
label="Jam Libur" label="Jam Libur"
placeholder="Masukkan jam libur" placeholder="Masukkan jam libur"
defaultValue={formData.jam.holiday} value={formData.jam.holiday}
onChange={(e) => handleNestedChange('jam', 'holiday', e.target.value)} onChange={(e) => handleNestedChange('jam', 'holiday', e.target.value)}
required required
/> />
@@ -224,28 +215,28 @@ function EditPuskesmas() {
<TextInput <TextInput
label="Kontak Puskesmas" label="Kontak Puskesmas"
placeholder="Masukkan kontak puskesmas" placeholder="Masukkan kontak puskesmas"
defaultValue={formData.kontak.kontakPuskesmas} value={formData.kontak.kontakPuskesmas}
onChange={(e) => handleNestedChange('kontak', 'kontakPuskesmas', e.target.value)} onChange={(e) => handleNestedChange('kontak', 'kontakPuskesmas', e.target.value)}
/> />
<TextInput <TextInput
label="Email" label="Email"
placeholder="Masukkan email" placeholder="Masukkan email"
defaultValue={formData.kontak.email} value={formData.kontak.email}
onChange={(e) => handleNestedChange('kontak', 'email', e.target.value)} onChange={(e) => handleNestedChange('kontak', 'email', e.target.value)}
/> />
<TextInput <TextInput
label="Facebook" label="Facebook"
placeholder="Masukkan facebook" placeholder="Masukkan facebook"
defaultValue={formData.kontak.facebook} value={formData.kontak.facebook}
onChange={(e) => handleNestedChange('kontak', 'facebook', e.target.value)} onChange={(e) => handleNestedChange('kontak', 'facebook', e.target.value)}
/> />
<TextInput <TextInput
label="Kontak UGD" label="Kontak UGD"
placeholder="Masukkan kontak UGD" placeholder="Masukkan kontak UGD"
defaultValue={formData.kontak.kontakUGD} value={formData.kontak.kontakUGD}
onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)} onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)}
/> />

View File

@@ -28,20 +28,21 @@ function EditAPBDes() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({
name: '',
jumlah: '',
imageId: '',
fileId: ''
});
const [previewImage, setPreviewImage] = useState<string | null>(null); const [previewImage, setPreviewImage] = useState<string | null>(null);
const [previewDoc, setPreviewDoc] = useState<string | null>(null); const [previewDoc, setPreviewDoc] = useState<string | null>(null);
const [imageFile, setImageFile] = useState<File | null>(null); const [imageFile, setImageFile] = useState<File | null>(null);
const [docFile, setDocFile] = useState<File | null>(null); const [docFile, setDocFile] = useState<File | null>(null);
const [formData, setFormData] = useState({
name: apbdesState.edit.form.name || '',
jumlah: apbdesState.edit.form.jumlah || '',
imageId: apbdesState.edit.form.imageId || '',
fileId: apbdesState.edit.form.fileId || ''
});
// Load APBDes data by id // Load data on mount
useEffect(() => { useEffect(() => {
const loadAPBDes = async () => { const loadData = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -54,62 +55,58 @@ function EditAPBDes() {
imageId: data.imageId || '', imageId: data.imageId || '',
fileId: data.fileId || '' fileId: data.fileId || ''
}); });
setPreviewImage(data.image?.link || null);
if (data.image?.link) setPreviewImage(data.image.link); setPreviewDoc(data.file?.link || null);
if (data.file?.link) setPreviewDoc(data.file.link);
} }
} catch (error) { } catch (err) {
console.error('Error loading APBDes:', error); console.error(err);
toast.error('Gagal memuat data APBDes'); toast.error('Gagal memuat data APBDes');
} }
}; };
loadAPBDes(); loadData();
}, [params?.id]); }, [params?.id]);
// Generic Dropzone handler
const handleDrop = (fileType: 'image' | 'doc') => (files: File[]) => {
const file = files[0];
if (!file) return;
if (fileType === 'image') {
setImageFile(file);
setPreviewImage(URL.createObjectURL(file));
} else {
setDocFile(file);
setPreviewDoc(URL.createObjectURL(file));
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// Update global state with form data // Update global state with local form data first
apbdesState.edit.form = { apbdesState.edit.form = { ...apbdesState.edit.form, ...formData };
...apbdesState.edit.form,
...formData, // Helper function for uploading file
const uploadFile = async (file: File | null) => {
if (!file) return null;
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
const uploaded = res.data?.data;
if (!uploaded?.id) throw new Error('Upload gagal');
return uploaded.id;
}; };
// Upload new image if exists // Upload files if selected
if (imageFile) { const uploadedImageId = await uploadFile(imageFile);
const res = await ApiFetch.api.fileStorage.create.post({ const uploadedDocId = await uploadFile(docFile);
file: imageFile,
name: imageFile.name
});
const uploaded = res.data?.data;
if (!uploaded?.id) { if (uploadedImageId) apbdesState.edit.form.imageId = uploadedImageId;
return toast.error('Gagal upload gambar'); if (uploadedDocId) apbdesState.edit.form.fileId = uploadedDocId;
}
apbdesState.edit.form.imageId = uploaded.id;
}
// Upload new document if exists
if (docFile) {
const res = await ApiFetch.api.fileStorage.create.post({
file: docFile,
name: docFile.name
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error('Gagal upload dokumen');
}
apbdesState.edit.form.fileId = uploaded.id;
}
await apbdesState.edit.update(); await apbdesState.edit.update();
toast.success('APBDes berhasil diperbarui!'); toast.success('APBDes berhasil diperbarui!');
router.push('/admin/landing-page/apbdes'); router.push('/admin/landing-page/apbdes');
} catch (error) { } catch (err) {
console.error('Error updating APBDes:', error); console.error(err);
toast.error('Terjadi kesalahan saat memperbarui APBDes'); toast.error('Terjadi kesalahan saat memperbarui APBDes');
} }
}; };
@@ -127,19 +124,13 @@ function EditAPBDes() {
</Title> </Title>
</Group> </Group>
<Paper <Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="lg" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md"> <Stack gap="md">
{/* Controlled Inputs */}
<TextInput <TextInput
label="Nama APBDes" label="Nama APBDes"
placeholder="Masukkan nama APBDes" placeholder="Masukkan nama APBDes"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required required
/> />
@@ -147,23 +138,16 @@ function EditAPBDes() {
<TextInput <TextInput
label="Jumlah Anggaran" label="Jumlah Anggaran"
placeholder="Masukkan jumlah anggaran" placeholder="Masukkan jumlah anggaran"
defaultValue={formData.jumlah} value={formData.jumlah}
onChange={(e) => setFormData({ ...formData, jumlah: e.target.value })} onChange={(e) => setFormData({ ...formData, jumlah: e.target.value })}
required required
/> />
{/* Image Dropzone */}
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>Gambar APBDes</Text>
Gambar APBDes
</Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={handleDrop('image')}
const selectedFile = files[0];
if (selectedFile) {
setImageFile(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/*': [] }}
@@ -171,57 +155,29 @@ function EditAPBDes() {
p="xl" p="xl"
> >
<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} /></Dropzone.Accept>
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} /> <Dropzone.Reject><IconX size={48} color="red" stroke={1.5} /></Dropzone.Reject>
</Dropzone.Accept> <Dropzone.Idle><IconPhoto size={48} color="#868e96" stroke={1.5} /></Dropzone.Idle>
<Dropzone.Reject>
<IconX size={48} color="red" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={48} color="#868e96" stroke={1.5} />
</Dropzone.Idle>
<Stack gap="xs" align="center"> <Stack gap="xs" align="center">
<Text size="md" fw={500}> <Text size="md" fw={500}>Seret gambar atau klik untuk memilih file</Text>
Seret gambar atau klik untuk memilih file <Text size="sm" c="dimmed">Maksimal 5MB, format gambar wajib</Text>
</Text>
<Text size="sm" c="dimmed">
Maksimal 5MB, format gambar wajib
</Text>
</Stack> </Stack>
</Group> </Group>
</Dropzone> </Dropzone>
{previewImage && ( {previewImage && (
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}> <Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
<Image <Image src={previewImage} alt="Preview Gambar" radius="md" style={{ maxHeight: 300, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }} loading="lazy" />
src={previewImage}
alt="Preview Gambar"
radius="md"
style={{
maxHeight: 300,
objectFit: 'contain',
border: `1px solid ${colors['blue-button']}`
}}
loading="lazy"
/>
</Box> </Box>
)} )}
</Box> </Box>
{/* Document Dropzone */}
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>Dokumen APBDes</Text>
Dokumen APBDes
</Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={handleDrop('doc')}
const selectedFile = files[0];
if (selectedFile) {
setDocFile(selectedFile);
setPreviewDoc(URL.createObjectURL(selectedFile));
}
}}
onReject={() => toast.error('File tidak valid, gunakan format dokumen')} onReject={() => toast.error('File tidak valid, gunakan format dokumen')}
maxSize={10 * 1024 ** 2} // 10MB maxSize={10 * 1024 ** 2}
accept={{ accept={{
'application/pdf': ['.pdf'], 'application/pdf': ['.pdf'],
'application/msword': ['.doc'], 'application/msword': ['.doc'],
@@ -233,40 +189,19 @@ function EditAPBDes() {
p="xl" p="xl"
> >
<Group justify="center" gap="xl" mih={150}> <Group justify="center" gap="xl" mih={150}>
<Dropzone.Accept> <Dropzone.Accept><IconUpload size={48} color={colors['blue-button']} stroke={1.5} /></Dropzone.Accept>
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} /> <Dropzone.Reject><IconX size={48} color="red" stroke={1.5} /></Dropzone.Reject>
</Dropzone.Accept> <Dropzone.Idle><IconFile size={48} color="#868e96" stroke={1.5} /></Dropzone.Idle>
<Dropzone.Reject>
<IconX size={48} color="red" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconFile size={48} color="#868e96" stroke={1.5} />
</Dropzone.Idle>
<Stack gap="xs" align="center"> <Stack gap="xs" align="center">
<Text size="md" fw={500}> <Text size="md" fw={500}>Seret dokumen atau klik untuk memilih file</Text>
Seret dokumen atau klik untuk memilih file <Text size="sm" c="dimmed">Maksimal 10MB, format PDF/DOC/DOCX/XLS/XLSX</Text>
</Text>
<Text size="sm" c="dimmed">
Maksimal 10MB, format PDF/DOC/DOCX/XLS/XLSX
</Text>
</Stack> </Stack>
</Group> </Group>
</Dropzone> </Dropzone>
{previewDoc && ( {previewDoc && (
<Box mt="sm"> <Box mt="sm">
<Text size="sm" c="dimmed" mb="xs"> <Text size="sm" c="dimmed" mb="xs">Dokumen terpilih: {docFile?.name || 'Dokumen'}</Text>
Dokumen terpilih: {docFile?.name || 'Dokumen'} <Button component="a" href={previewDoc} target="_blank" rel="noopener noreferrer" variant="light" leftSection={<IconFile size={16} />} size="sm">
</Text>
<Button
component="a"
href={previewDoc}
target="_blank"
rel="noopener noreferrer"
variant="light"
leftSection={<IconFile size={16} />}
size="sm"
>
Lihat Dokumen Lihat Dokumen
</Button> </Button>
</Box> </Box>

View File

@@ -1,11 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi'; import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
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, useCallback } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -15,28 +16,25 @@ export default function EditKategoriDesaAntiKorupsi() {
const id = params?.id as string; const id = params?.id as string;
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi); const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi);
const [formData, setFormData] = useState({ // state lokal untuk form
name: "", const [formData, setFormData] = useState({ name: '' });
});
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
// load data kategori saat mount atau id berubah
useEffect(() => { useEffect(() => {
const loadKategori = async () => {
if (!id) return; if (!id) return;
setIsLoading(true);
const loadKategori = async () => {
setIsLoading(true);
try { try {
const data = await stateKategori.edit.load(id); const data = await stateKategori.edit.load(id);
if (data) { if (data) {
stateKategori.edit.id = id; stateKategori.edit.id = id;
setFormData({ setFormData({ name: data.name || '' });
name: data.name || '',
});
} }
} catch (error) { } catch (err) {
console.error("Error loading kategori desa anti korupsi:", error); console.error(err);
toast.error("Gagal memuat data kategori desa anti korupsi"); toast.error('Gagal memuat data kategori desa anti korupsi');
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -45,31 +43,35 @@ export default function EditKategoriDesaAntiKorupsi() {
loadKategori(); loadKategori();
}, [id]); }, [id]);
const handleSubmit = async () => { // handler controlled input
if (!formData.name.trim()) { const handleChange = useCallback(
return toast.error('Nama kategori tidak boleh kosong'); (field: keyof typeof formData, value: string) => {
} setFormData(prev => ({ ...prev, [field]: value }));
},
[]
);
// submit form
const handleSubmit = useCallback(async () => {
if (!formData.name.trim()) return toast.error('Nama kategori tidak boleh kosong');
try { try {
setIsLoading(true); setIsLoading(true);
stateKategori.edit.form = {
name: formData.name.trim(),
};
if (!stateKategori.edit.id) { // update global state hanya saat submit
stateKategori.edit.id = id; stateKategori.edit.form = { name: formData.name.trim() };
} if (!stateKategori.edit.id) stateKategori.edit.id = id;
await stateKategori.edit.update(); await stateKategori.edit.update();
toast.success('Kategori berhasil diperbarui'); toast.success('Kategori berhasil diperbarui');
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi"); router.push('/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi');
} catch (error) { } catch (err) {
console.error("Error updating kategori desa anti korupsi:", error); console.error(err);
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui kategori'); toast.error(err instanceof Error ? err.message : 'Gagal memperbarui kategori');
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; }, [formData.name, id, router, stateKategori.edit]);
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -96,8 +98,8 @@ export default function EditKategoriDesaAntiKorupsi() {
<TextInput <TextInput
label="Nama Kategori" label="Nama Kategori"
placeholder="Masukkan nama kategori" placeholder="Masukkan nama kategori"
defaultValue={formData.name} value={formData.name} // controlled
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleChange('name', e.currentTarget.value)}
required required
disabled={isLoading} disabled={isLoading}
/> />

View File

@@ -1,18 +1,19 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client'; 'use client';
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react'; import { IconArrowBack, IconFile, 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, ChangeEvent } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { Dropzone } from '@mantine/dropzone';
import { useShallowEffect } from '@mantine/hooks';
import colors from '@/con/colors'; import colors from '@/con/colors';
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi'; import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
import ApiFetch from '@/lib/api-fetch'; import ApiFetch from '@/lib/api-fetch';
import { Dropzone } from '@mantine/dropzone';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import { useShallowEffect } from '@mantine/hooks';
interface FormDesaAntiKorupsi { interface FormDesaAntiKorupsi {
name: string; name: string;
@@ -23,9 +24,6 @@ interface FormDesaAntiKorupsi {
export default function EditDesaAntiKorupsi() { export default function EditDesaAntiKorupsi() {
const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi); const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi);
const [previewFile, setPreviewFile] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isLoading, setIsLoading] = useState(false);
const params = useParams(); const params = useParams();
const router = useRouter(); const router = useRouter();
@@ -36,10 +34,16 @@ export default function EditDesaAntiKorupsi() {
fileId: '', fileId: '',
}); });
const [previewFile, setPreviewFile] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isLoading, setIsLoading] = useState(false);
// Load kategori
useShallowEffect(() => { useShallowEffect(() => {
korupsiState.kategoriDesaAntiKorupsi.findMany.load(); korupsiState.kategoriDesaAntiKorupsi.findMany.load();
}, []); }, []);
// Load data existing
useEffect(() => { useEffect(() => {
const loadDesaAntiKorupsi = async () => { const loadDesaAntiKorupsi = async () => {
const id = params?.id as string; const id = params?.id as string;
@@ -47,15 +51,10 @@ export default function EditDesaAntiKorupsi() {
try { try {
const data = await desaAntiKorupsiState.edit.load(id); const data = await desaAntiKorupsiState.edit.load(id);
if (data) { if (!data) return;
desaAntiKorupsiState.edit.id = id;
desaAntiKorupsiState.edit.form = { desaAntiKorupsiState.edit.id = id;
name: data.name, desaAntiKorupsiState.edit.form = { ...data };
deskripsi: data.deskripsi,
kategoriId: data.kategoriId,
fileId: data.fileId,
};
setFormData({ setFormData({
name: data.name, name: data.name,
@@ -64,12 +63,9 @@ export default function EditDesaAntiKorupsi() {
fileId: data.fileId, fileId: data.fileId,
}); });
if (data?.file?.link) { if (data.file?.link) setPreviewFile(data.file.link);
setPreviewFile(data.file.link); } catch (err) {
} console.error(err);
}
} catch (error) {
console.error('Error loading data:', error);
toast.error('Gagal memuat data Desa Anti Korupsi'); toast.error('Gagal memuat data Desa Anti Korupsi');
} }
}; };
@@ -77,42 +73,47 @@ export default function EditDesaAntiKorupsi() {
loadDesaAntiKorupsi(); loadDesaAntiKorupsi();
}, [params?.id]); }, [params?.id]);
// Generic handler input
const handleInputChange = (key: keyof FormDesaAntiKorupsi) => (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | string | null) => {
const value = typeof e === 'string' ? e : e?.target?.value ?? '';
setFormData((prev) => ({ ...prev, [key]: value || '' }));
};
// Special handler for Select component
const handleSelectChange = (key: keyof FormDesaAntiKorupsi) => (value: string | null) => {
setFormData((prev) => ({ ...prev, [key]: value || '' }));
};
const handleDrop = (files: File[]) => {
if (!files.length) return;
const selectedFile = files[0];
setFile(selectedFile);
setPreviewFile(URL.createObjectURL(selectedFile));
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!formData.name) { if (!formData.name) return toast.warn('Masukkan judul dokumen');
return toast.warn('Masukkan judul dokumen'); if (!formData.kategoriId) return toast.warn('Pilih kategori dokumen');
}
if (!formData.kategoriId) {
return toast.warn('Pilih kategori dokumen');
}
setIsLoading(true); setIsLoading(true);
try { try {
// Update global state with form data // Update global state
desaAntiKorupsiState.edit.form = { desaAntiKorupsiState.edit.form = { ...desaAntiKorupsiState.edit.form, ...formData };
...desaAntiKorupsiState.edit.form,
...formData,
kategoriId: formData.kategoriId || '',
};
// Upload new file if exists // Upload file jika ada
if (file) { if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
file,
name: file.name
});
const uploaded = res.data?.data; const uploaded = res.data?.data;
if (!uploaded?.id) throw new Error('Gagal mengunggah dokumen');
if (!uploaded?.id) {
throw new Error('Gagal mengunggah dokumen');
}
desaAntiKorupsiState.edit.form.fileId = uploaded.id; desaAntiKorupsiState.edit.form.fileId = uploaded.id;
} }
await desaAntiKorupsiState.edit.update(); await desaAntiKorupsiState.edit.update();
toast.success('Data berhasil diperbarui'); toast.success('Data berhasil diperbarui');
router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi'); router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi');
} catch (error) { } catch (err) {
console.error('Error updating data:', error); console.error(err);
toast.error('Terjadi kesalahan saat memperbarui data'); toast.error('Terjadi kesalahan saat memperbarui data');
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -144,8 +145,8 @@ export default function EditDesaAntiKorupsi() {
<TextInput <TextInput
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukkan judul dokumen" placeholder="Masukkan judul dokumen"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={handleInputChange('name')}
required required
/> />
@@ -153,23 +154,18 @@ export default function EditDesaAntiKorupsi() {
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Deskripsi Deskripsi
</Text> </Text>
<EditEditor <EditEditor value={formData.deskripsi} onChange={handleInputChange('deskripsi')} />
value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
/>
</Box> </Box>
<Select <Select
label="Kategori" label="Kategori"
placeholder="Pilih kategori" placeholder="Pilih kategori"
value={formData.kategoriId} value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || '' })} onChange={handleSelectChange('kategoriId')}
data={ data={korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
value: v.id, value: v.id,
label: v.name, label: v.name,
})) || [] })) || []}
}
required required
searchable searchable
clearable clearable
@@ -180,13 +176,7 @@ export default function EditDesaAntiKorupsi() {
Dokumen Dokumen
</Text> </Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={handleDrop}
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewFile(URL.createObjectURL(selectedFile));
}
}}
onReject={() => toast.error('File tidak valid, gunakan format dokumen')} onReject={() => toast.error('File tidak valid, gunakan format dokumen')}
maxSize={5 * 1024 ** 2} maxSize={5 * 1024 ** 2}
accept={{ accept={{
@@ -229,12 +219,7 @@ export default function EditDesaAntiKorupsi() {
width: '100%', width: '100%',
}} }}
> >
<iframe <iframe src={previewFile} width="100%" height="100%" style={{ border: 'none' }} />
src={previewFile}
width="100%"
height="100%"
style={{ border: 'none' }}
/>
</Box> </Box>
</Box> </Box>
)} )}

View File

@@ -1,10 +1,21 @@
'use client' 'use client'
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { useRouter, useParams } from 'next/navigation'; import { useRouter, useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Title, TextInput, Text, Select, Tooltip } from '@mantine/core'; import {
Box,
Button,
Group,
Paper,
Stack,
Title,
TextInput,
Text,
Select,
Tooltip,
} from '@mantine/core';
import { IconArrowBack, IconDeviceFloppy } from '@tabler/icons-react'; import { IconArrowBack, IconDeviceFloppy } from '@tabler/icons-react';
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan'; import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -18,38 +29,33 @@ interface FormResponden {
} }
function EditResponden() { function EditResponden() {
const router = useRouter() const router = useRouter();
const params = useParams() as { id: string } const params = useParams() as { id: string };
const state = useProxy(indeksKepuasanState.responden) const state = useProxy(indeksKepuasanState.responden);
const id = params.id const id = params.id;
const [formData, setFormData] = useState<FormResponden>({ const [formData, setFormData] = useState<FormResponden>({
name: '', name: '',
tanggal: '', tanggal: '',
jenisKelaminId: '', jenisKelaminId: '',
ratingId: '', ratingId: '',
kelompokUmurId: '', kelompokUmurId: '',
}) });
useEffect(() => {
// Helper untuk load pilihan select
const loadSelectOptions = useCallback(() => {
indeksKepuasanState.jenisKelaminResponden.findMany.load(); indeksKepuasanState.jenisKelaminResponden.findMany.load();
indeksKepuasanState.pilihanRatingResponden.findMany.load(); indeksKepuasanState.pilihanRatingResponden.findMany.load();
indeksKepuasanState.kelompokUmurResponden.findMany.load(); indeksKepuasanState.kelompokUmurResponden.findMany.load();
}, []);
const loadResponden = async () => { // Load data responden
const id = params?.id as string; const loadResponden = useCallback(async () => {
if (!id) return; if (!id) return;
try { try {
const data = await state.update.load(id); const data = await state.update.load(id);
if (data) { if (!data) return;
state.update.id = id;
state.update.form = {
name: data.name,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
};
setFormData({ setFormData({
name: data.name, name: data.name,
@@ -58,24 +64,58 @@ function EditResponden() {
ratingId: data.ratingId, ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId, kelompokUmurId: data.kelompokUmurId,
}); });
}
} catch (error) { } catch (error) {
console.error("Error loading program penghijauan:", error); console.error("Error loading responden:", error);
toast.error("Gagal memuat data program penghijauan"); toast.error("Gagal memuat data responden");
}
} }
}, [id]);
useEffect(() => {
loadSelectOptions();
loadResponden(); loadResponden();
}, [params?.id]); }, [loadSelectOptions, loadResponden]);
const handleSubmit = async () => { const handleSubmit = async () => {
state.update.id = id; state.update.id = id;
state.update.form = { ...formData }; // <-- sinkronisasi manual state.update.form = { ...formData }; // sinkronisasi manual
await state.update.submit(); await state.update.submit();
router.push('/admin/landing-page/indeks-kepuasan-masyarakat/responden') router.push('/admin/landing-page/indeks-kepuasan-masyarakat/responden');
} };
// Reusable Select component
const ControlledSelect = ({
label,
value,
onChange,
options,
error,
placeholder = 'Pilih',
loading = false,
}: {
label: string;
value: string;
onChange: (val: string) => void;
options: { value: string; label: string }[];
error?: string;
placeholder?: string;
loading?: boolean;
}) => {
return (
<Select
label={<Text fw="bold" fz="sm" mb={4}>{label}</Text>}
value={value}
onChange={(val) => onChange(val || '')}
data={options}
placeholder={placeholder}
disabled={loading}
clearable
searchable
required
radius="md"
error={error}
/>
);
};
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -101,111 +141,56 @@ function EditResponden() {
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
label="Nama Responden" label="Nama Responden"
type='text'
placeholder="Masukkan nama responden" placeholder="Masukkan nama responden"
defaultValue={formData.name} value={formData.name}
onChange={(val) => { onChange={(e) => setFormData({ ...formData, name: e.currentTarget.value })}
setFormData({
...formData,
name: val.currentTarget.value
})
}}
radius="md" radius="md"
required required
/> />
<TextInput <TextInput
label="Tanggal" label="Tanggal"
type="date" type="date"
placeholder='Pilih tanggal' value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
defaultValue={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''} onChange={(e) => setFormData({ ...formData, tanggal: e.currentTarget.value })}
onChange={(e) => {
const selectedDate = e.currentTarget.value;
setFormData({
...formData,
tanggal: selectedDate,
});
}}
radius="md" radius="md"
required required
/> />
<Select
key="jenisKelamin"
label={
<Text fw="bold" fz="sm" mb={4}>
Jenis Kelamin
</Text>
}
placeholder="Pilih jenis kelamin"
value={formData.jenisKelaminId}
onChange={(val) => setFormData({ ...formData, jenisKelaminId: val || "" })}
data={
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({
value: v.id || '',
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
}))
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
clearable
searchable
required
radius="md"
error={!formData.jenisKelaminId ? "Pilih jenis kelamin" : undefined}
/>
<Select
key="rating"
value={formData.ratingId}
onChange={(val) => setFormData({ ...formData, ratingId: val || "" })}
label={
<Text fw="bold" fz="sm" mb={4}>
Rating
</Text>
}
placeholder='Pilih rating'
data={
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({
value: v.id || '',
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
}))
}
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
clearable
searchable
required
radius="md"
error={!formData.ratingId ? "Pilih rating" : undefined}
/>
<Select <ControlledSelect
key={"kelompokUmur"} label="Jenis Kelamin"
value={formData.kelompokUmurId} value={formData.jenisKelaminId}
onChange={(val) => setFormData({ ...formData, kelompokUmurId: val || "" })} onChange={(val) => setFormData({ ...formData, jenisKelaminId: val })}
label={<Text fw={"bold"} fz={"sm"}>Kelompok Umur</Text>} options={(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
placeholder='Pilih kelompok umur'
data={
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean) .filter(Boolean)
.map((v) => ({ .map((v) => ({ value: v.id || '', label: v.name || 'Tanpa Nama' }))}
value: v.id || '', loading={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama' error={!formData.jenisKelaminId ? 'Pilih jenis kelamin' : undefined}
})) />
}
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading} <ControlledSelect
clearable label="Rating"
searchable value={formData.ratingId}
required onChange={(val) => setFormData({ ...formData, ratingId: val })}
error={!formData.kelompokUmurId ? "Pilih kelompok umur" : undefined} options={(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({ value: v.id || '', label: v.name || 'Tanpa Nama' }))}
loading={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
error={!formData.ratingId ? 'Pilih rating' : undefined}
/>
<ControlledSelect
label="Kelompok Umur"
value={formData.kelompokUmurId}
onChange={(val) => setFormData({ ...formData, kelompokUmurId: val })}
options={(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({ value: v.id || '', label: v.name || 'Tanpa Nama' }))}
loading={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
error={!formData.kelompokUmurId ? 'Pilih kelompok umur' : undefined}
/> />
<Group justify="flex-end" mt="md"> <Group justify="flex-end" mt="md">
<Button <Button variant="light" color="red" onClick={() => router.back()}>
variant="light"
color="red"
onClick={() => router.back()}
>
Batal Batal
</Button> </Button>
<Button <Button

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import prestasiState from '@/app/admin/(dashboard)/_state/landing-page/prestasi-desa'; import prestasiState from '@/app/admin/(dashboard)/_state/landing-page/prestasi-desa';
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';
@@ -15,57 +16,51 @@ function EditKategoriPrestasi() {
const id = params?.id as string; const id = params?.id as string;
const stateKategori = useProxy(prestasiState.kategoriPrestasi); const stateKategori = useProxy(prestasiState.kategoriPrestasi);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({ name: '' });
name: "", const [loading, setLoading] = useState(false);
});
// Load data kategori prestasi saat component mount
useEffect(() => { useEffect(() => {
const loadKategoriprestasi = async () => {
if (!id) return; if (!id) return;
const loadKategori = async () => {
setLoading(true);
try { try {
const data = await stateKategori.edit.load(id); const data = await stateKategori.edit.load(id);
if (data) { if (data) {
// pastikan id-nya masuk ke state edit
stateKategori.edit.id = id; stateKategori.edit.id = id;
setFormData({ setFormData({ name: data.name || '' });
name: data.name || '',
});
} }
} catch (error) { } catch (err) {
console.error("Error loading kategori prestasi desa:", error); console.error(err);
toast.error("Gagal memuat data kategori prestasi desa"); toast.error('Gagal memuat data kategori prestasi desa');
} finally {
setLoading(false);
} }
}; };
loadKategoriprestasi(); loadKategori();
}, [id]); }, [id]);
// Submit: update global state hanya saat submit
const handleSubmit = async () => { const handleSubmit = async () => {
try {
if (!formData.name.trim()) { if (!formData.name.trim()) {
toast.error('Nama kategori prestasi tidak boleh kosong'); toast.error('Nama kategori prestasi tidak boleh kosong');
return; return;
} }
stateKategori.edit.form = { try {
name: formData.name.trim(), stateKategori.edit.form = { name: formData.name.trim() };
}; stateKategori.edit.id ||= id; // fallback jika id belum ada
// Safety check tambahan: pastikan ID tidak kosong
if (!stateKategori.edit.id) {
stateKategori.edit.id = id; // fallback
}
const success = await stateKategori.edit.update(); const success = await stateKategori.edit.update();
if (success) { if (success) {
router.push("/admin/landing-page/prestasi-desa/kategori-prestasi-desa"); toast.success('Kategori prestasi berhasil diperbarui');
router.push('/admin/landing-page/prestasi-desa/kategori-prestasi-desa');
} }
} catch (error) { } catch (err) {
console.error("Error updating kategori prestasi desa:", error); console.error(err);
// toast akan ditampilkan dari fungsi update toast.error('Gagal memperbarui kategori prestasi desa');
} }
}; };
@@ -94,9 +89,10 @@ function EditKategoriPrestasi() {
<TextInput <TextInput
label="Nama Kategori Prestasi" label="Nama Kategori Prestasi"
placeholder="Masukkan nama kategori prestasi" placeholder="Masukkan nama kategori prestasi"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required required
disabled={loading}
/> />
<Group justify="right"> <Group justify="right">
@@ -104,6 +100,7 @@ function EditKategoriPrestasi() {
onClick={handleSubmit} onClick={handleSubmit}
radius="md" radius="md"
size="md" size="md"
loading={loading}
style={{ style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`, background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff', color: '#fff',

View File

@@ -1,19 +1,17 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { toast } from 'react-toastify';
import colors from '@/con/colors'; import colors from '@/con/colors';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import prestasiState from '@/app/admin/(dashboard)/_state/landing-page/prestasi-desa'; import prestasiState from '@/app/admin/(dashboard)/_state/landing-page/prestasi-desa';
import ApiFetch from '@/lib/api-fetch'; import ApiFetch from '@/lib/api-fetch';
import { Dropzone } from '@mantine/dropzone';
import { toast } from 'react-toastify';
interface FormPrestasiDesa { interface FormPrestasiDesa {
name: string; name: string;
@@ -22,31 +20,31 @@ interface FormPrestasiDesa {
imageId: string; imageId: string;
} }
function EditPrestasiDesa() { export default function EditPrestasiDesa() {
const editState = useProxy(prestasiState.prestasiDesa) const editState = useProxy(prestasiState.prestasiDesa);
const [previewFile, setPreviewFile] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const params = useParams()
const router = useRouter()
const [formData, setFormData] = useState<FormPrestasiDesa>({ const [formData, setFormData] = useState<FormPrestasiDesa>({
name: '', name: '',
deskripsi: '', deskripsi: '',
kategoriId: '', kategoriId: '',
imageId: '', imageId: '',
}) });
const [previewFile, setPreviewFile] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const params = useParams();
const router = useRouter();
// Load kategori & prestasi desa
useEffect(() => { useEffect(() => {
prestasiState.kategoriPrestasi.findMany.load() prestasiState.kategoriPrestasi.findMany.load();
const loadDesaAntiKorupsi = async () => {
const loadPrestasi = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
try { try {
const data = await editState.edit.load(id); const data = await editState.edit.load(id);
if (data) { if (data) {
// ⬇️ FIX PENTING: tambahkan ini
editState.edit.id = id; editState.edit.id = id;
editState.edit.form = { editState.edit.form = {
name: data.name, name: data.name,
deskripsi: data.deskripsi, deskripsi: data.deskripsi,
@@ -61,51 +59,37 @@ function EditPrestasiDesa() {
imageId: data.imageId, imageId: data.imageId,
}); });
if (data?.image?.link) { if (data.image?.link) setPreviewFile(data.image.link);
setPreviewFile(data.image.link)
}
} }
} catch (error) { } catch (error) {
console.error("Error loading prestasi desa:", error); console.error('Error loading prestasi desa:', error);
toast.error("Gagal memuat data prestasi desa"); toast.error('Gagal memuat data prestasi desa');
} }
}
loadDesaAntiKorupsi();
}, [params?.id]);
const handleSubmit = async () => {
try {
// Update global state with form data
editState.edit.form = {
...editState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
kategoriId: formData.kategoriId || '',
imageId: formData.imageId // Keep existing imageId if not changed
}; };
// Jika ada file baru, upload loadPrestasi();
}, [params?.id]);
const handleSubmit = async () => {
try {
// Jika ada file baru, upload dulu
let imageId = formData.imageId;
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) return toast.error('Gagal upload gambar');
if (!uploaded?.id) { imageId = uploaded.id;
return toast.error("Gagal upload gambar");
}
// Update imageId in global state
editState.edit.form.imageId = uploaded.id;
} }
// Update global state sekaligus
editState.edit.form = { ...formData, imageId };
await editState.edit.update(); await editState.edit.update();
toast.success("prestasi desa berhasil diperbarui!");
router.push("/admin/landing-page/prestasi-desa/list-prestasi-desa"); toast.success('Prestasi desa berhasil diperbarui!');
router.push('/admin/landing-page/prestasi-desa/list-prestasi-desa');
} catch (error) { } catch (error) {
console.error("Error updating prestasi desa:", error); console.error('Error updating prestasi desa:', error);
toast.error("Terjadi kesalahan saat memperbarui prestasi desa"); toast.error('Terjadi kesalahan saat memperbarui prestasi desa');
} }
}; };
@@ -117,71 +101,35 @@ function EditPrestasiDesa() {
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />
</Button> </Button>
</Tooltip> </Tooltip>
<Title order={4} ml="sm" c="dark"> <Title order={4} ml="sm" c="dark">Edit Prestasi Desa</Title>
Edit Prestasi Desa
</Title>
</Group> </Group>
<Paper <Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="lg" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
label="Judul Prestasi" label="Judul Prestasi"
placeholder="Masukkan judul prestasi" placeholder="Masukkan judul prestasi"
defaultValue={formData.name} value={formData.name}
onChange={(val) => { onChange={(e) => setFormData({ ...formData, name: e.target.value })}
setFormData({
...formData,
name: val.target.value
});
}}
required required
/> />
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>Deskripsi</Text>
Deskripsi <EditEditor value={formData.deskripsi} onChange={(val) => setFormData({ ...formData, deskripsi: val })} />
</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
setFormData({
...formData,
deskripsi: val
});
}}
/>
</Box> </Box>
<Select <Select
label="Kategori" label="Kategori"
placeholder="Pilih kategori" placeholder="Pilih kategori"
value={formData.kategoriId} value={formData.kategoriId}
onChange={(val) => { onChange={(val) => setFormData({ ...formData, kategoriId: val ?? '' })}
setFormData({ data={prestasiState.kategoriPrestasi.findMany.data?.map((v) => ({ value: v.id, label: v.name })) || []}
...formData,
kategoriId: val ?? ""
});
}}
data={
prestasiState.kategoriPrestasi.findMany.data?.map((v) => ({
value: v.id,
label: v.name,
})) || []
}
required required
/> />
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>Gambar Prestasi</Text>
Gambar Prestasi
</Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {
const selectedFile = files[0]; const selectedFile = files[0];
@@ -197,22 +145,12 @@ function EditPrestasiDesa() {
p="xl" p="xl"
> >
<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} /></Dropzone.Accept>
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} /> <Dropzone.Reject><IconX size={48} color="red" stroke={1.5} /></Dropzone.Reject>
</Dropzone.Accept> <Dropzone.Idle><IconPhoto size={48} color="#868e96" stroke={1.5} /></Dropzone.Idle>
<Dropzone.Reject>
<IconX size={48} color="red" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={48} color="#868e96" stroke={1.5} />
</Dropzone.Idle>
<Stack gap="xs" align="center"> <Stack gap="xs" align="center">
<Text size="md" fw={500}> <Text size="md" fw={500}>Seret gambar atau klik untuk memilih file</Text>
Seret gambar atau klik untuk memilih file <Text size="sm" c="dimmed">Maksimal 5MB, format gambar wajib</Text>
</Text>
<Text size="sm" c="dimmed">
Maksimal 5MB, format gambar wajib
</Text>
</Stack> </Stack>
</Group> </Group>
</Dropzone> </Dropzone>
@@ -224,7 +162,7 @@ function EditPrestasiDesa() {
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>
)} )}
@@ -249,6 +187,3 @@ function EditPrestasiDesa() {
</Box> </Box>
); );
} }
export default EditPrestasiDesa;

View File

@@ -14,7 +14,7 @@ import {
TextInput, TextInput,
Title, Title,
Tooltip, Tooltip,
Image Image,
} from "@mantine/core"; } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone"; import { Dropzone } from "@mantine/dropzone";
import { IconArrowBack, IconDeviceFloppy, IconPhoto, IconUpload, IconX } from "@tabler/icons-react"; import { IconArrowBack, IconDeviceFloppy, IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
@@ -23,39 +23,37 @@ import { useEffect, useState } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { useProxy } from "valtio/utils"; import { useProxy } from "valtio/utils";
function EditKolaborasiInovasi() { export default function EditKolaborasiInovasi() {
const sdgsState = useProxy(sdgsDesa); const sdgsState = useProxy(sdgsDesa);
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const [formData, setFormData] = useState({
name: "",
jumlah: "",
imageId: "",
});
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({
name: sdgsState.edit.form.name || '',
jumlah: sdgsState.edit.form.jumlah || '',
imageId: sdgsState.edit.form.imageId || ''
});
// Load sdgs desa by id saat pertama kali // Load data sdgs desa by id
useEffect(() => { useEffect(() => {
const loadKolaborasi = async () => { const loadKolaborasi = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
try { try {
const data = await sdgsState.edit.load(id); // akses langsung, bukan dari proxy const data = await sdgsState.edit.load(id);
if (data) { if (data) {
setFormData({ setFormData({
name: data.name || '', name: data.name || "",
jumlah: data.jumlah || '', jumlah: data.jumlah || "",
imageId: data.imageId || '', imageId: data.imageId || "",
}); });
if (data.image) { if (data.image?.link) {
if (data?.image?.link) {
setPreviewImage(data.image.link); setPreviewImage(data.image.link);
} }
} }
}
} catch (error) { } catch (error) {
console.error("Error loading sdgs desa:", error); console.error("Error loading sdgs desa:", error);
toast.error("Gagal memuat data sdgs desa"); toast.error("Gagal memuat data sdgs desa");
@@ -65,31 +63,26 @@ function EditKolaborasiInovasi() {
loadKolaborasi(); loadKolaborasi();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { const handleInputChange = (field: keyof typeof formData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
try {
// edit global state with form data
sdgsState.edit.form = {
...sdgsState.edit.form,
name: formData.name,
jumlah: formData.jumlah,
imageId: formData.imageId // Keep existing imageId if not changed
}; };
// Jika ada file baru, upload const handleSubmit = async () => {
try {
let imageId = formData.imageId;
// Upload file baru jika 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) return toast.error("Gagal upload gambar");
if (!uploaded?.id) { imageId = uploaded.id;
return toast.error("Gagal upload gambar");
}
// edit imageId in global state
sdgsState.edit.form.imageId = uploaded.id;
} }
// Update global state hanya saat submit
sdgsState.edit.form = { ...sdgsState.edit.form, ...formData, imageId };
await sdgsState.edit.update(); await sdgsState.edit.update();
toast.success("sdgs desa berhasil diperbarui!"); toast.success("sdgs desa berhasil diperbarui!");
router.push("/admin/landing-page/sdgs-desa"); router.push("/admin/landing-page/sdgs-desa");
} catch (error) { } catch (error) {
@@ -99,11 +92,11 @@ function EditKolaborasiInovasi() {
}; };
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>
<Title order={4} ml="sm" c="dark"> <Title order={4} ml="sm" c="dark">
@@ -112,12 +105,12 @@ function EditKolaborasiInovasi() {
</Group> </Group>
<Paper <Paper
w={{ base: '100%', md: '50%' }} w={{ base: "100%", md: "50%" }}
bg={colors['white-1']} bg={colors["white-1"]}
p="lg" p="lg"
radius="md" radius="md"
shadow="sm" shadow="sm"
style={{ border: '1px solid #e0e0e0' }} style={{ border: "1px solid #e0e0e0" }}
> >
<Stack gap="md"> <Stack gap="md">
<Box> <Box>
@@ -132,15 +125,15 @@ function EditKolaborasiInovasi() {
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"
p="xl" p="xl"
> >
<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} />
@@ -160,12 +153,12 @@ function EditKolaborasiInovasi() {
</Dropzone> </Dropzone>
{previewImage && ( {previewImage && (
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}> <Box mt="sm" style={{ display: "flex", justifyContent: "center" }}>
<Image <Image
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>
@@ -175,16 +168,16 @@ function EditKolaborasiInovasi() {
<TextInput <TextInput
label="Nama Sdgs Desa" label="Nama Sdgs Desa"
placeholder="Masukkan nama Sdgs Desa" placeholder="Masukkan nama Sdgs Desa"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleInputChange("name", e.target.value)}
required required
/> />
<TextInput <TextInput
label="Jumlah" label="Jumlah"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
defaultValue={formData.jumlah} value={formData.jumlah}
onChange={(e) => setFormData({ ...formData, jumlah: e.target.value })} onChange={(e) => handleInputChange("jumlah", e.target.value)}
required required
type="number" type="number"
/> />
@@ -197,9 +190,9 @@ function EditKolaborasiInovasi() {
radius="md" radius="md"
size="md" size="md"
style={{ style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`, background: `linear-gradient(135deg, ${colors["blue-button"]}, #4facfe)`,
color: '#fff', color: "#fff",
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)', boxShadow: "0 4px 15px rgba(79, 172, 254, 0.4)",
}} }}
> >
Simpan Simpan
@@ -210,5 +203,3 @@ function EditKolaborasiInovasi() {
</Box> </Box>
); );
} }
export default EditKolaborasiInovasi;

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa'; import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -46,7 +47,7 @@ type IconKey =
| 'pohon' | 'pohon'
| 'air'; | 'air';
function EditDataLingkunganDesa() { export default function EditDataLingkunganDesa() {
const stateDataLingkunganDesa = useProxy(dataLingkunganDesaState); const stateDataLingkunganDesa = useProxy(dataLingkunganDesaState);
const params = useParams(); const params = useParams();
const router = useRouter(); const router = useRouter();
@@ -58,8 +59,9 @@ function EditDataLingkunganDesa() {
icon: '', icon: '',
}); });
// Load data saat komponen mount
useEffect(() => { useEffect(() => {
const loadProgramKreatif = async () => { const loadData = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -67,14 +69,6 @@ function EditDataLingkunganDesa() {
const data = await stateDataLingkunganDesa.update.load(id); const data = await stateDataLingkunganDesa.update.load(id);
if (data) { if (data) {
stateDataLingkunganDesa.update.id = id; stateDataLingkunganDesa.update.id = id;
stateDataLingkunganDesa.update.form = {
name: data.name,
deskripsi: data.deskripsi,
jumlah: data.jumlah,
icon: data.icon,
};
setFormData({ setFormData({
name: data.name, name: data.name,
deskripsi: data.deskripsi, deskripsi: data.deskripsi,
@@ -88,13 +82,14 @@ function EditDataLingkunganDesa() {
} }
}; };
loadProgramKreatif(); loadData();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// Update global state hanya saat submit
stateDataLingkunganDesa.update.form = { stateDataLingkunganDesa.update.form = {
...stateDataLingkunganDesa.update.form, ...formData,
name: formData.name.trim(), name: formData.name.trim(),
deskripsi: formData.deskripsi.trim(), deskripsi: formData.deskripsi.trim(),
jumlah: formData.jumlah.trim(), jumlah: formData.jumlah.trim(),
@@ -132,27 +127,21 @@ function EditDataLingkunganDesa() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
label={<Text fz="sm" fw="bold">Nama Data Lingkungan Desa</Text>} label={<Text fz="sm" fw="bold">Nama Data Lingkungan Desa</Text>}
placeholder="Masukkan nama data lingkungan desa" placeholder="Masukkan nama data lingkungan desa"
onChange={(val) => onChange={(e) =>
setFormData({ setFormData((prev) => ({ ...prev, name: e.target.value }))
...formData,
name: val.target.value,
})
} }
required required
/> />
<TextInput <TextInput
defaultValue={formData.jumlah} value={formData.jumlah}
label={<Text fz="sm" fw="bold">Jumlah Data Lingkungan Desa</Text>} label={<Text fz="sm" fw="bold">Jumlah Data Lingkungan Desa</Text>}
placeholder="Masukkan jumlah data lingkungan desa" placeholder="Masukkan jumlah data lingkungan desa"
onChange={(val) => onChange={(e) =>
setFormData({ setFormData((prev) => ({ ...prev, jumlah: e.target.value }))
...formData,
jumlah: val.target.value,
})
} }
required required
/> />
@@ -163,10 +152,9 @@ function EditDataLingkunganDesa() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
stateDataLingkunganDesa.update.form.deskripsi = htmlContent; }
}}
/> />
</Box> </Box>
@@ -176,10 +164,9 @@ function EditDataLingkunganDesa() {
</Text> </Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={formData.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={(value) =>
setFormData((prev) => ({ ...prev, icon: value })); setFormData((prev) => ({ ...prev, icon: value }))
stateDataLingkunganDesa.update.form.icon = value; }
}}
/> />
</Box> </Box>
@@ -202,5 +189,3 @@ function EditDataLingkunganDesa() {
</Box> </Box>
); );
} }
export default EditDataLingkunganDesa;

View File

@@ -1,4 +1,5 @@
'use client' 'use client'
import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan'; import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core';
@@ -10,48 +11,67 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
const EdukasiLingkunganTextEditor = dynamic( const EdukasiLingkunganTextEditor = dynamic(
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), () =>
import('../../_lib/edukasiLingkunganTextEditor').then(
(mod) => mod.EdukasiLingkunganTextEditor
),
{ ssr: false } { ssr: false }
); );
function EditContohKegiatanDesaDarmasaba() { export default function EditContohKegiatanDesaDarmasaba() {
const router = useRouter(); const router = useRouter();
const contohEdukasiState = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan); const contohEdukasiState = useProxy(
const [judul, setJudul] = useState(''); stateEdukasiLingkungan.stateContohEdukasiLingkungan
const [content, setContent] = useState(''); );
// state lokal untuk form
const [formData, setFormData] = useState({
judul: '',
deskripsi: '',
});
// load data awal
useShallowEffect(() => { useShallowEffect(() => {
if (!contohEdukasiState.findById.data) { if (!contohEdukasiState.findById.data) {
contohEdukasiState.findById.initialize(); contohEdukasiState.findById.initialize();
} }
}, []); }, []);
// update state lokal saat data global sudah ada
useEffect(() => { useEffect(() => {
if (contohEdukasiState.findById.data) { if (contohEdukasiState.findById.data) {
setJudul(contohEdukasiState.findById.data.judul ?? ''); setFormData({
setContent(contohEdukasiState.findById.data.deskripsi ?? ''); judul: contohEdukasiState.findById.data.judul ?? '',
deskripsi: contohEdukasiState.findById.data.deskripsi ?? '',
});
} }
}, [contohEdukasiState.findById.data]); }, [contohEdukasiState.findById.data]);
const submit = () => { // handler perubahan input
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
// submit update
const handleSubmit = () => {
if (contohEdukasiState.findById.data) { if (contohEdukasiState.findById.data) {
contohEdukasiState.findById.data.judul = judul; const updatedData = {
contohEdukasiState.findById.data.deskripsi = content; ...contohEdukasiState.findById.data,
contohEdukasiState.update.save(contohEdukasiState.findById.data); judul: formData.judul,
deskripsi: formData.deskripsi,
};
contohEdukasiState.update.save(updatedData);
} }
router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba'); router.push(
'/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba'
);
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
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>
<Title order={4} ml="sm" c="dark"> <Title order={4} ml="sm" c="dark">
@@ -75,8 +95,8 @@ function EditContohKegiatanDesaDarmasaba() {
</Text> </Text>
<EdukasiLingkunganTextEditor <EdukasiLingkunganTextEditor
showSubmit={false} showSubmit={false}
onChange={setJudul} onChange={(value) => handleChange('judul', value)}
initialContent={judul} initialContent={formData.judul}
/> />
</Box> </Box>
@@ -86,14 +106,14 @@ function EditContohKegiatanDesaDarmasaba() {
</Text> </Text>
<EdukasiLingkunganTextEditor <EdukasiLingkunganTextEditor
showSubmit={false} showSubmit={false}
onChange={setContent} onChange={(value) => handleChange('deskripsi', value)}
initialContent={content} initialContent={formData.deskripsi}
/> />
</Box> </Box>
<Group justify="right" mt="md"> <Group justify="right" mt="md">
<Button <Button
onClick={submit} onClick={handleSubmit}
radius="md" radius="md"
size="md" size="md"
style={{ style={{
@@ -111,5 +131,3 @@ function EditContohKegiatanDesaDarmasaba() {
</Box> </Box>
); );
} }
export default EditContohKegiatanDesaDarmasaba;

View File

@@ -14,30 +14,43 @@ const EdukasiLingkunganTextEditor = dynamic(
{ ssr: false } { ssr: false }
); );
function EditMateriEdukasiYangDiberikan() { export default function EditMateriEdukasiYangDiberikan() {
const router = useRouter(); const router = useRouter();
const materiEdukasiState = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan); const materiEdukasiState = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// State lokal gabungan untuk form
const [formData, setFormData] = useState({ judul: '', content: '' });
// Initialize data kalau belum ada
useShallowEffect(() => { useShallowEffect(() => {
if (!materiEdukasiState.findById.data) { if (!materiEdukasiState.findById.data) {
materiEdukasiState.findById.initialize(); materiEdukasiState.findById.initialize();
} }
}, []); }, []);
// Set formData saat data tersedia
useEffect(() => { useEffect(() => {
if (materiEdukasiState.findById.data) { if (materiEdukasiState.findById.data) {
setJudul(materiEdukasiState.findById.data.judul ?? ''); setFormData({
setContent(materiEdukasiState.findById.data.deskripsi ?? ''); judul: materiEdukasiState.findById.data.judul ?? '',
content: materiEdukasiState.findById.data.deskripsi ?? '',
});
} }
}, [materiEdukasiState.findById.data]); }, [materiEdukasiState.findById.data]);
// Handler perubahan form
const handleChange = (field: 'judul' | 'content', value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const submit = () => { const submit = () => {
if (materiEdukasiState.findById.data) { if (materiEdukasiState.findById.data) {
materiEdukasiState.findById.data.judul = judul; const updatedData = {
materiEdukasiState.findById.data.deskripsi = content; ...materiEdukasiState.findById.data,
materiEdukasiState.update.save(materiEdukasiState.findById.data); judul: formData.judul,
deskripsi: formData.content,
};
materiEdukasiState.update.save(updatedData);
} }
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan'); router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan');
}; };
@@ -66,14 +79,22 @@ function EditMateriEdukasiYangDiberikan() {
<Text fw="bold" mb={6}> <Text fw="bold" mb={6}>
Judul Judul
</Text> </Text>
<EdukasiLingkunganTextEditor showSubmit={false} onChange={setJudul} initialContent={judul} /> <EdukasiLingkunganTextEditor
showSubmit={false}
onChange={(val) => handleChange('judul', val)}
initialContent={formData.judul}
/>
</Box> </Box>
<Box> <Box>
<Text fw="bold" mb={6}> <Text fw="bold" mb={6}>
Konten Konten
</Text> </Text>
<EdukasiLingkunganTextEditor showSubmit={false} onChange={setContent} initialContent={content} /> <EdukasiLingkunganTextEditor
showSubmit={false}
onChange={(val) => handleChange('content', val)}
initialContent={formData.content}
/>
</Box> </Box>
<Group justify="right" mt="md"> <Group justify="right" mt="md">
@@ -96,5 +117,3 @@ function EditMateriEdukasiYangDiberikan() {
</Box> </Box>
); );
} }
export default EditMateriEdukasiYangDiberikan;

View File

@@ -1,4 +1,5 @@
'use client' 'use client'
import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan'; import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core';
@@ -14,30 +15,44 @@ const EdukasiLingkunganTextEditor = dynamic(
{ ssr: false } { ssr: false }
); );
function EditTujuanEdukasiLingkungan() { export default function EditTujuanEdukasiLingkungan() {
const router = useRouter(); const router = useRouter();
const tujuanEdukasiState = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi); const tujuanEdukasiState = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// State lokal untuk form, gak tergantung global state
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
// Initialize global state
useShallowEffect(() => { useShallowEffect(() => {
if (!tujuanEdukasiState.findById.data) { if (!tujuanEdukasiState.findById.data) {
tujuanEdukasiState.findById.initialize(); tujuanEdukasiState.findById.initialize();
} }
}, []); }, []);
// Sync initial values dari global state ke form lokal
useEffect(() => { useEffect(() => {
if (tujuanEdukasiState.findById.data) { if (tujuanEdukasiState.findById.data) {
setJudul(tujuanEdukasiState.findById.data.judul ?? ''); setFormData({
setContent(tujuanEdukasiState.findById.data.deskripsi ?? ''); judul: tujuanEdukasiState.findById.data.judul ?? '',
deskripsi: tujuanEdukasiState.findById.data.deskripsi ?? '',
});
} }
}, [tujuanEdukasiState.findById.data]); }, [tujuanEdukasiState.findById.data]);
// Handler input
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const submit = () => { const submit = () => {
if (tujuanEdukasiState.findById.data) { if (tujuanEdukasiState.findById.data) {
tujuanEdukasiState.findById.data.judul = judul; // Update global state hanya saat submit
tujuanEdukasiState.findById.data.deskripsi = content; const updatedData = {
tujuanEdukasiState.update.save(tujuanEdukasiState.findById.data); ...tujuanEdukasiState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi,
};
tujuanEdukasiState.update.save(updatedData);
} }
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan'); router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan');
}; };
@@ -73,8 +88,8 @@ function EditTujuanEdukasiLingkungan() {
</Text> </Text>
<EdukasiLingkunganTextEditor <EdukasiLingkunganTextEditor
showSubmit={false} showSubmit={false}
onChange={setJudul} onChange={(value) => handleChange('judul', value)}
initialContent={judul} initialContent={formData.judul}
/> />
</Box> </Box>
@@ -84,8 +99,8 @@ function EditTujuanEdukasiLingkungan() {
</Text> </Text>
<EdukasiLingkunganTextEditor <EdukasiLingkunganTextEditor
showSubmit={false} showSubmit={false}
onChange={setContent} onChange={(value) => handleChange('deskripsi', value)}
initialContent={content} initialContent={formData.deskripsi}
/> />
</Box> </Box>
@@ -109,5 +124,3 @@ function EditTujuanEdukasiLingkungan() {
</Box> </Box>
); );
} }
export default EditTujuanEdukasiLingkungan;

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong'; import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
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';
@@ -15,53 +16,59 @@ function EditKategoriKegiatan() {
const id = params?.id as string; const id = params?.id as string;
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan); const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({ nama: '' });
nama: '', const [loading, setLoading] = useState(true);
});
// Load data once
useEffect(() => { useEffect(() => {
const loadKategorikegiatan = async () => {
if (!id) return; if (!id) return;
const loadKategori = async () => {
try { try {
const data = await stateKategori.edit.load(id); const data = await stateKategori.edit.load(id);
if (data) { if (data) {
stateKategori.edit.id = id; stateKategori.edit.id = id;
setFormData({ setFormData({ nama: data.nama || '' });
nama: data.nama || '',
});
} }
} catch (error) { } catch (err) {
console.error('Error loading kategori kegiatan:', error); console.error('Error loading kategori kegiatan:', err);
toast.error('Gagal memuat data kategori kegiatan'); toast.error('Gagal memuat data kategori kegiatan');
} finally {
setLoading(false);
} }
}; };
loadKategorikegiatan(); loadKategori();
}, [id]); }, [id]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { const trimmedNama = formData.nama.trim();
if (!formData.nama.trim()) { if (!trimmedNama) {
toast.error('Nama kategori kegiatan tidak boleh kosong'); toast.error('Nama kategori kegiatan tidak boleh kosong');
return; return;
} }
stateKategori.edit.form = { nama: formData.nama.trim() }; try {
stateKategori.edit.form = { nama: trimmedNama };
if (!stateKategori.edit.id) stateKategori.edit.id = id; if (!stateKategori.edit.id) stateKategori.edit.id = id;
const success = await stateKategori.edit.update(); const success = await stateKategori.edit.update();
if (success) { if (success) {
toast.success('Kategori kegiatan berhasil diperbarui!'); toast.success('Kategori kegiatan berhasil diperbarui!');
router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan'); router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan');
} }
} catch (error) { } catch (err) {
console.error('Error updating kategori kegiatan:', error); console.error('Error updating kategori kegiatan:', err);
toast.error('Gagal memperbarui kategori kegiatan');
} }
}; };
if (loading) return <Text>Loading...</Text>;
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md" align="center"> <Group mb="md" align="center">
@@ -85,8 +92,8 @@ function EditKategoriKegiatan() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.nama} value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })} onChange={(e) => handleChange('nama', e.target.value)}
label={<Text fw="bold" fz="sm">Nama Kategori Kegiatan</Text>} label={<Text fw="bold" fz="sm">Nama Kategori Kegiatan</Text>}
placeholder="Masukkan nama kategori kegiatan" placeholder="Masukkan nama kategori kegiatan"
required required

View File

@@ -4,7 +4,19 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong'; import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
import colors from '@/con/colors'; import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch'; import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; import {
Box,
Button,
Group,
Image,
Paper,
Select,
Stack,
Text,
TextInput,
Title,
Tooltip,
} 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';
@@ -23,12 +35,10 @@ interface FormKegiatanDesa {
kategoriKegiatanId: string; kategoriKegiatanId: string;
} }
function EditGotongRoyong() { export default function EditKegiatanDesa() {
const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa) const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa);
const params = useParams() const params = useParams();
const router = useRouter() const router = useRouter();
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [formData, setFormData] = useState<FormKegiatanDesa>({ const [formData, setFormData] = useState<FormKegiatanDesa>({
judul: '', judul: '',
@@ -39,16 +49,19 @@ function EditGotongRoyong() {
partisipan: 0, partisipan: 0,
imageId: '', imageId: '',
kategoriKegiatanId: '', kategoriKegiatanId: '',
}) });
const [file, setFile] = useState<File | null>(null);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const formatDateForInput = (dateString: string) => { const formatDateForInput = (dateString: string) => {
if (!dateString) return ''; if (!dateString) return '';
const date = new Date(dateString); return new Date(dateString).toISOString().split('T')[0];
return date.toISOString().split('T')[0];
}; };
// Load kategori & data kegiatan
useEffect(() => { useEffect(() => {
const loadKegiatanDesa = async () => { const loadData = async () => {
gotongRoyongState.kategoriKegiatan.findMany.load();
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -65,43 +78,48 @@ function EditGotongRoyong() {
imageId: data.imageId || '', imageId: data.imageId || '',
kategoriKegiatanId: data.kategoriKegiatanId || '', kategoriKegiatanId: data.kategoriKegiatanId || '',
}); });
if (data.imageId) {
// Optional: bisa fetch URL image dari backend
setPreviewImage(`/api/file/${data.imageId}`);
}
} }
} catch (error) { } catch (error) {
console.error("Error loading kegiatan desa:", error); console.error(error);
toast.error("Gagal memuat data kegiatan desa"); toast.error('Gagal memuat data kegiatan desa');
} }
} };
gotongRoyongState.kategoriKegiatan.findMany.load() loadData();
loadKegiatanDesa();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
kegiatanDesaState.edit.form = { let imageId = formData.imageId;
...kegiatanDesaState.edit.form,
judul: formData.judul.trim(),
deskripsiSingkat: formData.deskripsiSingkat.trim(),
deskripsiLengkap: formData.deskripsiLengkap.trim(),
tanggal: new Date(formData.tanggal.trim()),
lokasi: formData.lokasi.trim(),
partisipan: formData.partisipan,
imageId: formData.imageId,
kategoriKegiatanId: formData.kategoriKegiatanId,
}
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) return toast.error("Gagal upload gambar"); if (!uploaded?.id) return toast.error('Gagal upload gambar');
kegiatanDesaState.edit.form.imageId = uploaded.id; imageId = uploaded.id;
} }
await kegiatanDesaState.edit.update()
toast.success("Kegiatan desa berhasil diperbarui!") kegiatanDesaState.edit.form = {
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa"); judul: formData.judul.trim(),
deskripsiSingkat: formData.deskripsiSingkat.trim(),
deskripsiLengkap: formData.deskripsiLengkap.trim(),
tanggal: new Date(formData.tanggal),
lokasi: formData.lokasi.trim(),
partisipan: formData.partisipan,
imageId,
kategoriKegiatanId: formData.kategoriKegiatanId,
};
await kegiatanDesaState.edit.update();
toast.success('Kegiatan desa berhasil diperbarui!');
router.push('/admin/lingkungan/gotong-royong/kegiatan-desa');
} catch (error) { } catch (error) {
console.error("Error updating kegiatan desa:", error); console.error(error);
toast.error("Terjadi kesalahan saat memperbarui kegiatan desa"); toast.error('Terjadi kesalahan saat memperbarui kegiatan desa');
}
} }
};
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -126,14 +144,14 @@ function EditGotongRoyong() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.judul} value={formData.judul}
label={<Text fz="sm" fw="bold">Judul Kegiatan Desa</Text>} label={<Text fz="sm" fw="bold">Judul Kegiatan Desa</Text>}
placeholder="masukkan judul kegiatan desa" placeholder="masukkan judul kegiatan desa"
onChange={(e) => setFormData({ ...formData, judul: e.target.value })} onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
required required
/> />
<TextInput <TextInput
defaultValue={formData.deskripsiSingkat} value={formData.deskripsiSingkat}
label={<Text fz="sm" fw="bold">Deskripsi Singkat Kegiatan Desa</Text>} label={<Text fz="sm" fw="bold">Deskripsi Singkat Kegiatan Desa</Text>}
placeholder="masukkan deskripsi singkat kegiatan desa" placeholder="masukkan deskripsi singkat kegiatan desa"
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })} onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
@@ -148,33 +166,27 @@ function EditGotongRoyong() {
value={formData.kategoriKegiatanId} value={formData.kategoriKegiatanId}
onChange={(val) => setFormData({ ...formData, kategoriKegiatanId: val ?? '' })} onChange={(val) => setFormData({ ...formData, kategoriKegiatanId: val ?? '' })}
/> />
<Box> <Box>
<Text fw="bold" fz="sm">Deskripsi Lengkap Kegiatan Desa</Text> <Text fw="bold" fz="sm">Deskripsi Lengkap Kegiatan Desa</Text>
<EditEditor <EditEditor
value={formData.deskripsiLengkap} value={formData.deskripsiLengkap}
onChange={(htmlContent) => { onChange={(htmlContent) => setFormData(prev => ({ ...prev, deskripsiLengkap: htmlContent }))}
setFormData((prev) => ({ ...prev, deskripsiLengkap: htmlContent }));
kegiatanDesaState.edit.form.deskripsiLengkap = htmlContent;
}}
/> />
</Box> </Box>
<TextInput <TextInput
label={<Text fz="sm" fw="bold">Tanggal Kegiatan Desa</Text>}
placeholder="masukkan tanggal kegiatan desa"
type="date" type="date"
defaultValue={formatDateForInput(formData.tanggal)} label={<Text fz="sm" fw="bold">Tanggal Kegiatan Desa</Text>}
value={formatDateForInput(formData.tanggal)}
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })} onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
/> />
<TextInput <TextInput
defaultValue={formData.lokasi} value={formData.lokasi}
label={<Text fz="sm" fw="bold">Lokasi Kegiatan Desa</Text>} label={<Text fz="sm" fw="bold">Lokasi Kegiatan Desa</Text>}
placeholder="masukkan lokasi kegiatan desa" placeholder="masukkan lokasi kegiatan desa"
onChange={(e) => setFormData({ ...formData, lokasi: e.target.value })} onChange={(e) => setFormData({ ...formData, lokasi: e.target.value })}
/> />
<TextInput <TextInput
defaultValue={formData.partisipan} value={formData.partisipan}
label={<Text fz="sm" fw="bold">Partisipan Kegiatan Desa</Text>} label={<Text fz="sm" fw="bold">Partisipan Kegiatan Desa</Text>}
placeholder="masukkan partisipan kegiatan desa" placeholder="masukkan partisipan kegiatan desa"
onChange={(e) => { onChange={(e) => {
@@ -187,11 +199,10 @@ function EditGotongRoyong() {
<Text fw="bold" fz="sm" mb={6}>Gambar Kegiatan Desa</Text> <Text fw="bold" fz="sm" mb={6}>Gambar Kegiatan Desa</Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {
const selectedFile = files[0]; const selected = files[0];
if (selectedFile) { if (!selected) return;
setFile(selectedFile); setFile(selected);
setPreviewImage(URL.createObjectURL(selectedFile)); setPreviewImage(URL.createObjectURL(selected));
}
}} }}
onReject={() => toast.error('File tidak valid.')} onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} maxSize={5 * 1024 ** 2}
@@ -248,5 +259,3 @@ function EditGotongRoyong() {
</Box> </Box>
); );
} }
export default EditGotongRoyong;

View File

@@ -17,9 +17,11 @@ const KonservasiAdatBaliTextEditor = dynamic(
function EditBentukKonservasiBerdasarkanAdat() { function EditBentukKonservasiBerdasarkanAdat() {
const router = useRouter(); const router = useRouter();
const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat); const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// Gabung semua field form jadi satu object
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
// Initialize data dari global state
useShallowEffect(() => { useShallowEffect(() => {
if (!bentukKonservasiState.findById.data) { if (!bentukKonservasiState.findById.data) {
bentukKonservasiState.findById.initialize(); bentukKonservasiState.findById.initialize();
@@ -28,16 +30,22 @@ function EditBentukKonservasiBerdasarkanAdat() {
useEffect(() => { useEffect(() => {
if (bentukKonservasiState.findById.data) { if (bentukKonservasiState.findById.data) {
setJudul(bentukKonservasiState.findById.data.judul ?? ''); setFormData({
setContent(bentukKonservasiState.findById.data.deskripsi ?? ''); judul: bentukKonservasiState.findById.data.judul ?? '',
deskripsi: bentukKonservasiState.findById.data.deskripsi ?? '',
});
} }
}, [bentukKonservasiState.findById.data]); }, [bentukKonservasiState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const submit = () => { const submit = () => {
if (bentukKonservasiState.findById.data) { if (bentukKonservasiState.findById.data) {
bentukKonservasiState.findById.data.judul = judul; // Update global state cuma pas submit
bentukKonservasiState.findById.data.deskripsi = content; const updatedData = { ...bentukKonservasiState.findById.data, ...formData };
bentukKonservasiState.update.save(bentukKonservasiState.findById.data); bentukKonservasiState.update.save(updatedData);
} }
router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat'); router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat');
}; };
@@ -46,12 +54,7 @@ function EditBentukKonservasiBerdasarkanAdat() {
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
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>
<Title order={4} ml="sm" c="dark"> <Title order={4} ml="sm" c="dark">
@@ -75,8 +78,8 @@ function EditBentukKonservasiBerdasarkanAdat() {
</Text> </Text>
<KonservasiAdatBaliTextEditor <KonservasiAdatBaliTextEditor
showSubmit={false} showSubmit={false}
onChange={setJudul} onChange={(value: string) => handleChange('judul', value)}
initialContent={judul} initialContent={formData.judul}
/> />
</Box> </Box>
@@ -86,8 +89,8 @@ function EditBentukKonservasiBerdasarkanAdat() {
</Text> </Text>
<KonservasiAdatBaliTextEditor <KonservasiAdatBaliTextEditor
showSubmit={false} showSubmit={false}
onChange={setContent} onChange={(value: string) => handleChange('deskripsi', value)}
initialContent={content} initialContent={formData.deskripsi}
/> />
</Box> </Box>

View File

@@ -1,4 +1,5 @@
'use client' 'use client'
import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali'; import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core';
@@ -20,26 +21,35 @@ const KonservasiAdatBaliTextEditor = dynamic(
function EditFilosofiTriHitaKarana() { function EditFilosofiTriHitaKarana() {
const router = useRouter(); const router = useRouter();
const filosofiTriHitaState = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita); const filosofiTriHitaState = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// Local state form
const [formData, setFormData] = useState({ judul: '', content: '' });
// Load data dari global state kalau belum ada
useShallowEffect(() => { useShallowEffect(() => {
if (!filosofiTriHitaState.findById.data) { if (!filosofiTriHitaState.findById.data) {
filosofiTriHitaState.findById.initialize(); filosofiTriHitaState.findById.initialize();
} }
}, []); }, []);
// Set formData dari global state saat data tersedia
useEffect(() => { useEffect(() => {
if (filosofiTriHitaState.findById.data) { if (filosofiTriHitaState.findById.data) {
setJudul(filosofiTriHitaState.findById.data.judul ?? ''); setFormData({
setContent(filosofiTriHitaState.findById.data.deskripsi ?? ''); judul: filosofiTriHitaState.findById.data.judul ?? '',
content: filosofiTriHitaState.findById.data.deskripsi ?? '',
});
} }
}, [filosofiTriHitaState.findById.data]); }, [filosofiTriHitaState.findById.data]);
const submit = () => { const handleChange = (field: 'judul' | 'content', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = () => {
if (filosofiTriHitaState.findById.data) { if (filosofiTriHitaState.findById.data) {
filosofiTriHitaState.findById.data.judul = judul; filosofiTriHitaState.findById.data.judul = formData.judul;
filosofiTriHitaState.findById.data.deskripsi = content; filosofiTriHitaState.findById.data.deskripsi = formData.content;
filosofiTriHitaState.update.save(filosofiTriHitaState.findById.data); filosofiTriHitaState.update.save(filosofiTriHitaState.findById.data);
} }
router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana'); router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana');
@@ -49,12 +59,7 @@ function EditFilosofiTriHitaKarana() {
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
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>
<Title order={4} ml="sm" c="dark"> <Title order={4} ml="sm" c="dark">
@@ -78,8 +83,8 @@ function EditFilosofiTriHitaKarana() {
</Text> </Text>
<KonservasiAdatBaliTextEditor <KonservasiAdatBaliTextEditor
showSubmit={false} showSubmit={false}
onChange={setJudul} onChange={(val) => handleChange('judul', val)}
initialContent={judul} initialContent={formData.judul}
/> />
</Box> </Box>
@@ -89,14 +94,14 @@ function EditFilosofiTriHitaKarana() {
</Text> </Text>
<KonservasiAdatBaliTextEditor <KonservasiAdatBaliTextEditor
showSubmit={false} showSubmit={false}
onChange={setContent} onChange={(val) => handleChange('content', val)}
initialContent={content} initialContent={formData.content}
/> />
</Box> </Box>
<Group justify="right" mt="md"> <Group justify="right" mt="md">
<Button <Button
onClick={submit} onClick={handleSubmit}
radius="md" radius="md"
size="md" size="md"
style={{ style={{

View File

@@ -17,26 +17,36 @@ const KonservasiAdatBaliTextEditor = dynamic(
function EditNilaiKonservasiAdat() { function EditNilaiKonservasiAdat() {
const router = useRouter(); const router = useRouter();
const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat); const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// state lokal untuk form
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
// load data awal
useShallowEffect(() => { useShallowEffect(() => {
if (!nilaiKonservasiState.findById.data) { if (!nilaiKonservasiState.findById.data) {
nilaiKonservasiState.findById.initialize(); nilaiKonservasiState.findById.initialize();
} }
}, []); }, []);
// sync state lokal saat data backend siap
useEffect(() => { useEffect(() => {
if (nilaiKonservasiState.findById.data) { if (nilaiKonservasiState.findById.data) {
setJudul(nilaiKonservasiState.findById.data.judul ?? ''); setFormData({
setContent(nilaiKonservasiState.findById.data.deskripsi ?? ''); judul: nilaiKonservasiState.findById.data.judul ?? '',
deskripsi: nilaiKonservasiState.findById.data.deskripsi ?? '',
});
} }
}, [nilaiKonservasiState.findById.data]); }, [nilaiKonservasiState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const submit = () => { const submit = () => {
if (nilaiKonservasiState.findById.data) { if (nilaiKonservasiState.findById.data) {
nilaiKonservasiState.findById.data.judul = judul; // update global state saat submit
nilaiKonservasiState.findById.data.deskripsi = content; nilaiKonservasiState.findById.data.judul = formData.judul;
nilaiKonservasiState.findById.data.deskripsi = formData.deskripsi;
nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data); nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data);
} }
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat'); router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat');
@@ -46,12 +56,7 @@ function EditNilaiKonservasiAdat() {
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
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>
<Title order={4} ml="sm" c="dark"> <Title order={4} ml="sm" c="dark">
@@ -75,8 +80,8 @@ function EditNilaiKonservasiAdat() {
</Text> </Text>
<KonservasiAdatBaliTextEditor <KonservasiAdatBaliTextEditor
showSubmit={false} showSubmit={false}
onChange={setJudul} onChange={val => handleChange('judul', val)}
initialContent={judul} initialContent={formData.judul}
/> />
</Box> </Box>
@@ -86,8 +91,8 @@ function EditNilaiKonservasiAdat() {
</Text> </Text>
<KonservasiAdatBaliTextEditor <KonservasiAdatBaliTextEditor
showSubmit={false} showSubmit={false}
onChange={setContent} onChange={val => handleChange('deskripsi', val)}
initialContent={content} initialContent={formData.deskripsi}
/> />
</Box> </Box>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client'; 'use client';
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah'; import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
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';
@@ -13,9 +14,9 @@ import { useProxy } from 'valtio/utils';
const LeafletMapEdit = dynamic(() => import('@/app/admin/(dashboard)/_com/leafletMapEdit'), { ssr: false }); const LeafletMapEdit = dynamic(() => import('@/app/admin/(dashboard)/_com/leafletMapEdit'), { ssr: false });
function EditKeteranganBankSampahTerdekat() { function EditKeteranganBankSampahTerdekat() {
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah) const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah);
const router = useRouter(); const router = useRouter();
const params = useParams() const params = useParams();
const [markerPosition, setMarkerPosition] = useState<{ lat: number; lng: number } | null>(null); const [markerPosition, setMarkerPosition] = useState<{ lat: number; lng: number } | null>(null);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@@ -24,10 +25,11 @@ function EditKeteranganBankSampahTerdekat() {
namaTempatMaps: '', namaTempatMaps: '',
lat: 0, lat: 0,
lng: 0, lng: 0,
}) });
// Load data ketika component mount
useEffect(() => { useEffect(() => {
const loadKeteranganBankSampahTerdekat = async () => { const loadKeterangan = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -35,14 +37,8 @@ function EditKeteranganBankSampahTerdekat() {
const data = await keteranganState.edit.load(id); const data = await keteranganState.edit.load(id);
if (data) { if (data) {
keteranganState.edit.id = id; keteranganState.edit.id = id;
keteranganState.edit.form = {
name: data.name,
alamat: data.alamat,
namaTempatMaps: data.namaTempatMaps,
lat: data.lat,
lng: data.lng,
};
// Update local formData dan markerPosition
setFormData({ setFormData({
name: data.name, name: data.name,
alamat: data.alamat, alamat: data.alamat,
@@ -50,35 +46,30 @@ function EditKeteranganBankSampahTerdekat() {
lat: data.lat, lat: data.lat,
lng: data.lng, lng: data.lng,
}); });
setMarkerPosition({ lat: data.lat, lng: data.lng }); setMarkerPosition({ lat: data.lat, lng: data.lng });
} }
} catch (error) { } catch (error) {
console.error("Error loading pengelolaan sampah:", error); console.error(error);
toast.error("Gagal memuat data pengelolaan sampah"); toast.error('Gagal memuat data pengelolaan sampah');
}
} }
};
loadKeteranganBankSampahTerdekat(); loadKeterangan();
}, [params?.id]); }, [params?.id]);
const handleInputChange = (field: keyof typeof formData, value: string | number) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
if (!formData.name.trim()) { if (!formData.name.trim()) return toast.error('Nama bank sampah harus diisi');
return toast.error('Nama bank sampah harus diisi'); if (!formData.alamat.trim()) return toast.error('Alamat harus diisi');
} if (!formData.namaTempatMaps.trim()) return toast.error('Nama tempat di Maps harus diisi');
if (!formData.alamat.trim()) { if (!markerPosition) return toast.error('Silakan pilih lokasi di peta');
return toast.error('Alamat harus diisi');
}
if (!formData.namaTempatMaps.trim()) {
return toast.error('Nama tempat di Maps harus diisi');
}
if (!markerPosition) {
return toast.error('Silakan pilih lokasi di peta');
}
// Update global state hanya saat submit
keteranganState.edit.form = { keteranganState.edit.form = {
...keteranganState.edit.form,
name: formData.name.trim(), name: formData.name.trim(),
alamat: formData.alamat.trim(), alamat: formData.alamat.trim(),
namaTempatMaps: formData.namaTempatMaps.trim(), namaTempatMaps: formData.namaTempatMaps.trim(),
@@ -91,10 +82,11 @@ function EditKeteranganBankSampahTerdekat() {
keteranganState.findUnique.data = null; keteranganState.findUnique.data = null;
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat"); router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat");
} catch (error) { } catch (error) {
console.error("Error updating pengelolaan sampah:", error); console.error(error);
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui data bank sampah'); toast.error(error instanceof Error ? error.message : 'Gagal memperbarui data bank sampah');
} }
} };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md"> <Group mb="md">
@@ -120,45 +112,35 @@ function EditKeteranganBankSampahTerdekat() {
<TextInput <TextInput
label="Nama Bank Sampah" label="Nama Bank Sampah"
placeholder="Masukkan nama bank sampah" placeholder="Masukkan nama bank sampah"
defaultValue={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => handleInputChange('name', e.target.value)}
required required
/> />
<TextInput <TextInput
label="Alamat" label="Alamat"
placeholder="Masukkan alamat lengkap" placeholder="Masukkan alamat lengkap"
defaultValue={formData.alamat} value={formData.alamat}
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })} onChange={(e) => handleInputChange('alamat', e.target.value)}
required required
/> />
<TextInput <TextInput
label="Nama Tempat di Maps" label="Nama Tempat di Maps"
placeholder="Masukkan nama tempat yang terdaftar di Google Maps" placeholder="Masukkan nama tempat yang terdaftar di Google Maps"
defaultValue={formData.namaTempatMaps} value={formData.namaTempatMaps}
onChange={(e) => setFormData({ ...formData, namaTempatMaps: e.target.value })} onChange={(e) => handleInputChange('namaTempatMaps', e.target.value)}
required required
/> />
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>Pilih Lokasi di Peta</Text>
Pilih Lokasi di Peta <Text fz="xs" c="dimmed" mb={4}>Klik pada peta untuk menandai lokasi</Text>
</Text>
<Text fz="xs" c="dimmed" mb={4}>
Klik pada peta untuk menandai lokasi
</Text>
<Box style={{ height: 300, width: '100%', borderRadius: '8px', overflow: 'hidden' }}> <Box style={{ height: 300, width: '100%', borderRadius: '8px', overflow: 'hidden' }}>
<LeafletMapEdit <LeafletMapEdit
key={markerPosition?.lat ?? 'default'}
initialPosition={markerPosition || { lat: -8.65, lng: 115.2 }} initialPosition={markerPosition || { lat: -8.65, lng: 115.2 }}
onChange={(pos) => { onChange={(pos) => {
setMarkerPosition(pos); setMarkerPosition(pos);
setFormData(prev => ({ handleInputChange('lat', pos.lat);
...prev, handleInputChange('lng', pos.lng);
lat: pos.lat,
lng: pos.lng,
}));
}} }}
/> />
</Box> </Box>

View File

@@ -17,15 +17,16 @@ interface FormProgramKreatif {
type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah' | 'truck' | 'scale' | 'clipboard' | 'trash'; type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah' | 'truck' | 'scale' | 'clipboard' | 'trash';
function EditProgramKreatifDesa() { function EditProgramKreatifDesa() {
const stateSampah = useProxy(pengelolaanSampahState.pengelolaanSampah) const stateSampah = useProxy(pengelolaanSampahState.pengelolaanSampah)
const params = useParams() const params = useParams()
const router = useRouter(); const router = useRouter();
// State lokal untuk form controlled
const [formData, setFormData] = useState<FormProgramKreatif>({ const [formData, setFormData] = useState<FormProgramKreatif>({
name: '', name: '',
icon: '', icon: '',
}) });
useEffect(() => { useEffect(() => {
const loadProgramKreatif = async () => { const loadProgramKreatif = async () => {
@@ -35,14 +36,7 @@ function EditProgramKreatifDesa() {
try { try {
const data = await stateSampah.update.load(id); const data = await stateSampah.update.load(id);
if (data) { if (data) {
// ⬇️ FIX PENTING: tambahkan ini stateSampah.update.id = id; // simpan id di global state
stateSampah.update.id = id;
stateSampah.update.form = {
name: data.name,
icon: data.icon,
};
setFormData({ setFormData({
name: data.name, name: data.name,
icon: data.icon, icon: data.icon,
@@ -52,17 +46,15 @@ function EditProgramKreatifDesa() {
console.error("Error loading pengelolaan sampah:", error); console.error("Error loading pengelolaan sampah:", error);
toast.error("Gagal memuat data pengelolaan sampah"); toast.error("Gagal memuat data pengelolaan sampah");
} }
} };
loadProgramKreatif(); loadProgramKreatif();
}, [params?.id]); }, [params?.id]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
// Update global state HANYA saat submit
stateSampah.update.form = { stateSampah.update.form = {
...stateSampah.update.form,
name: formData.name.trim(), name: formData.name.trim(),
icon: formData.icon.trim(), icon: formData.icon.trim(),
}; };
@@ -74,17 +66,13 @@ function EditProgramKreatifDesa() {
console.error("Error updating pengelolaan sampah:", error); console.error("Error updating pengelolaan sampah:", error);
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data pengelolaan sampah"); toast.error(error instanceof Error ? error.message : "Gagal memperbarui data pengelolaan sampah");
} }
} };
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 <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
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>
@@ -105,15 +93,8 @@ function EditProgramKreatifDesa() {
<TextInput <TextInput
label="Nama Pengelolaan Sampah" label="Nama Pengelolaan Sampah"
placeholder="Masukkan nama pengelolaan sampah" placeholder="Masukkan nama pengelolaan sampah"
defaultValue={formData.name} value={formData.name}
onChange={(e) => { onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
const value = e.target.value;
setFormData(prev => ({
...prev,
name: value
}));
stateSampah.update.form.name = value;
}}
required required
/> />
@@ -123,10 +104,7 @@ function EditProgramKreatifDesa() {
</Text> </Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={formData.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={(value) => setFormData(prev => ({ ...prev, icon: value }))}
setFormData(prev => ({ ...prev, icon: value }));
stateSampah.update.form.icon = value;
}}
/> />
</Box> </Box>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit'; import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan'; import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
@@ -50,13 +51,14 @@ function EditProgramPenghijauan() {
const [formData, setFormData] = useState<FormProgramPenghijauan>({ const [formData, setFormData] = useState<FormProgramPenghijauan>({
name: '', name: '',
deskripsi: '',
judul: '', judul: '',
deskripsi: '',
icon: '', icon: '',
}); });
// Load data program penghijauan
useEffect(() => { useEffect(() => {
const loadProgramPenghijauan = async () => { const loadProgram = async () => {
const id = params?.id as string; const id = params?.id as string;
if (!id) return; if (!id) return;
@@ -64,14 +66,6 @@ function EditProgramPenghijauan() {
const data = await stateProgramPenghijauan.update.load(id); const data = await stateProgramPenghijauan.update.load(id);
if (data) { if (data) {
stateProgramPenghijauan.update.id = id; stateProgramPenghijauan.update.id = id;
stateProgramPenghijauan.update.form = {
name: data.name,
judul: data.judul,
deskripsi: data.deskripsi,
icon: data.icon,
};
setFormData({ setFormData({
name: data.name, name: data.name,
judul: data.judul, judul: data.judul,
@@ -85,19 +79,21 @@ function EditProgramPenghijauan() {
} }
}; };
loadProgramPenghijauan(); loadProgram();
}, [params?.id]); }, [params?.id]);
// Hanya update global state saat submit
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
stateProgramPenghijauan.update.form = { stateProgramPenghijauan.update.form = {
...stateProgramPenghijauan.update.form,
name: formData.name.trim(), name: formData.name.trim(),
deskripsi: formData.deskripsi.trim(),
judul: formData.judul.trim(), judul: formData.judul.trim(),
deskripsi: formData.deskripsi.trim(),
icon: formData.icon.trim(), icon: formData.icon.trim(),
}; };
await stateProgramPenghijauan.update.submit(); await stateProgramPenghijauan.update.submit();
toast.success('Program penghijauan berhasil diperbarui');
router.push('/admin/lingkungan/program-penghijauan'); router.push('/admin/lingkungan/program-penghijauan');
} catch (error) { } catch (error) {
console.error('Error updating program penghijauan:', error); console.error('Error updating program penghijauan:', error);
@@ -107,7 +103,7 @@ function EditProgramPenghijauan() {
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan back button */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow> <Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button <Button
@@ -135,27 +131,21 @@ function EditProgramPenghijauan() {
> >
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
defaultValue={formData.name} value={formData.name}
label="Nama Program Penghijauan" label="Nama Program Penghijauan"
placeholder="Masukkan nama program penghijauan" placeholder="Masukkan nama program penghijauan"
onChange={(val) => onChange={(e) =>
setFormData({ setFormData((prev) => ({ ...prev, name: e.target.value }))
...formData,
name: val.target.value,
})
} }
required required
/> />
<TextInput <TextInput
defaultValue={formData.judul} value={formData.judul}
label="Judul Deskripsi Program Penghijauan" label="Judul Deskripsi Program Penghijauan"
placeholder="Masukkan judul deskripsi program penghijauan" placeholder="Masukkan judul deskripsi program penghijauan"
onChange={(val) => onChange={(e) =>
setFormData({ setFormData((prev) => ({ ...prev, judul: e.target.value }))
...formData,
judul: val.target.value,
})
} }
required required
/> />
@@ -166,10 +156,9 @@ function EditProgramPenghijauan() {
</Text> </Text>
<EditEditor <EditEditor
value={formData.deskripsi} value={formData.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) =>
setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); setFormData((prev) => ({ ...prev, deskripsi: htmlContent }))
stateProgramPenghijauan.update.form.deskripsi = htmlContent; }
}}
/> />
</Box> </Box>
@@ -179,10 +168,9 @@ function EditProgramPenghijauan() {
</Text> </Text>
<SelectIconProgramEdit <SelectIconProgramEdit
value={formData.icon as IconKey} value={formData.icon as IconKey}
onChange={(value) => { onChange={(value) =>
setFormData((prev) => ({ ...prev, icon: value })); setFormData((prev) => ({ ...prev, icon: value }))
stateProgramPenghijauan.update.form.icon = value; }
}}
/> />
</Box> </Box>