Fix QC Kak Inno Admin, Fix QC Keano UI User, Fix QC Pak jun tabel apbdes

This commit is contained in:
2025-11-12 17:42:31 +08:00
parent 417a8937f5
commit 9622eb5a9a
354 changed files with 11444 additions and 4012 deletions

View File

@@ -4,10 +4,12 @@ import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -26,6 +28,7 @@ function CreateArtikelKesehatan() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
stateArtikelKesehatan.create.form = {
@@ -62,25 +65,32 @@ function CreateArtikelKesehatan() {
const handleSubmit = async (e?: React.FormEvent) => {
e?.preventDefault();
if (!file) {
return toast.warn('Silakan pilih file gambar terlebih dahulu');
try {
if (!file) {
return toast.warn('Silakan pilih file gambar terlebih dahulu');
}
const res = await ApiFetch.api.fileStorage.create.post({
file,
name: file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error('Gagal mengunggah gambar, silakan coba lagi');
}
stateArtikelKesehatan.create.form.imageId = uploaded.id;
await stateArtikelKesehatan.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan');
} catch (error) {
console.error('Error submitting form:', error);
toast.error('Gagal menyimpan data, silakan coba lagi');
} finally {
setIsSubmitting(false);
}
const res = await ApiFetch.api.fileStorage.create.post({
file,
name: file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error('Gagal mengunggah gambar, silakan coba lagi');
}
stateArtikelKesehatan.create.form.imageId = uploaded.id;
await stateArtikelKesehatan.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan');
};
return (
@@ -124,7 +134,7 @@ function CreateArtikelKesehatan() {
}}
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
radius="md"
p="xl"
>
@@ -145,7 +155,7 @@ function CreateArtikelKesehatan() {
</Dropzone>
{previewImage && (
<Box mt="sm" style={{ textAlign: 'center' }}>
<Box pos={"relative"} mt="sm" style={{ textAlign: 'center' }}>
<Image
src={previewImage}
alt="Preview Gambar"
@@ -157,6 +167,24 @@ function CreateArtikelKesehatan() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
@@ -164,7 +192,7 @@ function CreateArtikelKesehatan() {
<TextInput
label={"Judul"}
placeholder="Masukkan judul"
defaultValue={stateArtikelKesehatan.create.form.title}
value={stateArtikelKesehatan.create.form.title}
onChange={(e) => {
stateArtikelKesehatan.create.form.title = e.target.value;
}}
@@ -173,7 +201,7 @@ function CreateArtikelKesehatan() {
<TextInput
label={"Deskripsi"}
placeholder="Masukkan deskripsi"
defaultValue={stateArtikelKesehatan.create.form.content}
value={stateArtikelKesehatan.create.form.content}
onChange={(e) => {
stateArtikelKesehatan.create.form.content = e.target.value;
}}
@@ -196,7 +224,7 @@ function CreateArtikelKesehatan() {
label={"Judul Gejala"}
required
placeholder="Masukkan judul gejala penyakit"
defaultValue={stateArtikelKesehatan.create.form.symptom.title}
value={stateArtikelKesehatan.create.form.symptom.title}
onChange={(e) => {
stateArtikelKesehatan.create.form.symptom.title = e.target.value;
}}
@@ -220,7 +248,7 @@ function CreateArtikelKesehatan() {
label={"Judul Pencegahan"}
required
placeholder="Masukkan judul"
defaultValue={stateArtikelKesehatan.create.form.prevention.title}
value={stateArtikelKesehatan.create.form.prevention.title}
onChange={(e) => {
stateArtikelKesehatan.create.form.prevention.title = e.target.value;
}}
@@ -241,7 +269,7 @@ function CreateArtikelKesehatan() {
label={"Judul Pertolongan Pertama"}
required
placeholder="Masukkan judul"
defaultValue={stateArtikelKesehatan.create.form.firstAid.title}
value={stateArtikelKesehatan.create.form.firstAid.title}
onChange={(e) => {
stateArtikelKesehatan.create.form.firstAid.title = e.target.value;
}}
@@ -262,7 +290,7 @@ function CreateArtikelKesehatan() {
label={"Judul Mitos dan Fakta"}
required
placeholder="Masukkan judul"
defaultValue={stateArtikelKesehatan.create.form.mythVsFact.title}
value={stateArtikelKesehatan.create.form.mythVsFact.title}
onChange={(e) => {
stateArtikelKesehatan.create.form.mythVsFact.title = e.target.value;
}}
@@ -300,8 +328,20 @@ function CreateArtikelKesehatan() {
{/* Submit Button */}
<Group justify="right">
{/* Tombol Batal */}
<Button
type="submit"
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
@@ -310,7 +350,7 @@ function CreateArtikelKesehatan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
@@ -8,6 +9,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
Text,
@@ -45,6 +47,7 @@ function EditFasilitasKesehatan() {
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const router = useRouter();
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<FasilitasKesehatanFormBase>({
name: '',
@@ -56,6 +59,16 @@ function EditFasilitasKesehatan() {
tarifDanLayanan: { layanan: '', tarif: '' },
});
const [originalData, setOriginalData] = useState<FasilitasKesehatanFormBase>({
name: '',
informasiUmum: { fasilitas: '', alamat: '', jamOperasional: '' },
layananUnggulan: { content: '' },
dokterdanTenagaMedis: { name: '', specialist: '', jadwal: '' },
fasilitasPendukung: { content: '' },
prosedurPendaftaran: { content: '' },
tarifDanLayanan: { layanan: '', tarif: '' },
});
// Helper untuk update nested state
const updateForm = <K extends keyof FasilitasKesehatanFormBase>(
key: K,
@@ -71,26 +84,73 @@ function EditFasilitasKesehatan() {
[key]: { ...prev[key] as object, [nestedKey]: value },
}));
const deepClone = (obj: any): any => {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
console.warn('Gagal deep clone dengan JSON fallback:', error);
return obj; // fallback (berisiko shared reference)
}
};
// Load data
useEffect(() => {
const load = async () => {
const id = params?.id as string;
if (!id) return;
try {
await state.edit.load(id);
const form = state.edit.form;
if (form) setFormData(form as FasilitasKesehatanFormBase);
const loadedData = state.edit.form;
if (!loadedData) {
toast.error('Data tidak ditemukan');
return;
}
// Gunakan JSON fallback untuk deep clone
const clonedData = deepClone(loadedData) as FasilitasKesehatanFormBase;
setFormData(clonedData);
setOriginalData(clonedData);
} catch (err) {
console.error(err);
toast.error('Gagal memuat data fasilitas kesehatan');
}
};
load();
}, [params?.id]);
const handleResetForm = () => {
setFormData({
name: originalData.name,
informasiUmum:
{
fasilitas: originalData.informasiUmum.fasilitas,
alamat: originalData.informasiUmum.alamat,
jamOperasional: originalData.informasiUmum.jamOperasional
},
layananUnggulan: { content: originalData.layananUnggulan.content },
dokterdanTenagaMedis: {
name: originalData.dokterdanTenagaMedis.name,
specialist: originalData.dokterdanTenagaMedis.specialist,
jadwal: originalData.dokterdanTenagaMedis.jadwal
},
fasilitasPendukung: { content: originalData.fasilitasPendukung.content },
prosedurPendaftaran: { content: originalData.prosedurPendaftaran.content },
tarifDanLayanan: {
layanan: originalData.tarifDanLayanan.layanan,
tarif: originalData.tarifDanLayanan.tarif
},
});
toast.info("Form dikembalikan ke data awal");
};
// Submit
const handleSubmit = async () => {
try {
setIsSubmitting(true);
state.edit.form = { ...state.edit.form, ...formData };
const success = await state.edit.submit();
if (success) {
@@ -100,6 +160,8 @@ function EditFasilitasKesehatan() {
} catch (err) {
console.error(err);
toast.error('Terjadi kesalahan saat memperbarui data fasilitas kesehatan');
} finally {
setIsSubmitting(false);
}
};
@@ -230,19 +292,30 @@ function EditFasilitasKesehatan() {
</Box>
{/* Tombol Simpan */}
<Group justify="flex-end">
<Group justify="right">
{/* Tombol Batal */}
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
size="md"
loading={state.edit.loading}
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -6,6 +6,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
Text,
@@ -14,6 +15,7 @@ import {
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
@@ -21,6 +23,7 @@ import { useProxy } from 'valtio/utils';
function CreateFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
stateFasilitasKesehatan.create.form = {
@@ -53,10 +56,18 @@ function CreateFasilitasKesehatan() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await stateFasilitasKesehatan.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan');
try {
setIsSubmitting(true);
await stateFasilitasKesehatan.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan');
} catch (error) {
console.error(error);
toast.error('Gagal menyimpan data');
} finally {
setIsSubmitting(false);
}
};
return (
@@ -89,7 +100,7 @@ function CreateFasilitasKesehatan() {
<TextInput
label={"Nama Fasilitas Kesehatan"}
placeholder="Masukkan nama fasilitas kesehatan"
defaultValue={stateFasilitasKesehatan.create.form.name}
value={stateFasilitasKesehatan.create.form.name}
onChange={(e) => (stateFasilitasKesehatan.create.form.name = e.target.value)}
required
/>
@@ -100,21 +111,21 @@ function CreateFasilitasKesehatan() {
<TextInput
label="Fasilitas"
placeholder="Masukkan fasilitas"
defaultValue={stateFasilitasKesehatan.create.form.informasiUmum.fasilitas}
value={stateFasilitasKesehatan.create.form.informasiUmum.fasilitas}
onChange={(e) => (stateFasilitasKesehatan.create.form.informasiUmum.fasilitas = e.target.value)}
required
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat"
defaultValue={stateFasilitasKesehatan.create.form.informasiUmum.alamat}
value={stateFasilitasKesehatan.create.form.informasiUmum.alamat}
onChange={(e) => (stateFasilitasKesehatan.create.form.informasiUmum.alamat = e.target.value)}
required
/>
<TextInput
label="Jam Operasional"
placeholder="Masukkan jam operasional"
defaultValue={stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional}
value={stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional}
onChange={(e) => (stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional = e.target.value)}
required
/>
@@ -135,21 +146,21 @@ function CreateFasilitasKesehatan() {
<TextInput
label="Nama Dokter"
placeholder="Masukkan nama dokter"
defaultValue={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name}
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name}
onChange={(e) => (stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name = e.target.value)}
required
/>
<TextInput
label="Spesialis"
placeholder="Masukkan spesialis"
defaultValue={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist}
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist}
onChange={(e) => (stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist = e.target.value)}
required
/>
<TextInput
label="Jadwal"
placeholder="Masukkan jadwal"
defaultValue={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal}
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal}
onChange={(e) => (stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal = e.target.value)}
required
/>
@@ -179,23 +190,35 @@ function CreateFasilitasKesehatan() {
<TextInput
label="Tarif"
placeholder="Masukkan tarif"
defaultValue={stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif}
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif}
onChange={(e) => (stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif = e.target.value)}
required
/>
<TextInput
label="Layanan"
placeholder="Masukkan layanan"
defaultValue={stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan}
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan}
onChange={(e) => (stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan = e.target.value)}
required
/>
</Box>
{/* Submit */}
<Group justify="right" mt="md">
<Group justify="right">
{/* Tombol Batal */}
<Button
type="submit"
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
@@ -204,7 +227,7 @@ function CreateFasilitasKesehatan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -40,7 +40,7 @@ function CreateDokter() {
<TextInput
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
placeholder="masukkan nama dokter"
defaultValue={createState.create.create.form.name}
value={createState.create.create.form.name}
onChange={(e) => {
createState.create.create.form.name = e.target.value;
}}
@@ -49,7 +49,7 @@ function CreateDokter() {
<TextInput
label={<Text fz="sm" fw="bold">Specialist</Text>}
placeholder="masukkan specialist"
defaultValue={createState.create.create.form.specialist}
value={createState.create.create.form.specialist}
onChange={(e) => {
createState.create.create.form.specialist = e.target.value;
}}

View File

@@ -19,7 +19,7 @@ import {
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -28,18 +28,10 @@ import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_wa
function FasilitasKesehatan() {
const router = useRouter();
const [search, setSearch] = useState("");
return (
<Box>
{/* Tombol Back */}
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={25} />
</Button>
</Box>
{/* Header Search */}
<HeaderSearch
title='Fasilitas Kesehatan'

View File

@@ -7,6 +7,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
TextInput,
@@ -17,11 +18,13 @@ import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertToISODate } from '../../../persentase_data_kelahiran_kematian/lib/dateUtils';
function EditGrafikHasilKepuasan() {
const editState = useProxy(grafikkepuasan);
const router = useRouter();
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
nama: '',
@@ -31,6 +34,14 @@ function EditGrafikHasilKepuasan() {
penyakit: '',
});
const [originalData, setOriginalData] = useState({
nama: '',
tanggal: '',
jenisKelamin: '',
alamat: '',
penyakit: '',
});
// Load data once
useEffect(() => {
const loadData = async () => {
@@ -39,13 +50,25 @@ function EditGrafikHasilKepuasan() {
try {
const data = await editState.update.load(id);
if (data) setFormData({
nama: data.nama || '',
tanggal: data.tanggal || '',
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyakit: data.penyakit || '',
});
if (data) {
const formattedTanggal = convertToISODate(data.tanggal);
setFormData({
nama: data.nama || '',
tanggal: formattedTanggal,
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyakit: data.penyakit || '',
});
setOriginalData({
nama: data.nama || '',
tanggal: formattedTanggal,
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyakit: data.penyakit || '',
});
}
} catch (err) {
console.error("Error loading grafik hasil kepuasan:", err);
toast.error("Gagal memuat data grafik hasil kepuasan");
@@ -60,8 +83,20 @@ function EditGrafikHasilKepuasan() {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleResetForm = () => {
setFormData({
nama: originalData.nama,
tanggal: originalData.tanggal,
jenisKelamin: originalData.jenisKelamin,
alamat: originalData.alamat,
penyakit: originalData.penyakit,
});
toast.info("Form dikembalikan ke data awal");
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
editState.update.form = { ...editState.update.form, ...formData };
await editState.update.submit();
toast.success('Grafik hasil kepuasan berhasil diperbarui!');
@@ -69,6 +104,8 @@ function EditGrafikHasilKepuasan() {
} catch (err) {
console.error('Error updating grafik hasil kepuasan:', err);
toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan');
} finally {
setIsSubmitting(false);
}
};
@@ -112,6 +149,17 @@ function EditGrafikHasilKepuasan() {
))}
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -122,7 +170,7 @@ function EditGrafikHasilKepuasan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -7,6 +7,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
TextInput,
@@ -15,12 +16,14 @@ import {
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateGrafikHasilKepuasanMasyarakat() {
const stateGrafikKepuasan = useProxy(grafikkepuasan);
const [chartData, setChartData] = useState<any[]>([]);
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
stateGrafikKepuasan.create.form = {
@@ -33,9 +36,17 @@ function CreateGrafikHasilKepuasanMasyarakat() {
};
const handleSubmit = async () => {
await stateGrafikKepuasan.create.create();
resetForm();
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
try {
setIsSubmitting(true);
await stateGrafikKepuasan.create.create();
resetForm();
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
} catch (error) {
console.error("Error creating grafik kepuasan:", error);
toast.error("Terjadi kesalahan saat membuat grafik kepuasan");
} finally {
setIsSubmitting(false);
}
};
return (
@@ -68,7 +79,7 @@ function CreateGrafikHasilKepuasanMasyarakat() {
<TextInput
label="Nama"
placeholder="Masukkan nama"
defaultValue={stateGrafikKepuasan.create.form.nama}
value={stateGrafikKepuasan.create.form.nama}
onChange={(e) => (stateGrafikKepuasan.create.form.nama = e.target.value)}
required
/>
@@ -76,33 +87,44 @@ function CreateGrafikHasilKepuasanMasyarakat() {
type="date"
label="Tanggal"
placeholder="Masukkan tanggal"
defaultValue={stateGrafikKepuasan.create.form.tanggal}
value={stateGrafikKepuasan.create.form.tanggal}
onChange={(e) => (stateGrafikKepuasan.create.form.tanggal = e.target.value)}
required
/>
<TextInput
label="Jenis Kelamin"
placeholder="Masukkan jenis kelamin"
defaultValue={stateGrafikKepuasan.create.form.jenisKelamin}
value={stateGrafikKepuasan.create.form.jenisKelamin}
onChange={(e) => (stateGrafikKepuasan.create.form.jenisKelamin = e.target.value)}
required
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat"
defaultValue={stateGrafikKepuasan.create.form.alamat}
value={stateGrafikKepuasan.create.form.alamat}
onChange={(e) => (stateGrafikKepuasan.create.form.alamat = e.target.value)}
required
/>
<TextInput
label="Penyakit"
placeholder="Masukkan penyakit"
defaultValue={stateGrafikKepuasan.create.form.penyakit}
value={stateGrafikKepuasan.create.form.penyakit}
onChange={(e) => (stateGrafikKepuasan.create.form.penyakit = e.target.value)}
required
/>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -113,7 +135,7 @@ function CreateGrafikHasilKepuasanMasyarakat() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -4,7 +4,7 @@
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Loader, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -39,7 +39,10 @@ function EditJadwalKegiatan() {
const router = useRouter();
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<JadwalKegiatanFormBase>(emptyForm());
const [originalData, setOriginalData] = useState<JadwalKegiatanFormBase>(emptyForm());
// Helper untuk update nested state
const updateNested = <
@@ -85,6 +88,19 @@ function EditJadwalKegiatan() {
syaratKetentuanJadwalKegiatan: { content: form.syaratKetentuanJadwalKegiatan?.content || '' },
dokumenJadwalKegiatan: { content: form.dokumenJadwalKegiatan?.content || '' },
});
setOriginalData({
content: form.content || '',
informasiJadwalKegiatan: {
name: form.informasiJadwalKegiatan?.name || '',
tanggal: form.informasiJadwalKegiatan?.tanggal || '',
waktu: form.informasiJadwalKegiatan?.waktu || '',
lokasi: form.informasiJadwalKegiatan?.lokasi || '',
},
deskripsiJadwalKegiatan: { deskripsi: form.deskripsiJadwalKegiatan?.deskripsi || '' },
layananJadwalKegiatan: { content: form.layananJadwalKegiatan?.content || '' },
syaratKetentuanJadwalKegiatan: { content: form.syaratKetentuanJadwalKegiatan?.content || '' },
dokumenJadwalKegiatan: { content: form.dokumenJadwalKegiatan?.content || '' },
});
}
} catch (error) {
console.error("Error loading jadwal kegiatan:", error);
@@ -94,8 +110,26 @@ function EditJadwalKegiatan() {
loadJadwalKegiatan();
}, [params?.id]);
const handleResetForm = () => {
setFormData({
content: originalData.content || '',
informasiJadwalKegiatan: {
name: originalData.informasiJadwalKegiatan?.name || '',
tanggal: originalData.informasiJadwalKegiatan?.tanggal || '',
waktu: originalData.informasiJadwalKegiatan?.waktu || '',
lokasi: originalData.informasiJadwalKegiatan?.lokasi || '',
},
deskripsiJadwalKegiatan: { deskripsi: originalData.deskripsiJadwalKegiatan?.deskripsi || '' },
layananJadwalKegiatan: { content: originalData.layananJadwalKegiatan?.content || '' },
syaratKetentuanJadwalKegiatan: { content: originalData.syaratKetentuanJadwalKegiatan?.content || '' },
dokumenJadwalKegiatan: { content: originalData.dokumenJadwalKegiatan?.content || '' },
});
toast.info("Form dikembalikan ke data awal");
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
stateJadwalKegiatan.edit.form = { ...stateJadwalKegiatan.edit.form, ...formData };
const success = await stateJadwalKegiatan.edit.submit();
if (success) {
@@ -105,6 +139,8 @@ function EditJadwalKegiatan() {
} catch (error) {
console.error("Error updating jadwal kegiatan:", error);
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data jadwal kegiatan");
} finally {
setIsSubmitting(false);
}
};
@@ -190,6 +226,17 @@ function EditJadwalKegiatan() {
{/* Submit */}
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -200,7 +247,7 @@ function EditJadwalKegiatan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -6,6 +6,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
Text,
@@ -14,12 +15,14 @@ import {
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateJadwalKegiatan() {
const stateJadwalKegiatan = useProxy(jadwalKegiatanState);
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
stateJadwalKegiatan.create.form = {
@@ -47,11 +50,18 @@ function CreateJadwalKegiatan() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await stateJadwalKegiatan.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan');
try {
setIsSubmitting(true);
await stateJadwalKegiatan.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan');
} catch (error) {
console.error(error);
toast.error('Gagal menyimpan data jadwal kegiatan');
} finally {
setIsSubmitting(false);
}
};
return (
@@ -84,7 +94,7 @@ function CreateJadwalKegiatan() {
<TextInput
label="Nama Jadwal Kegiatan"
placeholder="Masukkan nama jadwal kegiatan"
defaultValue={stateJadwalKegiatan.create.form.content}
value={stateJadwalKegiatan.create.form.content}
onChange={(e) => {
stateJadwalKegiatan.create.form.content = e.target.value;
}}
@@ -107,7 +117,7 @@ function CreateJadwalKegiatan() {
label="Nama"
required
placeholder="Masukkan nama"
defaultValue={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name}
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name}
onChange={(e) => {
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name = e.target.value;
}}
@@ -116,7 +126,7 @@ function CreateJadwalKegiatan() {
type="date"
required
label="Tanggal"
defaultValue={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal}
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal}
onChange={(e) => {
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal = e.target.value;
}}
@@ -125,7 +135,7 @@ function CreateJadwalKegiatan() {
label="Waktu"
required
placeholder="Masukkan waktu"
defaultValue={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu}
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu}
onChange={(e) => {
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu = e.target.value;
}}
@@ -134,7 +144,7 @@ function CreateJadwalKegiatan() {
label="Lokasi"
required
placeholder="Masukkan lokasi"
defaultValue={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi}
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi}
onChange={(e) => {
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi = e.target.value;
}}
@@ -172,8 +182,20 @@ function CreateJadwalKegiatan() {
</Box>
{/* Save Button */}
<Group justify="right">
{/* Tombol Batal */}
<Button
type="submit"
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
@@ -182,7 +204,7 @@ function CreateJadwalKegiatan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -19,7 +19,7 @@ import {
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -27,18 +27,10 @@ import HeaderSearch from '../../../_com/header';
import jadwalKegiatanState from '../../../_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
function JadwalKegiatan() {
const router = useRouter();
const [search, setSearch] = useState("");
return (
<Box>
{/* Tombol Back */}
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={25} />
</Button>
</Box>
{/* Header Search */}
<HeaderSearch
title="Jadwal Kegiatan"

View File

@@ -7,6 +7,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
TextInput,
@@ -17,11 +18,13 @@ import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertToISODate } from '../../../lib/dateUtils';
function EditKelahiran() {
const editState = useProxy(persentaseKelahiranKematian.kelahiran);
const router = useRouter();
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
nama: '',
@@ -30,26 +33,44 @@ function EditKelahiran() {
alamat: '',
});
const [originalData, setOriginalData] = useState({
nama: '',
tanggal: '',
jenisKelamin: '',
alamat: '',
});
// Load data saat mount atau params.id berubah
useEffect(() => {
const loadKelahiran = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await editState.edit.load(id);
if (data) setFormData({
nama: data.nama || '',
tanggal: data.tanggal || '',
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || ''
});
if (data) {
const formattedTanggal = convertToISODate(data.tanggal);
setFormData({
nama: data.nama || '',
tanggal: formattedTanggal,
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || ''
});
setOriginalData({
nama: data.nama || '',
tanggal: formattedTanggal,
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || ''
});
}
} catch (error) {
console.error('Error loading data kelahiran:', error);
toast.error('Gagal memuat data kelahiran');
}
};
loadKelahiran();
}, [params?.id]);
@@ -57,8 +78,20 @@ function EditKelahiran() {
setFormData((prev) => ({ ...prev, [key]: value }));
};
const handleResetForm = () => {
setFormData({
nama: originalData.nama,
tanggal: originalData.tanggal,
jenisKelamin: originalData.jenisKelamin,
alamat: originalData.alamat,
});
toast.info("Form dikembalikan ke data awal");
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
// Update global state hanya saat submit
editState.edit.form = { ...editState.edit.form, ...formData };
await editState.edit.update();
@@ -67,6 +100,8 @@ function EditKelahiran() {
} catch (error) {
console.error('Error updating data kelahiran:', error);
toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
} finally {
setIsSubmitting(false);
}
};
@@ -123,6 +158,17 @@ function EditKelahiran() {
/>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -133,7 +179,7 @@ function EditKelahiran() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -5,6 +5,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
Text,
@@ -13,13 +14,15 @@ import {
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateKelahiran() {
const createState = useProxy(persentaseKelahiranKematian.kelahiran);
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
createState.create.form = {
@@ -32,11 +35,19 @@ function CreateKelahiran() {
const handleSubmit = async () => {
await createState.create.create();
resetForm();
router.push(
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran'
);
try {
setIsSubmitting(true);
await createState.create.create();
resetForm();
router.push(
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran'
);
} catch (error) {
console.error('Error creating kelahiran:', error);
toast.error('Gagal menambahkan data kelahiran');
} finally {
setIsSubmitting(false);
}
};
@@ -71,7 +82,7 @@ function CreateKelahiran() {
<TextInput
label={<Text fw="bold" fz="sm">Nama</Text>}
placeholder="Masukkan nama"
defaultValue={createState.create.form.nama}
value={createState.create.form.nama}
onChange={(e) => (createState.create.form.nama = e.target.value)}
required
/>
@@ -79,27 +90,38 @@ function CreateKelahiran() {
type="date"
label={<Text fw="bold" fz="sm">Tanggal</Text>}
placeholder="Masukkan tanggal"
defaultValue={createState.create.form.tanggal}
value={createState.create.form.tanggal}
onChange={(e) => (createState.create.form.tanggal = e.target.value)}
required
/>
<TextInput
label={<Text fw="bold" fz="sm">Jenis Kelamin</Text>}
placeholder="Masukkan jenis kelamin"
defaultValue={createState.create.form.jenisKelamin}
value={createState.create.form.jenisKelamin}
onChange={(e) => (createState.create.form.jenisKelamin = e.target.value)}
required
/>
<TextInput
label={<Text fw="bold" fz="sm">Alamat</Text>}
placeholder="Masukkan alamat"
defaultValue={createState.create.form.alamat}
value={createState.create.form.alamat}
onChange={(e) => (createState.create.form.alamat = e.target.value)}
required
/>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -110,7 +132,7 @@ function CreateKelahiran() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -8,6 +8,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
Text,
@@ -19,11 +20,13 @@ import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertToISODate } from '../../../lib/dateUtils';
function EditKematian() {
const editState = useProxy(persentaseKelahiranKematian.kematian);
const router = useRouter();
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
nama: '',
@@ -33,6 +36,14 @@ function EditKematian() {
penyebab: '',
});
const [originalData, setOriginalData] = useState({
nama: '',
tanggal: '',
jenisKelamin: '',
alamat: '',
penyebab: '',
});
// Load data saat mount
useEffect(() => {
const loadData = async () => {
@@ -42,12 +53,22 @@ function EditKematian() {
try {
const data = await editState.edit.load(id);
if (data) {
const formattedTanggal = convertToISODate(data.tanggal);
setFormData({
nama: data.nama || '',
tanggal: data.tanggal || '',
tanggal: formattedTanggal,
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyebab: data.penyebab || '',
penyebab: data.penyebab || ''
});
setOriginalData({
nama: data.nama || '',
tanggal: formattedTanggal,
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyebab: data.penyebab || ''
});
}
} catch (error) {
@@ -63,8 +84,20 @@ function EditKematian() {
setFormData(prev => ({ ...prev, [key]: value }));
};
const handleResetForm = () => {
setFormData({
nama: originalData.nama,
tanggal: originalData.tanggal,
jenisKelamin: originalData.jenisKelamin,
alamat: originalData.alamat,
penyebab: originalData.penyebab,
});
toast.info("Form dikembalikan ke data awal");
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
// Update global state saat submit
editState.edit.form = { ...editState.edit.form, ...formData };
await editState.edit.update();
@@ -75,6 +108,8 @@ function EditKematian() {
} catch (error) {
console.error('Error updating data kematian:', error);
toast.error('Terjadi kesalahan saat memperbarui data kematian');
} finally {
setIsSubmitting(false);
}
};
@@ -144,6 +179,17 @@ function EditKematian() {
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -154,7 +200,7 @@ function EditKematian() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -6,6 +6,7 @@ import {
Box,
Button,
Group,
Loader,
Paper,
Stack,
TextInput,
@@ -13,18 +14,14 @@ import {
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateKematian() {
const createState = useProxy(persentaseKelahiranKematian.kematian);
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
createState.create.form = {
@@ -38,19 +35,27 @@ function CreateKematian() {
const handleSubmit = async () => {
if (!createState.create.form.nama) {
return toast.warn('Nama wajib diisi');
}
if (!createState.create.form.tanggal) {
return toast.warn('Tanggal wajib diisi');
}
try {
setIsSubmitting(true);
if (!createState.create.form.nama) {
return toast.warn('Nama wajib diisi');
}
if (!createState.create.form.tanggal) {
return toast.warn('Tanggal wajib diisi');
}
await createState.create.create();
resetForm();
router.push(
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian'
);
await createState.create.create();
resetForm();
router.push(
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian'
);
} catch (error) {
console.error('Error creating data kematian:', error);
toast.error('Gagal menambahkan data kematian');
} finally {
setIsSubmitting(false);
}
};
@@ -80,7 +85,7 @@ function CreateKematian() {
<TextInput
label="Nama"
placeholder="Masukkan nama"
defaultValue={createState.create.form.nama}
value={createState.create.form.nama}
onChange={(e) => (createState.create.form.nama = e.target.value)}
required
/>
@@ -88,21 +93,21 @@ function CreateKematian() {
type="date"
label="Tanggal"
placeholder="Masukkan tanggal"
defaultValue={createState.create.form.tanggal}
value={createState.create.form.tanggal}
onChange={(e) => (createState.create.form.tanggal = e.target.value)}
required
/>
<TextInput
label="Jenis Kelamin"
placeholder="Masukkan jenis kelamin"
defaultValue={createState.create.form.jenisKelamin}
value={createState.create.form.jenisKelamin}
onChange={(e) => (createState.create.form.jenisKelamin = e.target.value)}
required
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat"
defaultValue={createState.create.form.alamat}
value={createState.create.form.alamat}
onChange={(e) => (createState.create.form.alamat = e.target.value)}
required
/>
@@ -120,6 +125,17 @@ function CreateKematian() {
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -130,7 +146,7 @@ function CreateKematian() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -0,0 +1,24 @@
export const convertToISODate = (dateString: string): string => {
if (!dateString) return '';
// Jika format dd/mm/yyyy
const parts = dateString.split('/');
if (parts.length === 3 && parts[0].length === 2 && parts[1].length === 2 && parts[2].length === 4) {
const [day, month, year] = parts;
return `${year}-${month}-${day}`;
}
// Jika sudah format YYYY-MM-DD, biarkan
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
return dateString;
}
// Jika format lain, coba parse dengan Date
const date = new Date(dateString);
if (!isNaN(date.getTime())) {
return date.toISOString().split('T')[0]; // YYYY-MM-DD
}
console.warn(`Format tanggal tidak dikenali: ${dateString}`);
return '';
};

View File

@@ -5,10 +5,12 @@ import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wab
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -26,13 +28,22 @@ function EditInfoWabahPenyakit() {
const infoWabahPenyakitState = useProxy(infoWabahPenyakit);
const router = useRouter();
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
name: '',
deskripsiSingkat: '',
deskripsi: '',
deskripsiLengkap: '',
imageId: '',
});
const [originalData, setOriginalData] = useState({
name: '',
deskripsiSingkat: '',
deskripsiLengkap: '',
imageId: '',
imageUrl: ''
});
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
@@ -53,9 +64,16 @@ function EditInfoWabahPenyakit() {
setFormData({
name: data.name || '',
deskripsiSingkat: data.deskripsiSingkat || '',
deskripsi: data.deskripsiLengkap || '',
deskripsiLengkap: data.deskripsiLengkap || '',
imageId: data.imageId || '',
});
setOriginalData({
name: data.name || '',
deskripsiSingkat: data.deskripsiSingkat || '',
deskripsiLengkap: data.deskripsiLengkap || '',
imageId: data.imageId || '',
imageUrl: data.image?.link || '',
});
if (data.image?.link) setPreviewImage(data.image.link);
}
@@ -70,6 +88,7 @@ function EditInfoWabahPenyakit() {
const handleSubmit = async () => {
try {
setIsSubmitting(true);
let uploadedImageId = formData.imageId;
// Upload file kalau ada
@@ -86,7 +105,7 @@ function EditInfoWabahPenyakit() {
...infoWabahPenyakitState.edit.form,
name: formData.name,
deskripsiSingkat: formData.deskripsiSingkat,
deskripsiLengkap: formData.deskripsi,
deskripsiLengkap: formData.deskripsiLengkap,
imageId: uploadedImageId,
};
@@ -96,9 +115,23 @@ function EditInfoWabahPenyakit() {
} catch (error) {
console.error(error);
toast.error('Terjadi kesalahan saat memperbarui info wabah penyakit');
} finally {
setIsSubmitting(false);
}
};
const handleResetForm = () => {
setFormData({
name: originalData.name,
deskripsiSingkat: originalData.deskripsiSingkat,
deskripsiLengkap: originalData.deskripsiLengkap,
imageId: originalData.imageId,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
toast.info("Form dikembalikan ke data awal");
};
const handleDrop = (files: File[]) => {
const selectedFile = files[0];
if (selectedFile) {
@@ -149,11 +182,11 @@ function EditInfoWabahPenyakit() {
<Box>
<Text fz="sm" fw="bold">
Deskripsi
Deskripsi Lengkap
</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => updateField('deskripsi', val)}
value={formData.deskripsiLengkap}
onChange={(val) => updateField('deskripsiLengkap', val)}
/>
</Box>
@@ -165,7 +198,7 @@ function EditInfoWabahPenyakit() {
onDrop={handleDrop}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -190,7 +223,7 @@ function EditInfoWabahPenyakit() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -203,11 +236,40 @@ function EditInfoWabahPenyakit() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -218,7 +280,7 @@ function EditInfoWabahPenyakit() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -2,10 +2,12 @@
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -26,6 +28,7 @@ function CreateInfoWabahPenyakit() {
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
infoWabahPenyakitState.create.form = {
@@ -39,25 +42,33 @@ function CreateInfoWabahPenyakit() {
};
const handleSubmit = async () => {
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
try {
setIsSubmitting(true);
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
}
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.create.form.imageId = uploaded.id;
await infoWabahPenyakitState.create.create();
resetForm();
router.push("/admin/kesehatan/info-wabah-penyakit")
} catch (error) {
console.error("Error creating info wabah penyakit:", error);
toast.error("Gagal menambahkan info wabah penyakit");
} finally {
setIsSubmitting(false);
}
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.create.form.imageId = uploaded.id;
await infoWabahPenyakitState.create.create();
resetForm();
router.push("/admin/kesehatan/info-wabah-penyakit")
};
return (
@@ -88,7 +99,7 @@ function CreateInfoWabahPenyakit() {
>
<Stack gap="md">
<TextInput
defaultValue={infoWabahPenyakitState.create.form.name}
value={infoWabahPenyakitState.create.form.name}
onChange={(val) => {
infoWabahPenyakitState.create.form.name = val.target.value;
}}
@@ -129,7 +140,7 @@ function CreateInfoWabahPenyakit() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -154,7 +165,7 @@ function CreateInfoWabahPenyakit() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -167,11 +178,40 @@ function CreateInfoWabahPenyakit() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -182,7 +222,7 @@ function CreateInfoWabahPenyakit() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -1,13 +1,16 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -28,12 +31,20 @@ function EditKontakDarurat() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
imageId: '',
whatsapp: '',
});
const [originalData, setOriginalData] = useState({
name: '',
deskripsi: '',
imageId: '',
whatsapp: '',
imageUrl: '',
});
const [loading, setLoading] = useState(true);
// Load data sekali saat mount
@@ -51,6 +62,13 @@ function EditKontakDarurat() {
imageId: data.imageId || '',
whatsapp: data.whatsapp || '',
});
setOriginalData({
name: data.name || '',
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
whatsapp: data.whatsapp || '',
imageUrl: data.image?.link || '',
});
if (data?.image?.link) setPreviewImage(data.image.link);
}
} catch (error) {
@@ -62,10 +80,11 @@ function EditKontakDarurat() {
};
loadKontakDarurat();
}, [params?.id, kontakDaruratState.edit]);
}, [params?.id]);
const handleSubmit = async () => {
try {
setIsSubmitting(true);
let imageId = formData.imageId;
// Upload file baru jika ada
@@ -89,9 +108,23 @@ function EditKontakDarurat() {
} catch (error) {
console.error("Error updating kontak darurat:", error);
toast.error("Terjadi kesalahan saat memperbarui kontak darurat");
} finally {
setIsSubmitting(false);
}
};
const handleResetForm = () => {
setFormData({
name: originalData.name,
deskripsi: originalData.deskripsi,
imageId: originalData.imageId,
whatsapp: originalData.whatsapp,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
toast.info("Form dikembalikan ke data awal");
};
if (loading) return <Text>Loading...</Text>;
return (
@@ -151,7 +184,7 @@ function EditKontakDarurat() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -172,7 +205,7 @@ function EditKontakDarurat() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -185,11 +218,40 @@ function EditKontakDarurat() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -200,7 +262,7 @@ function EditKontakDarurat() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -2,10 +2,12 @@
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -31,6 +33,7 @@ function CreateKontakDarurat() {
const kontakDaruratState = useProxy(kontakDarurat);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
kontakDaruratState.create.form = {
@@ -44,26 +47,34 @@ function CreateKontakDarurat() {
};
const handleSubmit = async () => {
if (!file) {
return toast.warn('Pilih file gambar terlebih dahulu');
try {
setIsSubmitting(true);
if (!file) {
return toast.warn('Pilih file gambar terlebih dahulu');
}
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');
}
kontakDaruratState.create.form.imageId = uploaded.id;
await kontakDaruratState.create.create();
resetForm();
router.push('/admin/kesehatan/kontak-darurat');
} catch (error) {
console.error('Error creating kontak darurat:', error);
toast.error('Gagal menambahkan kontak darurat');
} finally {
setIsSubmitting(false);
}
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');
}
kontakDaruratState.create.form.imageId = uploaded.id;
await kontakDaruratState.create.create();
resetForm();
router.push('/admin/kesehatan/kontak-darurat');
};
return (
@@ -94,7 +105,7 @@ function CreateKontakDarurat() {
>
<Stack gap="md">
<TextInput
defaultValue={kontakDaruratState.create.form.name}
value={kontakDaruratState.create.form.name}
onChange={(val) => {
kontakDaruratState.create.form.name = val.target.value;
}}
@@ -105,7 +116,7 @@ function CreateKontakDarurat() {
<TextInput
type='number'
defaultValue={kontakDaruratState.create.form.whatsapp}
value={kontakDaruratState.create.form.whatsapp}
onChange={(val) => {
kontakDaruratState.create.form.whatsapp = val.target.value;
}}
@@ -136,7 +147,7 @@ function CreateKontakDarurat() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group
justify="center"
@@ -178,7 +189,7 @@ function CreateKontakDarurat() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -191,11 +202,40 @@ function CreateKontakDarurat() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -206,7 +246,7 @@ function CreateKontakDarurat() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -6,10 +6,12 @@ import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penangan
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -27,6 +29,7 @@ function EditPenangananDarurat() {
const penangananDaruratState = useProxy(penangananDarurat)
const router = useRouter();
const params = useParams()
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
name: '',
@@ -34,6 +37,13 @@ function EditPenangananDarurat() {
imageId: '',
});
const [originalData, setOriginalData] = useState({
name: '',
deskripsi: '',
imageId: '',
imageUrl: '',
});
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(true);
@@ -53,6 +63,13 @@ function EditPenangananDarurat() {
imageId: data.imageId || '',
});
setOriginalData({
name: data.name || '',
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
imageUrl: data.image?.link || '',
});
if (data.image?.link) {
setPreviewImage(data.image.link);
}
@@ -80,8 +97,20 @@ function EditPenangananDarurat() {
setPreviewImage(URL.createObjectURL(selected));
};
const handleResetForm = () => {
setFormData({
name: originalData.name,
deskripsi: originalData.deskripsi,
imageId: originalData.imageId,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
toast.info("Form dikembalikan ke data awal");
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
let imageId = formData.imageId;
if (file) {
@@ -107,6 +136,8 @@ function EditPenangananDarurat() {
} catch (err) {
console.error("Error updating penanganan darurat:", err);
toast.error("Gagal memperbarui data penanganan darurat");
} finally {
setIsSubmitting(false);
}
};
@@ -156,7 +187,7 @@ function EditPenangananDarurat() {
onDrop={handleDrop}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -181,7 +212,7 @@ function EditPenangananDarurat() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos="relative">
<Image
src={previewImage}
alt="Preview"
@@ -194,11 +225,40 @@ function EditPenangananDarurat() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -209,7 +269,7 @@ function EditPenangananDarurat() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -2,10 +2,12 @@
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -30,6 +32,7 @@ function CreatePenangananDarurat() {
const router = useRouter();
const penangananDaruratState = useProxy(penangananDarurat);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [file, setFile] = useState<File | null>(null);
const resetForm = () => {
@@ -43,26 +46,34 @@ function CreatePenangananDarurat() {
};
const handleSubmit = async () => {
if (!file) {
return toast.warn('Pilih file gambar terlebih dahulu');
try {
setIsSubmitting(true);
if (!file) {
return toast.warn('Pilih file gambar terlebih dahulu');
}
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');
}
penangananDaruratState.create.form.imageId = uploaded.id;
await penangananDaruratState.create.create();
resetForm();
router.push('/admin/kesehatan/penanganan-darurat');
} catch (error) {
console.error(error);
toast.error('Gagal menambahkan penanganan darurat');
} finally {
setIsSubmitting(false);
}
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');
}
penangananDaruratState.create.form.imageId = uploaded.id;
await penangananDaruratState.create.create();
resetForm();
router.push('/admin/kesehatan/penanganan-darurat');
};
return (
@@ -96,7 +107,7 @@ function CreatePenangananDarurat() {
<TextInput
label={<Text fw="bold" fz="sm">Judul</Text>}
placeholder="Masukkan judul"
defaultValue={penangananDaruratState.create.form.name}
value={penangananDaruratState.create.form.name}
onChange={(val) => {
penangananDaruratState.create.form.name = val.target.value;
}}
@@ -128,7 +139,7 @@ function CreatePenangananDarurat() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group
justify="center"
@@ -170,7 +181,7 @@ function CreatePenangananDarurat() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -183,6 +194,24 @@ function CreatePenangananDarurat() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
@@ -190,6 +219,17 @@ function CreatePenangananDarurat() {
{/* Button Simpan */}
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -200,7 +240,7 @@ function CreatePenangananDarurat() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -6,10 +6,12 @@ import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/pos
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -30,6 +32,7 @@ function EditPosyandu() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
name: '',
nomor: '',
@@ -37,6 +40,14 @@ function EditPosyandu() {
imageId: '',
jadwalPelayanan: '',
});
const [originalData, setOriginalData] = useState({
name: "",
nomor: "",
deskripsi: "",
imageId: "",
jadwalPelayanan: "",
imageUrl: ""
});
// Load data posyandu
useEffect(() => {
@@ -54,6 +65,14 @@ function EditPosyandu() {
imageId: data.imageId || '',
jadwalPelayanan: data.jadwalPelayanan || '',
});
setOriginalData({
name: data.name || '',
nomor: data.nomor || '',
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
jadwalPelayanan: data.jadwalPelayanan || '',
imageUrl: data.image?.link || '',
});
if (data?.image?.link) setPreviewImage(data.image.link);
}
} catch (error) {
@@ -66,7 +85,7 @@ function EditPosyandu() {
const handleSubmit = async () => {
try {
// Update global state hanya saat submit
setIsSubmitting(true);
const updatedForm = { ...statePosyandu.edit.form, ...formData };
// Upload file jika ada
@@ -86,9 +105,24 @@ function EditPosyandu() {
} catch (error) {
console.error('Error updating posyandu:', error);
toast.error('Terjadi kesalahan saat memperbarui posyandu');
} finally {
setIsSubmitting(false);
}
};
const resetForm = () => {
setFormData({
name: originalData.name,
nomor: originalData.nomor,
deskripsi: originalData.deskripsi,
imageId: originalData.imageId,
jadwalPelayanan: originalData.jadwalPelayanan,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
toast.info("Form dikembalikan ke data awal");
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Tombol Back */}
@@ -126,7 +160,7 @@ function EditPosyandu() {
}}
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
radius="md"
p="xl"
>
@@ -145,25 +179,45 @@ function EditPosyandu() {
Seret gambar atau klik untuk memilih file
</Text>
<Text size="sm" c="dimmed">
Maksimal 5MB, format gambar wajib
Maksimal 5MB, format gambar .png, .jpg, .jpeg, webp
</Text>
</Stack>
</Group>
</Dropzone>
{previewImage && (
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
<Box mt="sm" pos={"relative"} style={{ display: 'flex', justifyContent: 'center' }}>
<Image
src={previewImage}
alt="Preview Gambar"
radius="md"
style={{
maxHeight: 220,
maxHeight: 200,
objectFit: 'contain',
border: `1px solid ${colors['blue-button']}`,
border: '1px solid #ddd',
}}
loading="lazy"
/>
{/* Tombol hapus (pojok kanan atas) */}
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
@@ -209,6 +263,17 @@ function EditPosyandu() {
{/* Tombol Submit */}
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -219,7 +284,7 @@ function EditPosyandu() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -2,10 +2,12 @@
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -27,6 +29,7 @@ function CreatePosyandu() {
const router = useRouter();
const [file, setFile] = useState<File | null>(null);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
@@ -43,35 +46,31 @@ function CreatePosyandu() {
const handleSubmit = async () => {
if (!file) {
return toast.warn('Silakan pilih file gambar terlebih dahulu');
try {
setIsSubmitting(true);
if (!file) {
return toast.warn('Silakan pilih file gambar terlebih dahulu');
}
// Upload gambar dulu
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');
}
statePosyandu.create.form.imageId = uploaded.id;
await statePosyandu.create.create();
resetForm();
router.push('/admin/kesehatan/posyandu');
} catch (error) {
console.error('Error creating posyandu:', error);
toast.error('Gagal menambahkan posyandu');
} finally {
setIsSubmitting(false);
}
// Upload gambar dulu
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');
}
statePosyandu.create.form.imageId = uploaded.id;
await statePosyandu.create.create();
resetForm();
router.push('/admin/kesehatan/posyandu');
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */}
@@ -109,7 +108,7 @@ function CreatePosyandu() {
}}
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
radius="md"
p="xl"
>
@@ -131,7 +130,7 @@ function CreatePosyandu() {
{previewImage && (
<Box mt="sm" style={{ textAlign: 'center' }}>
<Box pos={"relative"} mt="sm" style={{ textAlign: 'center' }}>
<Image
src={previewImage}
alt="Preview Gambar"
@@ -143,6 +142,24 @@ function CreatePosyandu() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
@@ -152,14 +169,14 @@ function CreatePosyandu() {
<TextInput
label="Nama Posyandu"
placeholder="Masukkan nama posyandu"
defaultValue={statePosyandu.create.form.name || ''}
value={statePosyandu.create.form.name || ''}
onChange={(e) => (statePosyandu.create.form.name = e.target.value)}
required
/>
<TextInput
label="Telepon Posyandu"
placeholder="Masukkan telepon posyandu"
defaultValue={statePosyandu.create.form.nomor || ''}
value={statePosyandu.create.form.nomor || ''}
onChange={(e) => (statePosyandu.create.form.nomor = e.target.value)}
required
/>
@@ -189,6 +206,17 @@ function CreatePosyandu() {
{/* Button */}
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -199,7 +227,7 @@ function CreatePosyandu() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -5,10 +5,12 @@ import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-k
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -29,12 +31,20 @@ function EditProgramKesehatan() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
name: '',
deskripsiSingkat: '',
deskripsi: '',
imageId: '',
});
const [originalData, setOriginalData] = useState({
name: '',
deskripsiSingkat: '',
deskripsi: '',
imageId: '',
imageUrl: ''
});
// Load data awal
useEffect(() => {
@@ -52,6 +62,13 @@ function EditProgramKesehatan() {
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
});
setOriginalData({
name: data.name || '',
deskripsiSingkat: data.deskripsiSingkat || '',
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
imageUrl: data.image?.link || '',
});
if (data?.image?.link) setPreviewImage(data.image.link);
} catch (err) {
@@ -68,9 +85,22 @@ function EditProgramKesehatan() {
setFormData((prev) => ({ ...prev, [key]: value }));
};
const handleResetForm = () => {
setFormData({
name: originalData.name,
deskripsiSingkat: originalData.deskripsiSingkat,
deskripsi: originalData.deskripsi,
imageId: originalData.imageId,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
toast.info("Form dikembalikan ke data awal");
};
// Submit form
const handleSubmit = async () => {
try {
setIsSubmitting(true);
const updatedForm = { ...programKesehatanState.edit.form, ...formData };
// Upload file kalau ada
@@ -89,6 +119,8 @@ function EditProgramKesehatan() {
} catch (err) {
console.error(err);
toast.error('Terjadi kesalahan saat memperbarui program kesehatan');
} finally {
setIsSubmitting(false);
}
};
@@ -158,7 +190,7 @@ function EditProgramKesehatan() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -183,7 +215,7 @@ function EditProgramKesehatan() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -196,11 +228,40 @@ function EditProgramKesehatan() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -211,7 +272,7 @@ function EditProgramKesehatan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -2,10 +2,12 @@
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -26,6 +28,7 @@ function CreateProgramKesehatan() {
const programKesehatanState = useProxy(programKesehatan);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
programKesehatanState.create.form = {
@@ -45,25 +48,33 @@ function CreateProgramKesehatan() {
if (!programKesehatanState.create.form.deskripsiSingkat) {
return toast.warn("Deskripsi singkat wajib diisi");
}
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
try {
setIsSubmitting(true);
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
}
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");
}
programKesehatanState.create.form.imageId = uploaded.id;
await programKesehatanState.create.create();
resetForm();
router.push("/admin/kesehatan/program-kesehatan");
} catch (error) {
console.error("Error creating program kesehatan:", error);
toast.error("Gagal menambahkan program kesehatan");
} finally {
setIsSubmitting(false);
}
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");
}
programKesehatanState.create.form.imageId = uploaded.id;
await programKesehatanState.create.create();
resetForm();
router.push("/admin/kesehatan/program-kesehatan");
};
return (
@@ -89,7 +100,7 @@ function CreateProgramKesehatan() {
>
<Stack gap="md">
<TextInput
defaultValue={programKesehatanState.create.form.name}
value={programKesehatanState.create.form.name}
onChange={(val) => {
programKesehatanState.create.form.name = val.target.value;
}}
@@ -136,7 +147,7 @@ function CreateProgramKesehatan() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -161,7 +172,7 @@ function CreateProgramKesehatan() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -174,11 +185,40 @@ function CreateProgramKesehatan() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -189,7 +229,7 @@ function CreateProgramKesehatan() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
@@ -5,10 +6,12 @@ import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/p
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -52,6 +55,7 @@ function EditPuskesmas() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<PuskesmasFormData>({
name: '',
alamat: '',
@@ -59,6 +63,14 @@ function EditPuskesmas() {
kontak: { kontakPuskesmas: '', email: '', facebook: '', kontakUGD: '' },
imageId: '',
});
const [originalData, setOriginalData] = useState({
name: '',
alamat: '',
jam: { workDays: '', weekDays: '', holiday: '' },
kontak: { kontakPuskesmas: '', email: '', facebook: '', kontakUGD: '' },
imageId: '',
imageUrl: ''
});
useEffect(() => {
const loadPuskesmas = async () => {
@@ -85,6 +97,24 @@ function EditPuskesmas() {
},
imageId: form.imageId,
});
setOriginalData({
name: form.name,
alamat: form.alamat,
jam: {
workDays: form.jam.workDays,
weekDays: form.jam.weekDays,
holiday: form.jam.holiday,
},
kontak: {
kontakPuskesmas: form.kontak.kontakPuskesmas,
email: form.kontak.email,
facebook: form.kontak.facebook,
kontakUGD: form.kontak.kontakUGD,
},
imageId: form.imageId,
imageUrl: (form as any).image?.link
});
const formWithImage = form as PuskesmasFormData;
if (formWithImage.image?.link) {
@@ -99,8 +129,31 @@ function EditPuskesmas() {
loadPuskesmas();
}, [params?.id]);
const handleResetForm = () => {
setFormData({
name: originalData.name,
alamat: originalData.alamat,
jam: {
workDays: originalData.jam.workDays,
weekDays: originalData.jam.weekDays,
holiday: originalData.jam.holiday,
},
kontak: {
kontakPuskesmas: originalData.kontak.kontakPuskesmas,
email: originalData.kontak.email,
facebook: originalData.kontak.facebook,
kontakUGD: originalData.kontak.kontakUGD,
},
imageId: originalData.imageId,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
toast.info("Form dikembalikan ke data awal");
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
statePuskesmas.edit.form = {
...statePuskesmas.edit.form,
name: formData.name,
@@ -130,6 +183,8 @@ function EditPuskesmas() {
} catch (error) {
console.error("Error updating puskesmas:", error);
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data puskesmas");
} finally {
setIsSubmitting(false);
}
};
@@ -252,7 +307,7 @@ function EditPuskesmas() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -276,7 +331,7 @@ function EditPuskesmas() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -289,11 +344,41 @@ function EditPuskesmas() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
<Group justify="right">
{/* Tombol Batal */}
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
@@ -303,9 +388,8 @@ function EditPuskesmas() {
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
loading={statePuskesmas.edit.loading}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -2,10 +2,12 @@
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import {
ActionIcon,
Box,
Button,
Group,
Image,
Loader,
Paper,
Stack,
Text,
@@ -25,6 +27,7 @@ function CreatePuskesmas() {
const router = useRouter();
const [file, setFile] = useState<File | null>(null);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
statePuskesmas.create.form = {
@@ -50,35 +53,43 @@ function CreatePuskesmas() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!file) {
return toast.warn('Pilih file gambar terlebih dahulu');
try {
setIsSubmitting(true);
if (!file) {
return toast.warn('Pilih file gambar terlebih dahulu');
}
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');
}
statePuskesmas.create.form.imageId = uploaded.id;
await statePuskesmas.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/puskesmas');
} catch (error) {
console.error('Error creating posyandu:', error);
toast.error('Terjadi kesalahan saat membuat posyandu');
} finally {
setIsSubmitting(false);
}
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');
}
statePuskesmas.create.form.imageId = uploaded.id;
await statePuskesmas.create.submit();
toast.success('Data berhasil disimpan');
resetForm();
router.push('/admin/kesehatan/puskesmas');
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
<Title order={4} ml="sm" c="dark">
Tambah Data Puskesmas
</Title>
@@ -97,40 +108,40 @@ function CreatePuskesmas() {
<TextInput
label="Nama Puskesmas"
placeholder="Masukkan nama puskesmas"
defaultValue={statePuskesmas.create.form.name}
value={statePuskesmas.create.form.name}
onChange={(e) => (statePuskesmas.create.form.name = e.target.value)}
required
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat"
defaultValue={statePuskesmas.create.form.alamat}
value={statePuskesmas.create.form.alamat}
onChange={(e) => (statePuskesmas.create.form.alamat = e.target.value)}
required
/>
<TextInput
label="Jam Buka"
placeholder="Masukkan jam buka"
defaultValue={statePuskesmas.create.form.jam.workDays}
value={statePuskesmas.create.form.jam.workDays}
onChange={(e) => (statePuskesmas.create.form.jam.workDays = e.target.value)}
/>
<TextInput
label="Jam Tutup"
placeholder="Masukkan jam tutup"
defaultValue={statePuskesmas.create.form.jam.weekDays}
value={statePuskesmas.create.form.jam.weekDays}
onChange={(e) => (statePuskesmas.create.form.jam.weekDays = e.target.value)}
/>
<TextInput
label="Holiday"
placeholder="Masukkan hari libur"
defaultValue={statePuskesmas.create.form.jam.holiday}
value={statePuskesmas.create.form.jam.holiday}
onChange={(e) => (statePuskesmas.create.form.jam.holiday = e.target.value)}
/>
<TextInput
label="Kontak Puskesmas"
placeholder="Masukkan kontak puskesmas"
defaultValue={statePuskesmas.create.form.kontak.kontakPuskesmas}
value={statePuskesmas.create.form.kontak.kontakPuskesmas}
onChange={(e) =>
(statePuskesmas.create.form.kontak.kontakPuskesmas = e.target.value)
}
@@ -138,19 +149,19 @@ function CreatePuskesmas() {
<TextInput
label="Email"
placeholder="Masukkan email"
defaultValue={statePuskesmas.create.form.kontak.email}
value={statePuskesmas.create.form.kontak.email}
onChange={(e) => (statePuskesmas.create.form.kontak.email = e.target.value)}
/>
<TextInput
label="Facebook"
placeholder="Masukkan facebook"
defaultValue={statePuskesmas.create.form.kontak.facebook}
value={statePuskesmas.create.form.kontak.facebook}
onChange={(e) => (statePuskesmas.create.form.kontak.facebook = e.target.value)}
/>
<TextInput
label="Kontak UGD"
placeholder="Masukkan kontak UGD"
defaultValue={statePuskesmas.create.form.kontak.kontakUGD}
value={statePuskesmas.create.form.kontak.kontakUGD}
onChange={(e) => (statePuskesmas.create.form.kontak.kontakUGD = e.target.value)}
/>
@@ -168,7 +179,7 @@ function CreatePuskesmas() {
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] }}
>
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
@@ -193,7 +204,7 @@ function CreatePuskesmas() {
</Dropzone>
{previewImage && (
<Box mt="sm">
<Box mt="sm" pos={"relative"}>
<Image
src={previewImage}
alt="Preview"
@@ -206,14 +217,44 @@ function CreatePuskesmas() {
}}
loading="lazy"
/>
<ActionIcon
variant="filled"
color="red"
radius="xl"
size="sm"
pos="absolute"
top={5}
right={5}
onClick={() => {
setPreviewImage(null);
setFile(null);
}}
style={{
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
}}
>
<IconX size={14} />
</ActionIcon>
</Box>
)}
</Box>
{/* Action Button */}
<Group justify="right">
{/* Tombol Batal */}
<Button
type="submit"
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
@@ -222,7 +263,7 @@ function CreatePuskesmas() {
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>