QC User & Admin Responsive : Menu Landing Page - Desa

This commit is contained in:
2025-10-02 00:10:33 +08:00
parent 63054cedf0
commit 8a6d8ed8db
70 changed files with 1839 additions and 1052 deletions

View File

@@ -1,4 +1,5 @@
'use client'
import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa';
import colors from '@/con/colors';
import {
@@ -33,8 +34,8 @@ function EditFasilitasYangDisediakan() {
const router = useRouter();
const editState = useProxy(stateBimbinganBelajarDesa.fasilitasYangDisediakanState);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// === State lokal form ===
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// Load data pertama kali
@@ -44,16 +45,22 @@ function EditFasilitasYangDisediakan() {
}
}, []);
// Sinkronkan state dengan data yang sudah di-load
// Sinkronkan formData saat data load
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? ''
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -61,9 +68,13 @@ function EditFasilitasYangDisediakan() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
// Update global state hanya saat submit
const payload = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi
};
await editState.update.save(payload);
toast.success('Berhasil menyimpan perubahan');
router.push('/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan');
@@ -78,7 +89,6 @@ function EditFasilitasYangDisediakan() {
const handleBack = () => router.back();
// Loading state
if (editState.findById.loading) {
return (
<Box>
@@ -123,9 +133,9 @@ function EditFasilitasYangDisediakan() {
<TextInput
label={<Text fw="bold">Judul</Text>}
placeholder="Masukkan judul fasilitas"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
{/* Deskripsi */}
@@ -133,8 +143,8 @@ function EditFasilitasYangDisediakan() {
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<BimbinganBelajarDesaTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value) => handleChange('deskripsi', value)}
initialContent={formData.deskripsi}
/>
</Box>
@@ -144,7 +154,7 @@ function EditFasilitasYangDisediakan() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -1,4 +1,5 @@
'use client'
import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa';
import colors from '@/con/colors';
import {
@@ -33,27 +34,33 @@ function EditLokasiDanJadwal() {
const router = useRouter();
const editState = useProxy(stateBimbinganBelajarDesa.lokasiDanJadwalState);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// state lokal untuk form, tidak langsung merubah global state
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// load data sekali
// Load data sekali
useShallowEffect(() => {
if (!editState.findById.data) {
editState.findById.initialize();
}
}, []);
// isi state ketika data loaded
// Isi state lokal setelah data global ter-load
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? ''
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -61,9 +68,13 @@ function EditLokasiDanJadwal() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
// update global state hanya saat submit
const updatedData = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi
};
await editState.update.save(updatedData);
toast.success('Berhasil menyimpan perubahan');
router.push('/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal');
@@ -78,7 +89,6 @@ function EditLokasiDanJadwal() {
const handleBack = () => router.back();
// loading state
if (editState.findById.loading) {
return (
<Box>
@@ -92,15 +102,10 @@ function EditLokasiDanJadwal() {
return (
<Box>
<Stack gap="xs">
{/* Header dengan tombol kembali */}
{/* Header */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button
variant="subtle"
onClick={handleBack}
p="xs"
radius="md"
>
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
@@ -125,9 +130,9 @@ function EditLokasiDanJadwal() {
<TextInput
label={<Text fw="bold">Judul</Text>}
placeholder="Masukkan judul lokasi/jadwal"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
{/* Deskripsi Field */}
@@ -135,8 +140,8 @@ function EditLokasiDanJadwal() {
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<BimbinganBelajarDesaTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value) => handleChange('deskripsi', value)}
initialContent={formData.deskripsi}
/>
</Box>
@@ -146,7 +151,7 @@ function EditLokasiDanJadwal() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -1,4 +1,5 @@
'use client'
import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa';
import colors from '@/con/colors';
import {
@@ -33,26 +34,31 @@ function EditTujuanProgram() {
const router = useRouter();
const editState = useProxy(stateBimbinganBelajarDesa.stateTujuanProgram);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// gabung judul & content jadi formData
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// load data once
// load data sekali
useShallowEffect(() => {
if (!editState.findById.data) {
editState.findById.initialize();
}
if (!editState.findById.data) editState.findById.initialize();
}, []);
// sync data dari global state ke local formData sekali setelah load
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? '',
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -60,8 +66,9 @@ function EditTujuanProgram() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
// update global state hanya saat submit
editState.findById.data.judul = formData.judul;
editState.findById.data.deskripsi = formData.deskripsi;
await editState.update.save(editState.findById.data);
toast.success('Berhasil menyimpan perubahan');
@@ -117,18 +124,20 @@ function EditTujuanProgram() {
<TextInput
label={<Text fw="bold">Judul</Text>}
placeholder="Masukkan judul program"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
{/* Deskripsi Field */}
<Box>
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<Text fz="sm" fw="bold" mb="xs">
Deskripsi
</Text>
<BimbinganBelajarDesaTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
initialContent={formData.deskripsi}
onChange={(value) => handleChange('deskripsi', value)}
/>
</Box>
@@ -138,7 +147,7 @@ function EditTujuanProgram() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -5,7 +5,7 @@ import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import dataPendidikan from '../../../_state/pendidikan/data-pendidikan';
@@ -15,23 +15,35 @@ export default function EditDataPendidikan() {
const stateDPM = useProxy(dataPendidikan);
const id = params.id;
// state lokal untuk form
const [formData, setFormData] = useState({
name: '',
jumlah: '',
});
// Load data saat mount
useEffect(() => {
if (id) {
stateDPM.findUnique.load(id).then(() => {
const data = stateDPM.findUnique.data;
if (data) {
stateDPM.update.form = {
setFormData({
name: data.name || '',
jumlah: data.jumlah || '',
};
});
}
});
}
}, [id]);
const handleChange = (field: 'name' | 'jumlah', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
// update global state hanya saat submit
stateDPM.update.id = id;
stateDPM.update.form = { ...formData };
await stateDPM.update.submit();
router.push('/admin/pendidikan/data-pendidikan');
};
@@ -59,8 +71,8 @@ export default function EditDataPendidikan() {
<TextInput
label="Nama Pendidikan"
placeholder="Contoh: SD, SMP, SMA"
defaultValue={stateDPM.update.form.name}
onChange={(e) => (stateDPM.update.form.name = e.currentTarget.value)}
value={formData.name}
onChange={(e) => handleChange('name', e.currentTarget.value)}
radius="md"
required
/>
@@ -68,8 +80,8 @@ export default function EditDataPendidikan() {
label="Jumlah Peserta"
type="number"
placeholder="Masukkan jumlah peserta"
defaultValue={stateDPM.update.form.jumlah}
onChange={(e) => (stateDPM.update.form.jumlah = e.currentTarget.value)}
value={formData.jumlah}
onChange={(e) => handleChange('jumlah', e.currentTarget.value)}
radius="md"
required
/>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
import colors from '@/con/colors';
import {
@@ -22,64 +23,62 @@ function EditJenjangPendidikan() {
const router = useRouter();
const params = useParams();
const id = params?.id as string;
const stateJenjang = useProxy(infoSekolahPaud.jenjangPendidikan);
const [formData, setFormData] = useState({
nama: "",
});
const [formData, setFormData] = useState({ nama: '' });
const [loading, setLoading] = useState(true);
// Load data sekali saat component mount
useEffect(() => {
const loadJenjangPendidikan = async () => {
if (!id) return;
if (!id) return;
const loadJenjang = async () => {
try {
const data = await stateJenjang.edit.load(id);
if (data) {
stateJenjang.edit.id = id;
setFormData({
nama: data.nama || '',
});
stateJenjang.edit.id = id; // tetap simpan di global state
setFormData({ nama: data.nama || '' });
}
} catch (error) {
console.error("Error loading jenjang pendidikan:", error);
toast.error("Gagal memuat data jenjang pendidikan");
console.error('Error loading jenjang pendidikan:', error);
toast.error('Gagal memuat data jenjang pendidikan');
} finally {
setLoading(false);
}
};
loadJenjangPendidikan();
loadJenjang();
}, [id]);
const handleChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!formData.nama.trim()) {
toast.error('Nama jenjang pendidikan tidak boleh kosong');
return;
}
try {
if (!formData.nama.trim()) {
toast.error('Nama jenjang pendidikan tidak boleh kosong');
return;
}
stateJenjang.edit.form = {
nama: formData.nama.trim(),
};
if (!stateJenjang.edit.id) {
stateJenjang.edit.id = id;
}
stateJenjang.edit.form = { nama: formData.nama.trim() };
if (!stateJenjang.edit.id) stateJenjang.edit.id = id;
const success = await stateJenjang.edit.update();
if (success) {
toast.success("Jenjang pendidikan berhasil diperbarui!");
router.push("/admin/pendidikan/info-sekolah/jenjang-pendidikan");
toast.success('Jenjang pendidikan berhasil diperbarui!');
router.push('/admin/pendidikan/info-sekolah/jenjang-pendidikan');
}
} catch (error) {
console.error("Error updating jenjang pendidikan:", error);
toast.error("Terjadi kesalahan saat memperbarui jenjang pendidikan");
console.error('Error updating jenjang pendidikan:', error);
toast.error('Terjadi kesalahan saat memperbarui jenjang pendidikan');
}
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header Back + Title */}
{/* Header */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
@@ -91,7 +90,7 @@ function EditJenjangPendidikan() {
</Title>
</Group>
{/* Form Container */}
{/* Form */}
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
@@ -104,9 +103,10 @@ function EditJenjangPendidikan() {
<TextInput
label="Nama Jenjang Pendidikan"
placeholder="Masukkan nama jenjang pendidikan"
defaultValue={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
value={formData.nama}
onChange={(e) => handleChange('nama', e.target.value)}
required
disabled={loading}
/>
<Group justify="right">

View File

@@ -31,6 +31,7 @@ export default function EditLembaga() {
jenjangId: '',
});
// Load jenjang pendidikan dan data lembaga
useEffect(() => {
infoSekolahPaud.jenjangPendidikan.findMany.load();
@@ -46,12 +47,17 @@ export default function EditLembaga() {
}
}, [id]);
const handleChange = (field: 'nama' | 'jenjangId', value: string) => {
setForm((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!form.nama || !form.jenjangId) {
toast.warn('Nama dan jenjang pendidikan harus diisi');
return;
}
// Update global state hanya saat submit
state.edit.id = id;
state.edit.form = form;
@@ -65,7 +71,7 @@ export default function EditLembaga() {
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan back button */}
{/* Header */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button
@@ -82,7 +88,7 @@ export default function EditLembaga() {
</Title>
</Group>
{/* Card Form */}
{/* Form Card */}
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
@@ -95,8 +101,8 @@ export default function EditLembaga() {
<TextInput
label="Nama Lembaga"
placeholder="Masukkan nama lembaga"
defaultValue={form.nama}
onChange={(e) => setForm({ ...form, nama: e.currentTarget.value })}
value={form.nama}
onChange={(e) => handleChange('nama', e.currentTarget.value)}
required
/>
@@ -111,7 +117,7 @@ export default function EditLembaga() {
})) || []
}
value={form.jenjangId}
onChange={(val) => setForm({ ...form, jenjangId: val || '' })}
onChange={(val) => handleChange('jenjangId', val || '')}
required
/>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
import colors from '@/con/colors';
import {
@@ -25,21 +26,22 @@ interface FormPengajar {
}
function EditPengajar() {
const pengajarState = useProxy(infoSekolahPaud.pengajar)
const params = useParams()
const router = useRouter()
const pengajarState = useProxy(infoSekolahPaud.pengajar);
const params = useParams();
const router = useRouter();
const [formData, setFormData] = useState<FormPengajar>({
nama: '',
lembagaId: ''
})
});
useEffect(() => {
const loadPengajar = async () => {
const loadData = async () => {
const id = params?.id as string;
if (!id) return;
try {
infoSekolahPaud.lembagaPendidikan.findMany.load();
const data = await pengajarState.edit.load(id);
if (data) {
setFormData({
@@ -47,30 +49,35 @@ function EditPengajar() {
lembagaId: data.lembagaId || '',
});
}
} catch (error) {
console.error("Error loading pengajar:", error);
} catch (err) {
console.error(err);
toast.error("Gagal memuat data pengajar");
}
}
infoSekolahPaud.lembagaPendidikan.findMany.load()
loadPengajar();
};
loadData();
}, [params?.id]);
const handleChange = (field: keyof FormPengajar, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
try {
// update global state hanya saat submit
pengajarState.edit.form = {
...pengajarState.edit.form,
nama: formData.nama.trim(),
lembagaId: formData.lembagaId.trim(),
}
await pengajarState.edit.update()
toast.success("Data pengajar berhasil diperbarui!")
lembagaId: formData.lembagaId.trim()
};
await pengajarState.edit.update();
toast.success("Data pengajar berhasil diperbarui!");
router.push("/admin/pendidikan/info-sekolah/pengajar");
} catch (error) {
console.error("Error updating pengajar:", error);
} catch (err) {
console.error(err);
toast.error("Terjadi kesalahan saat memperbarui pengajar");
}
}
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
@@ -99,8 +106,8 @@ function EditPengajar() {
<TextInput
label="Nama Pengajar"
placeholder="Masukkan nama pengajar"
defaultValue={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
value={formData.nama}
onChange={(e) => handleChange('nama', e.target.value)}
required
/>
@@ -112,7 +119,7 @@ function EditPengajar() {
label: k.nama
})) ?? []}
value={formData.lembagaId}
onChange={(val) => setFormData({ ...formData, lembagaId: val ?? '' })}
onChange={(val) => handleChange('lembagaId', val ?? '')}
required
/>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
import colors from '@/con/colors';
import {
@@ -35,6 +36,7 @@ function EditSiswa() {
lembagaId: '',
});
// Load data siswa
useEffect(() => {
const loadSiswa = async () => {
const id = params?.id as string;
@@ -53,10 +55,20 @@ function EditSiswa() {
toast.error('Gagal memuat data siswa');
}
};
infoSekolahPaud.lembagaPendidikan.findMany.load();
loadSiswa();
}, [params?.id]);
// Handler update local formData
const handleChange = (field: keyof FormSiswa, value: string) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
// Submit ke global state
const handleSubmit = async () => {
try {
siswaState.edit.form = {
@@ -75,7 +87,7 @@ function EditSiswa() {
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header dengan tombol kembali */}
{/* Header */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
@@ -87,7 +99,7 @@ function EditSiswa() {
</Title>
</Group>
{/* Card Form */}
{/* Form Card */}
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
@@ -100,8 +112,8 @@ function EditSiswa() {
<TextInput
label={<Text fz="sm" fw="bold">Nama Siswa</Text>}
placeholder="Masukkan nama siswa"
defaultValue={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
value={formData.nama}
onChange={(e) => handleChange('nama', e.target.value)}
required
/>
@@ -115,7 +127,7 @@ function EditSiswa() {
})) ?? []
}
value={formData.lembagaId}
onChange={(val) => setFormData({ ...formData, lembagaId: val ?? '' })}
onChange={(val) => handleChange('lembagaId', val ?? '')}
required
/>

View File

@@ -33,8 +33,7 @@ function EditJenisProgramYangDiselenggarakan() {
const router = useRouter();
const editState = useProxy(pendidikanNonFormalState.stateJenisProgram);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
const [formData, setFormData] = useState({ judul: '', content: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// Load data pertama kali
@@ -44,16 +43,22 @@ function EditJenisProgramYangDiselenggarakan() {
}
}, []);
// Sync state dengan data hasil fetch
// Sync state lokal dengan data hasil fetch
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
content: editState.findById.data.deskripsi ?? '',
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'content', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -61,11 +66,15 @@ function EditJenisProgramYangDiselenggarakan() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
const updatedData = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.content,
};
await editState.update.save(updatedData);
toast.success('Berhasil menyimpan perubahan');
router.push(
'/admin/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan'
);
@@ -117,17 +126,17 @@ function EditJenisProgramYangDiselenggarakan() {
<TextInput
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="Masukkan judul program"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
<Box>
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<JenisProgramYangDiselenggarakanTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value: string) => handleChange('content', value)}
initialContent={formData.content}
/>
</Box>
@@ -136,7 +145,7 @@ function EditJenisProgramYangDiselenggarakan() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -1,4 +1,5 @@
'use client'
import pendidikanNonFormalState from '@/app/admin/(dashboard)/_state/pendidikan/pendidikan-non-formal';
import colors from '@/con/colors';
import {
@@ -33,8 +34,11 @@ function EditTempatKegiatan() {
const router = useRouter();
const editState = useProxy(pendidikanNonFormalState.stateTempatKegiatan);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// state lokal form
const [formData, setFormData] = useState({
judul: '',
deskripsi: '',
});
const [isSubmitting, setIsSubmitting] = useState(false);
// load data pertama kali
@@ -44,16 +48,22 @@ function EditTempatKegiatan() {
}
}, []);
// sync state dengan data hasil fetch
// sync state lokal ketika data fetch selesai
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? '',
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -61,10 +71,13 @@ function EditTempatKegiatan() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
const updatedData = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi,
};
await editState.update.save(updatedData);
toast.success('Berhasil menyimpan perubahan');
router.push(
'/admin/pendidikan/pendidikan-non-formal/tempat-kegiatan'
@@ -117,9 +130,9 @@ function EditTempatKegiatan() {
<TextInput
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="Masukkan judul tempat kegiatan"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
<Box>
@@ -128,8 +141,8 @@ function EditTempatKegiatan() {
</Text>
<TempatKegiatanTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value) => handleChange('deskripsi', value)}
initialContent={formData.deskripsi}
/>
</Box>
@@ -138,7 +151,7 @@ function EditTempatKegiatan() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -1,4 +1,5 @@
'use client'
import pendidikanNonFormalState from '@/app/admin/(dashboard)/_state/pendidikan/pendidikan-non-formal';
import colors from '@/con/colors';
import {
@@ -30,41 +31,49 @@ function EditTujuanProgram() {
const router = useRouter();
const editState = useProxy(pendidikanNonFormalState.stateTujuanPendidikanNonFormal);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// load data pertama kali
// Load data pertama kali
useShallowEffect(() => {
if (!editState.findById.data) {
editState.findById.initialize();
}
}, []);
// sync data hasil fetch ke local state
// Sync hasil fetch ke local state (sekali)
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? ''
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
if (!editState.findById.data) return;
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
const payload = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi
};
toast.success('Berhasil menyimpan perubahan');
router.push('/admin/pendidikan/pendidikan-non-formal/tujuan-program');
}
await editState.update.save(payload);
toast.success('Berhasil menyimpan perubahan');
router.push('/admin/pendidikan/pendidikan-non-formal/tujuan-program');
} catch (err) {
console.error('Error saving:', err);
toast.error('Gagal menyimpan data');
@@ -88,16 +97,13 @@ function EditTujuanProgram() {
return (
<Box>
<Stack gap="xs">
{/* Back Button + Title */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={22} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Edit Tujuan Program
</Title>
<Title order={4} ml="sm" c="dark">Edit Tujuan Program</Title>
</Group>
<Paper
@@ -112,17 +118,17 @@ function EditTujuanProgram() {
<TextInput
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="Masukkan judul program"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
<Box>
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<PendidikanNonFormalTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value) => handleChange('deskripsi', value)}
initialContent={formData.deskripsi}
/>
</Box>
@@ -131,7 +137,7 @@ function EditTujuanProgram() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -1,74 +1,86 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import { useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import { toast } from 'react-toastify';
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 EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
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 { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPerpustakaanDigital() {
const editState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
const router = useRouter();
const params = useParams();
const editState = useProxy(perpustakaanDigitalState.dataPerpustakaan);
const [formData, setFormData] = useState({
judul: '',
deskripsi: '',
kategoriId: '',
imageId: '',
});
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [formData, setFormData] = useState({
judul: editState.update.form.judul || "",
deskripsi: editState.update.form.deskripsi || "",
imageId: editState.update.form.imageId || "",
kategoriId: editState.update.form.kategoriId || "",
})
// Load kategori & data awal
useEffect(() => {
perpustakaanDigitalState.kategoriBuku.findMany.load();
const loadDataPerpustakaan = async () => {
const id = params?.id as string;
const loadData = async () => {
const id = Array.isArray(params?.id) ? params.id[0] : params?.id;
if (!id) return;
try {
const data = await editState.update.load(id);
if (data) {
setFormData({
judul: data.judul || "",
deskripsi: data.deskripsi || "",
imageId: data.imageId || "",
kategoriId: data.kategoriId || "",
});
if (data.image?.link) setPreviewImage(data.image.link);
}
} catch (error) {
console.error("Error loading data perpustakaan:", error);
toast.error(error instanceof Error ? error.message : "Gagal mengambil data perpustakaan");
}
}
if (!data) return;
loadDataPerpustakaan();
setFormData({
judul: data.judul || '',
deskripsi: data.deskripsi || '',
kategoriId: data.kategoriId || '',
imageId: data.imageId || '',
});
if (data.image?.link) setPreviewImage(data.image.link);
} catch (error) {
console.error(error);
toast.error(error instanceof Error ? error.message : 'Gagal memuat data perpustakaan');
}
};
loadData();
}, [params?.id]);
const handleInputChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
try {
editState.update.form = { ...editState.update.form, ...formData }
// Update global state hanya pas submit
editState.update.form = { ...editState.update.form, ...formData };
// Upload file jika 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");
if (!uploaded?.id) return toast.error('Gagal upload gambar');
editState.update.form.imageId = uploaded.id;
}
await editState.update.update();
toast.success("Data perpustakaan berhasil diperbarui!");
router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan");
toast.success('Data perpustakaan berhasil diperbarui!');
router.push('/admin/pendidikan/perpustakaan-digital/data-perpustakaan');
} catch (error) {
console.error("Error updating data perpustakaan:", error);
toast.error("Terjadi kesalahan saat memperbarui data perpustakaan");
console.error(error);
toast.error('Terjadi kesalahan saat memperbarui data perpustakaan');
}
};
@@ -86,18 +98,26 @@ function EditPerpustakaanDigital() {
</Title>
</Group>
{/* Form Paper */}
<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">
{/* Dropzone Image */}
<Box>
<Text fw="bold" fz="sm" mb={6}>Gambar</Text>
<Text fw="bold" fz="sm" mb={6}>
Gambar
</Text>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
const selected = files[0];
if (selected) {
setFile(selected);
setPreviewImage(URL.createObjectURL(selected));
}
}}
onReject={() => toast.error('File tidak valid.')}
@@ -117,8 +137,12 @@ function EditPerpustakaanDigital() {
<IconPhoto size={48} color="#868e96" stroke={1.5} />
</Dropzone.Idle>
<Stack gap="xs" align="center">
<Text size="md" fw={500}>Seret gambar atau klik untuk memilih file</Text>
<Text size="sm" c="dimmed">Maksimal 5MB, format gambar wajib</Text>
<Text size="md" fw={500}>
Seret gambar atau klik untuk memilih file
</Text>
<Text size="sm" c="dimmed">
Maksimal 5MB, format gambar wajib
</Text>
</Stack>
</Group>
</Dropzone>
@@ -140,28 +164,30 @@ function EditPerpustakaanDigital() {
<TextInput
label="Judul"
placeholder="Masukkan judul"
defaultValue={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
value={formData.judul}
onChange={(e) => handleInputChange('judul', e.target.value)}
required
/>
{/* Deskripsi */}
<Box>
<Text fw="bold" fz="sm" mb={6}>Deskripsi</Text>
<EditEditor value={formData.deskripsi} onChange={(val) => setFormData({ ...formData, deskripsi: val })} />
<Text fw="bold" fz="sm" mb={6}>
Deskripsi
</Text>
<EditEditor value={formData.deskripsi} onChange={(val) => handleInputChange('deskripsi', val)} />
</Box>
{/* Kategori */}
<Select
value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })}
onChange={(val) => handleInputChange('kategoriId', val || '')}
label="Kategori"
placeholder='Pilih kategori'
data={perpustakaanDigitalState.kategoriBuku.findMany.data?.map(v => ({ value: v.id, label: v.name })) || []}
placeholder="Pilih kategori"
data={perpustakaanDigitalState.kategoriBuku.findMany.data?.map((v) => ({ value: v.id, label: v.name })) || []}
clearable
searchable
required
error={!formData.kategoriId ? "Pilih kategori" : undefined}
error={!formData.kategoriId ? 'Pilih kategori' : undefined}
/>
{/* Submit Button */}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
@@ -14,9 +15,8 @@ function EditKategoriBuku() {
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
name: editState.update.form.name || '',
});
const [formData, setFormData] = useState({ name: '' });
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadKategori = async () => {
@@ -26,21 +26,26 @@ function EditKategoriBuku() {
try {
const data = await editState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
});
setFormData({ name: data.name || '' });
}
} catch (error) {
console.error('Error loading kategori buku:', error);
toast.error('Gagal memuat data kategori buku');
} finally {
setLoading(false);
}
};
loadKategori();
}, [params?.id]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({ ...prev, name: e.target.value }));
};
const handleSubmit = async () => {
try {
// Update global state hanya saat submit
editState.update.form = { ...editState.update.form, name: formData.name };
await editState.update.update();
toast.success('Kategori Buku berhasil diperbarui!');
@@ -76,9 +81,10 @@ function EditKategoriBuku() {
<TextInput
label="Nama Kategori Buku"
placeholder="Masukkan nama kategori buku"
defaultValue={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
value={formData.name}
onChange={handleChange}
required
disabled={loading}
/>
<Group justify="right">
@@ -91,6 +97,7 @@ function EditKategoriBuku() {
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
disabled={loading || !formData.name.trim()}
>
Simpan
</Button>

View File

@@ -1,4 +1,5 @@
'use client'
import stateProgramPendidikanAnak from '@/app/admin/(dashboard)/_state/pendidikan/program-pendidikan-anak';
import colors from '@/con/colors';
import {
@@ -17,7 +18,7 @@ import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
@@ -29,30 +30,46 @@ const ProgramPendidikanAnakTextEditor = dynamic(
{ ssr: false }
);
interface FormData {
judul: string;
deskripsi: string;
}
function EditTujuanProgram() {
const router = useRouter();
const editState = useProxy(stateProgramPendidikanAnak.programUnggulanState);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// lokal state form
const [formData, setFormData] = useState<FormData>({
judul: '',
deskripsi: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
// load data once
useShallowEffect(() => {
if (!editState.findById.data) {
editState.findById.initialize();
}
if (!editState.findById.data) editState.findById.initialize();
}, []);
// populate formData saat data global tersedia
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? ''
});
}
}, [editState.findById.data]);
const handleChange = useCallback(
(field: keyof FormData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
},
[]
);
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -60,9 +77,13 @@ function EditTujuanProgram() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
// update global state only on submit
const updatedData = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi
};
await editState.update.save(updatedData);
toast.success('Berhasil menyimpan perubahan');
router.push('/admin/pendidikan/program-pendidikan-anak/program-unggulan');
@@ -77,7 +98,6 @@ function EditTujuanProgram() {
const handleBack = () => router.back();
// loading state
if (editState.findById.loading) {
return (
<Box>
@@ -93,12 +113,7 @@ function EditTujuanProgram() {
<Stack gap="xs">
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button
variant="subtle"
onClick={handleBack}
p="xs"
radius="md"
>
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
@@ -122,18 +137,20 @@ function EditTujuanProgram() {
<TextInput
label={<Text fw="bold">Judul</Text>}
placeholder="Masukkan judul program"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
{/* Deskripsi Field */}
<Box>
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<Text fz="sm" fw="bold" mb="xs">
Deskripsi
</Text>
<ProgramPendidikanAnakTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value) => handleChange('deskripsi', value)}
initialContent={formData.deskripsi}
/>
</Box>
@@ -143,7 +160,7 @@ function EditTujuanProgram() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>

View File

@@ -11,7 +11,7 @@ import {
Text,
TextInput,
Title,
Tooltip
Tooltip,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react';
@@ -30,8 +30,8 @@ function EditTujuanProgram() {
const router = useRouter();
const editState = useProxy(stateProgramPendidikanAnak.stateTujuanProgram);
const [judul, setJudul] = useState('');
const [content, setContent] = useState('');
// Menggunakan satu state lokal untuk form
const [formData, setFormData] = useState({ judul: '', deskripsi: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// Load data pertama kali
@@ -41,16 +41,22 @@ function EditTujuanProgram() {
}
}, []);
// Sync state dengan data hasil fetch
// Sync hanya sekali saat data fetch berhasil
useEffect(() => {
if (editState.findById.data) {
setJudul(editState.findById.data.judul ?? '');
setContent(editState.findById.data.deskripsi ?? '');
setFormData({
judul: editState.findById.data.judul ?? '',
deskripsi: editState.findById.data.deskripsi ?? '',
});
}
}, [editState.findById.data]);
const handleChange = (field: 'judul' | 'deskripsi', value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
if (!judul.trim()) {
if (!formData.judul.trim()) {
toast.error('Judul wajib diisi');
return;
}
@@ -58,9 +64,12 @@ function EditTujuanProgram() {
setIsSubmitting(true);
try {
if (editState.findById.data) {
editState.findById.data.judul = judul;
editState.findById.data.deskripsi = content;
await editState.update.save(editState.findById.data);
const updatedData = {
...editState.findById.data,
judul: formData.judul,
deskripsi: formData.deskripsi,
};
await editState.update.save(updatedData);
toast.success('Berhasil menyimpan perubahan');
router.push('/admin/pendidikan/program-pendidikan-anak/tujuan-program');
@@ -112,17 +121,17 @@ function EditTujuanProgram() {
<TextInput
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="Masukkan judul program"
defaultValue={judul}
onChange={(e) => setJudul(e.currentTarget.value)}
error={!judul && 'Judul wajib diisi'}
value={formData.judul}
onChange={(e) => handleChange('judul', e.currentTarget.value)}
error={!formData.judul && 'Judul wajib diisi'}
/>
<Box>
<Text fz="sm" fw="bold" mb="xs">Deskripsi</Text>
<ProgramPendidikanAnakTextEditor
showSubmit={false}
onChange={setContent}
initialContent={content}
onChange={(value) => handleChange('deskripsi', value)}
initialContent={formData.deskripsi}
/>
</Box>
@@ -131,7 +140,7 @@ function EditTujuanProgram() {
bg={colors['blue-button']}
onClick={handleSubmit}
loading={isSubmitting || editState.update.loading}
disabled={!judul}
disabled={!formData.judul}
>
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
</Button>