feat: implement form validation for empty fields across multiple admin pages

- Added validation to disable submit buttons when required fields are empty
- Implemented consistent validation patterns across various admin pages
- Applied validation to create and edit forms for berita, gallery, layanan, penghargaan, pengumuman, potensi, profil-desa, and ppid sections
- Used helper functions to check for empty HTML content in editor fields
- Ensured submit buttons are disabled until all required fields are filled

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-02-14 14:17:17 +08:00
parent b35874b120
commit 9678e6979b
41 changed files with 1131 additions and 83 deletions

View File

@@ -28,6 +28,15 @@ function EditDaftarInformasiPublik() {
tanggal: '',
});
// Check if form is valid
const isFormValid = () => {
return (
formData.jenisInformasi?.trim() !== '' &&
formData.deskripsi?.trim() !== '' &&
formData.tanggal?.trim() !== ''
);
};
const formatDateForInput = (dateString: string) => {
if (!dateString) return '';
const date = new Date(dateString);
@@ -128,10 +137,13 @@ function EditDaftarInformasiPublik() {
<Group justify="right" mt="md">
<Button
onClick={handleSubmit}
disabled={!isFormValid()}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid()
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -11,6 +11,15 @@ export default function CreateDaftarInformasi() {
const daftarInformasi = useProxy(daftarInformasiPublik);
const router = useRouter();
// Check if form is valid
const isFormValid = () => {
return (
daftarInformasi.create.form.jenisInformasi?.trim() !== '' &&
daftarInformasi.create.form.deskripsi?.trim() !== '' &&
daftarInformasi.create.form.tanggal?.trim() !== ''
);
};
const resetForm = () => {
daftarInformasi.create.form = {
jenisInformasi: "",
@@ -106,10 +115,13 @@ export default function CreateDaftarInformasi() {
<Button
onClick={handleSubmit}
loading={daftarInformasi.create.loading}
disabled={!isFormValid() || daftarInformasi.create.loading}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || daftarInformasi.create.loading
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -27,6 +27,21 @@ function EditDasarHukum() {
content: '',
});
// Helper function to check if rich text content is empty
const isRichTextEmpty = (content: string) => {
// Remove HTML tags and check if the resulting text is empty
const plainText = content.replace(/<[^>]*>/g, '').trim();
return plainText === '' || content.trim() === '<p></p>' || content.trim() === '<p><br></p>';
};
// Check if form is valid
const isFormValid = () => {
return (
!isRichTextEmpty(formData.judul) &&
!isRichTextEmpty(formData.content)
);
};
// Load data awal sekali
useShallowEffect(() => {
if (!dasarHukumState.findById.data) {
@@ -137,8 +152,11 @@ function EditDasarHukum() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -57,6 +57,17 @@ function EditResponden() {
const [isSubmitting, setIsSubmitting] = useState(false);
// Check if form is valid
const isFormValid = () => {
return (
formData.name?.trim() !== '' &&
formData.tanggal?.trim() !== '' &&
formData.jenisKelaminId?.trim() !== '' &&
formData.ratingId?.trim() !== '' &&
formData.kelompokUmurId?.trim() !== ''
);
};
// 🔹 Load data pilihan select
const loadSelectOptions = useCallback(() => {
indeksKepuasanState.jenisKelaminResponden.findMany.load();
@@ -231,8 +242,11 @@ function EditResponden() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -16,6 +16,17 @@ function RespondenCreate() {
const [donutData, setDonutData] = useState<any[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
// Check if form is valid
const isFormValid = () => {
return (
stategrafikBerdasarkanResponden.create.form.name?.trim() !== '' &&
stategrafikBerdasarkanResponden.create.form.tanggal?.trim() !== '' &&
stategrafikBerdasarkanResponden.create.form.jenisKelaminId?.trim() !== '' &&
stategrafikBerdasarkanResponden.create.form.ratingId?.trim() !== '' &&
stategrafikBerdasarkanResponden.create.form.kelompokUmurId?.trim() !== ''
);
};
const resetForm = () => {
stategrafikBerdasarkanResponden.create.form = {
...stategrafikBerdasarkanResponden.create.form,
@@ -151,8 +162,11 @@ function RespondenCreate() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -56,6 +56,12 @@ export default function EditPegawaiPPID() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
// Helper function to validate email format
const isValidEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
// Format date for <input type="date">
const formatDateForInput = (dateString: string) => {
if (!dateString) return '';
@@ -63,6 +69,20 @@ export default function EditPegawaiPPID() {
return date.toISOString().split('T')[0];
};
// Check if form is valid
const isFormValid = () => {
return (
formData.namaLengkap?.trim() !== '' &&
formData.gelarAkademik?.trim() !== '' &&
formData.posisiId !== '' &&
formData.tanggalMasuk !== '' &&
formData.email !== '' &&
isValidEmail(formData.email) &&
formData.telepon !== '' &&
formData.alamat !== ''
);
};
useEffect(() => {
const loadPegawai = async () => {
try {
@@ -347,8 +367,11 @@ export default function EditPegawaiPPID() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -24,6 +24,27 @@ function CreatePegawaiPPID() {
resetForm();
}, []);
// Helper function to validate email format
const isValidEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
// Check if form is valid
const isFormValid = () => {
return (
stateOrganisasi.create.form.namaLengkap?.trim() !== '' &&
stateOrganisasi.create.form.gelarAkademik?.trim() !== '' &&
stateOrganisasi.create.form.posisiId !== '' &&
stateOrganisasi.create.form.tanggalMasuk !== '' &&
stateOrganisasi.create.form.email !== '' &&
isValidEmail(stateOrganisasi.create.form.email) &&
stateOrganisasi.create.form.telepon !== '' &&
stateOrganisasi.create.form.alamat !== '' &&
file !== null
);
};
const resetForm = () => {
stateOrganisasi.create.form = {
namaLengkap: "",
@@ -78,7 +99,7 @@ function CreatePegawaiPPID() {
};
return (
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
@@ -99,15 +120,16 @@ function CreatePegawaiPPID() {
<Stack gap="md">
<Box>
<TextInput
required
label="Nama Lengkap"
placeholder="Masukkan nama lengkap"
value={stateOrganisasi.create.form.namaLengkap}
onChange={(e) => (stateOrganisasi.create.form.namaLengkap = e.currentTarget.value)}
required
/>
</Box>
<Box>
<TextInput
required
label="Gelar Akademik"
placeholder="Contoh: S.Kom"
value={stateOrganisasi.create.form.gelarAkademik}
@@ -191,6 +213,7 @@ function CreatePegawaiPPID() {
</Box>
<Box>
<TextInput
required
label="Tanggal Masuk"
type="date"
placeholder="Contoh: 2022-01-01"
@@ -201,6 +224,7 @@ function CreatePegawaiPPID() {
<Box>
<TextInput
required
label="Email"
type="email"
placeholder="Contoh: email@example.com"
@@ -211,6 +235,7 @@ function CreatePegawaiPPID() {
<Box>
<TextInput
required
type='number'
label="Nomor Telepon"
placeholder="Contoh: 08123456789"
@@ -221,6 +246,7 @@ function CreatePegawaiPPID() {
<Box>
<TextInput
required
label="Alamat"
placeholder="Contoh: Jl. Contoh No. 1"
value={stateOrganisasi.create.form.alamat}
@@ -229,35 +255,8 @@ function CreatePegawaiPPID() {
</Box>
<Box>
<Text fw="bold" fz="sm" mb={6}>
Posisi
</Text>
<Select
label="Kategori"
placeholder="Pilih kategori"
data={stateStrukturPPID.posisiOrganisasi.findManyAll.data?.map((item) => ({
label: item.nama,
value: item.id,
})) || []}
value={stateOrganisasi.create.form.posisiId || null}
onChange={(val: string | null) => {
if (val) {
const selected = stateStrukturPPID.posisiOrganisasi.findManyAll.data?.find(
(item) => item.id === val
);
if (selected) {
stateOrganisasi.create.form.posisiId = selected.id;
}
} else {
stateOrganisasi.create.form.posisiId = '';
}
}}
searchable
clearable
nothingFoundMessage="Tidak ditemukan"
required
/>
{/* <Select
label="Posisi"
placeholder="Pilih posisi"
data={stateStrukturPPID.posisiOrganisasi.findManyAll.data?.map(p => ({
value: p.id,
@@ -269,10 +268,10 @@ function CreatePegawaiPPID() {
}}
searchable
clearable
/> */}
required
/>
</Box>
{/* ======= Tombol Aksi ======= */}
<Group justify="right">
{/* Tombol Batal */}
@@ -291,8 +290,11 @@ function CreatePegawaiPPID() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -26,6 +26,15 @@ function EditPosisiOrganisasiPPID() {
const [isSubmitting, setIsSubmitting] = useState(false);
// Check if form is valid
const isFormValid = () => {
return (
formData.nama?.trim() !== '' &&
formData.hierarki?.toString().trim() !== '' &&
formData.deskripsi?.trim() !== ''
);
};
const [originalData, setOriginalData] = useState({
nama: "",
deskripsi: "",
@@ -174,8 +183,11 @@ function EditPosisiOrganisasiPPID() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -15,6 +15,15 @@ function CreatePosisiOrganisasiPPID() {
const stateOrganisasi = useProxy(stateStrukturPPID.posisiOrganisasi);
const [isSubmitting, setIsSubmitting] = useState(false);
// Check if form is valid
const isFormValid = () => {
return (
stateOrganisasi.create.form.nama?.trim() !== '' &&
stateOrganisasi.create.form.hierarki?.toString().trim() !== '' &&
stateOrganisasi.create.form.deskripsi?.trim() !== ''
);
};
useEffect(() => {
stateOrganisasi.findMany.load();
}, []);
@@ -115,8 +124,11 @@ function CreatePosisiOrganisasiPPID() {
onClick={handleSubmit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}

View File

@@ -20,6 +20,21 @@ function VisiMisiPPIDEdit() {
const [originalData, setOriginalData] = useState({ visi: '', misi: '' });
const [isSubmitting, setIsSubmitting] = useState(false);
// Helper function to check if rich text content is empty
const isRichTextEmpty = (content: string) => {
// Remove HTML tags and check if the resulting text is empty
const plainText = content.replace(/<[^>]*>/g, '').trim();
return plainText === '' || content.trim() === '<p></p>' || content.trim() === '<p><br></p>';
};
// Check if form is valid
const isFormValid = () => {
return (
!isRichTextEmpty(formData.visi) &&
!isRichTextEmpty(formData.misi)
);
};
// Initialize global data ke state lokal saat pertama load
useShallowEffect(() => {
if (!visiMisi.findById.data) {
@@ -116,8 +131,11 @@ function VisiMisiPPIDEdit() {
onClick={submit}
radius="md"
size="md"
disabled={!isFormValid() || isSubmitting}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
background: !isFormValid() || isSubmitting
? `linear-gradient(135deg, #cccccc, #eeeeee)`
: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}