Fix QC Kak Inno Admin, Fix QC Keano UI User, Fix QC Pak jun tabel apbdes
This commit is contained in:
@@ -1,152 +1,244 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
'use client';
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Alert, Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { IconAlertCircle, IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
// 🧩 Type untuk form
|
||||
interface FormData {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
}
|
||||
|
||||
// 🧩 Main Component
|
||||
function Page() {
|
||||
const lambangState = useProxy(stateProfileDesa.lambangDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
return;
|
||||
}
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
try {
|
||||
const data = await lambangState.findUnique.load(id);
|
||||
lambangState.update.initialize(data);
|
||||
} catch (error) {
|
||||
console.error("Error loading lambang:", error);
|
||||
toast.error("Gagal memuat data lambang desa");
|
||||
}
|
||||
};
|
||||
const [formData, setFormData] = useState<FormData>({ judul: '', deskripsi: '' });
|
||||
const [originalData, setOriginalData] = useState<FormData>({ judul: '', deskripsi: '' });
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
loadData();
|
||||
// 🧭 Load data awal
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error('ID tidak valid');
|
||||
router.push('/admin/desa/profile/profile-desa');
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
lambangState.update.reset();
|
||||
lambangState.findUnique.reset();
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
setIsLoading(true);
|
||||
setLoadError(null);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !lambangState.update.form.judul.trim()) {
|
||||
toast.error("Judul wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const success = await lambangState.update.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update lambang desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update lambang desa");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
try {
|
||||
const data = await stateProfileDesa.lambangDesa.findUnique.load(id);
|
||||
|
||||
if (data) {
|
||||
const initial: FormData = {
|
||||
judul: data.judul || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
};
|
||||
setFormData(initial);
|
||||
setOriginalData(initial);
|
||||
|
||||
// Penting untuk isi id di state sebelum submit
|
||||
stateProfileDesa.lambangDesa.update.initialize(data);
|
||||
} else {
|
||||
setLoadError('Data tidak ditemukan');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading lambang:', error);
|
||||
setLoadError('Gagal memuat data lambang desa');
|
||||
toast.error('Gagal memuat data lambang desa');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => router.back();
|
||||
loadData();
|
||||
|
||||
// Loading state
|
||||
if (lambangState.findUnique.loading || lambangState.update.loading) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
return () => {
|
||||
stateProfileDesa.lambangDesa.update.reset();
|
||||
stateProfileDesa.lambangDesa.findUnique.reset();
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
// 🔁 Reset form
|
||||
const handleResetForm = () => {
|
||||
setFormData(originalData);
|
||||
toast.info('Form dikembalikan ke data awal');
|
||||
};
|
||||
|
||||
// 💾 Submit handler
|
||||
const handleSubmit = async () => {
|
||||
if (!formData.judul.trim()) {
|
||||
toast.error('Judul wajib diisi');
|
||||
return;
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (lambangState.findUnique.error) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="red">
|
||||
<Text fw="bold">Error</Text>
|
||||
<Text>{lambangState.findUnique.error}</Text>
|
||||
</Alert>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const state = stateProfileDesa.lambangDesa;
|
||||
state.update.form.judul = formData.judul;
|
||||
state.update.form.deskripsi = formData.deskripsi;
|
||||
|
||||
const success = await state.update.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success('Data berhasil disimpan');
|
||||
router.push('/admin/desa/profile/profile-desa');
|
||||
} else {
|
||||
toast.error('Gagal menyimpan data');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error update lambang desa:', error);
|
||||
toast.error('Terjadi kesalahan saat update lambang desa');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 📝 Handlers
|
||||
const handleJudulChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData(prev => ({ ...prev, judul: e.target.value }));
|
||||
};
|
||||
|
||||
const handleDeskripsiChange = (html: string) => {
|
||||
setFormData(prev => ({ ...prev, deskripsi: html }));
|
||||
};
|
||||
|
||||
const handleBack = () => router.back();
|
||||
|
||||
// 🔄 Loading
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">Edit Lambang Desa</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p="md" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Edit Lambang Desa</Title>
|
||||
|
||||
{/* Judul */}
|
||||
<TextInput
|
||||
label={<Text fw="bold">Judul</Text>}
|
||||
placeholder="Judul lambang"
|
||||
defaultValue={lambangState.update.form.judul}
|
||||
onChange={(e) => lambangState.update.form.judul = e.currentTarget.value}
|
||||
error={!lambangState.update.form.judul && "Judul wajib diisi"}
|
||||
/>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||
<EditEditor
|
||||
value={lambangState.update.form.deskripsi}
|
||||
onChange={(val) => lambangState.update.form.deskripsi = val}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Buttons */}
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
loading={isSubmitting || lambangState.update.loading}
|
||||
disabled={!lambangState.update.form.judul}
|
||||
>
|
||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={handleBack} disabled={isSubmitting || lambangState.update.loading}>
|
||||
Batal
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Stack align="center" gap="md">
|
||||
<Loader size="lg" color={colors['blue-button']} />
|
||||
<Text size="lg" fw={500} c="dimmed">
|
||||
Memuat data lambang desa...
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ❌ Error
|
||||
if (loadError) {
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Alert icon={<IconAlertCircle size={20} />} color="red" title="Terjadi Kesalahan" radius="md">
|
||||
{loadError}
|
||||
</Alert>
|
||||
<Button onClick={() => router.push('/admin/desa/profile/profile-desa')} variant="outline">
|
||||
Kembali ke Halaman Utama
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// 🧱 UI utama
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Stack gap="md">
|
||||
{/* Header */}
|
||||
<Group mb="sm">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Lambang Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="xl"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Judul */}
|
||||
<TextInput
|
||||
label={<Text fw="bold" size="sm">Judul</Text>}
|
||||
placeholder="Masukkan judul lambang desa"
|
||||
value={formData.judul}
|
||||
onChange={handleJudulChange}
|
||||
error={!formData.judul.trim() && 'Judul wajib diisi'}
|
||||
required
|
||||
size="md"
|
||||
radius="md"
|
||||
/>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fw="bold" size="sm" mb={8}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EditEditor value={formData.deskripsi} onChange={handleDeskripsiChange} />
|
||||
</Box>
|
||||
|
||||
{/* Tombol Aksi */}
|
||||
<Group justify="right">
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -5,7 +5,7 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Alert, Box, Button, Center, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Alert, Box, Button, Center, Group, Image, Loader, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconAlertCircle, IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
@@ -19,8 +19,8 @@ function Page() {
|
||||
const params = useParams();
|
||||
|
||||
const [images, setImages] = useState<
|
||||
Array<{ file: File | null; preview: string; label: string; imageId?: string }>
|
||||
>([]);
|
||||
Array<{ file: File | null; preview: string; label: string; imageId?: string }>
|
||||
>([]);
|
||||
const [formData, setFormData] = useState({
|
||||
judul: '',
|
||||
deskripsi: '',
|
||||
@@ -28,6 +28,12 @@ function Page() {
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [originalData, setOriginalData] = useState({
|
||||
judul: "",
|
||||
deskripsi: "",
|
||||
images: [] as Array<{ label: string; imageId: string }>
|
||||
});
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
@@ -52,6 +58,17 @@ function Page() {
|
||||
})),
|
||||
});
|
||||
|
||||
setOriginalData({
|
||||
judul: data.judul || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
images: (data.images || []).map((img: any) => ({
|
||||
label: img.label,
|
||||
imageId: img.image?.id ?? '',
|
||||
preview: img.image?.link ?? '',
|
||||
})),
|
||||
});
|
||||
|
||||
|
||||
if (data?.images?.length > 0 && data.images[0].image?.link) {
|
||||
setImages(data.images.map((img: any) => ({
|
||||
file: null,
|
||||
@@ -77,15 +94,36 @@ function Page() {
|
||||
|
||||
const handleBack = () => router.back();
|
||||
|
||||
const handleResetForm = () => {
|
||||
setFormData({
|
||||
judul: originalData.judul,
|
||||
deskripsi: originalData.deskripsi,
|
||||
images: originalData.images.map((img) => ({
|
||||
label: img.label,
|
||||
imageId: img.imageId,
|
||||
})),
|
||||
});
|
||||
|
||||
setImages(
|
||||
originalData.images.map((img: any) => ({
|
||||
file: null,
|
||||
preview: img.preview, // pakai preview masing-masing, bukan cuma satu
|
||||
label: img.label,
|
||||
imageId: img.imageId,
|
||||
}))
|
||||
);
|
||||
|
||||
toast.info("Form dikembalikan ke data awal");
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !formData.judul.trim()) {
|
||||
toast.error("Judul wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const uploadedImages = [];
|
||||
|
||||
// Upload semua gambar baru
|
||||
@@ -95,7 +133,7 @@ function Page() {
|
||||
uploadedImages.push({ imageId: img.imageId, label: img.label });
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// upload baru
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file: img.file,
|
||||
@@ -108,7 +146,7 @@ function Page() {
|
||||
}
|
||||
uploadedImages.push({ imageId: uploaded.id, label: img.label || "main" });
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Update ke global state
|
||||
maskotState.update.updateField("judul", formData.judul);
|
||||
@@ -161,9 +199,9 @@ function Page() {
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">Edit Maskot Desa</Title>
|
||||
</Group>
|
||||
|
||||
@@ -175,7 +213,7 @@ function Page() {
|
||||
<TextInput
|
||||
label={<Text fw="bold">Judul</Text>}
|
||||
placeholder="Masukkan judul maskot"
|
||||
defaultValue={formData.judul}
|
||||
value={formData.judul}
|
||||
onChange={(e) => setFormData({ ...formData, judul: e.currentTarget.value })}
|
||||
error={!formData.judul && "Judul wajib diisi"}
|
||||
/>
|
||||
@@ -231,7 +269,7 @@ function Page() {
|
||||
setImages(updated);
|
||||
}}
|
||||
>
|
||||
Hapus
|
||||
<IconX size={16} />
|
||||
</Button>
|
||||
</Group>
|
||||
<Image
|
||||
@@ -260,18 +298,31 @@ function Page() {
|
||||
</SimpleGrid>
|
||||
|
||||
{/* Buttons */}
|
||||
<Group>
|
||||
<Group justify="right">
|
||||
{/* Tombol Batal */}
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
loading={isSubmitting || maskotState.update.loading}
|
||||
disabled={!formData.judul}
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
>
|
||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleBack} disabled={isSubmitting || maskotState.update.loading}>
|
||||
Batal
|
||||
</Button>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -1,146 +1,272 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
'use client';
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Alert, Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { IconAlertCircle, IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
// 🔹 Types
|
||||
interface FormData {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
}
|
||||
|
||||
// 🔹 Main Component
|
||||
function Page() {
|
||||
const sejarahState = useProxy(stateProfileDesa.sejarahDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
// Load data
|
||||
// 🧩 Local State
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
judul: '',
|
||||
deskripsi: '',
|
||||
});
|
||||
const [originalData, setOriginalData] = useState<FormData>({
|
||||
judul: '',
|
||||
deskripsi: '',
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
// 🧭 Load Initial Data
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
toast.error('ID tidak valid');
|
||||
router.push('/admin/desa/profile/profile-desa');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setLoadError(null);
|
||||
|
||||
try {
|
||||
const data = await sejarahState.findUnique.load(id);
|
||||
const data = await stateProfileDesa.sejarahDesa.findUnique.load(id);
|
||||
|
||||
if (data) {
|
||||
sejarahState.update.initialize(data);
|
||||
const initialData: FormData = {
|
||||
judul: data.judul || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
};
|
||||
|
||||
setFormData(initialData);
|
||||
setOriginalData(initialData);
|
||||
|
||||
stateProfileDesa.sejarahDesa.update.initialize(data);
|
||||
} else {
|
||||
setLoadError('Data tidak ditemukan');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading sejarah:", error);
|
||||
toast.error("Gagal memuat data sejarah desa");
|
||||
console.error('Error loading sejarah:', error);
|
||||
setLoadError('Gagal memuat data sejarah desa');
|
||||
toast.error('Gagal memuat data sejarah desa');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
sejarahState.update.reset();
|
||||
sejarahState.findUnique.reset();
|
||||
stateProfileDesa.sejarahDesa.update.reset();
|
||||
stateProfileDesa.sejarahDesa.findUnique.reset();
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
// 🔄 Check if form has changes
|
||||
|
||||
|
||||
// 🔁 Reset Form to Original Data
|
||||
const handleResetForm = () => {
|
||||
setFormData(originalData);
|
||||
toast.info('Form dikembalikan ke data awal');
|
||||
};
|
||||
|
||||
// 💾 Submit Handler
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !sejarahState.update.form.judul.trim()) {
|
||||
toast.error("Judul wajib diisi");
|
||||
// Validation
|
||||
if (!formData.judul.trim()) {
|
||||
toast.error('Judul wajib diisi');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const success = await sejarahState.update.submit();
|
||||
// Access original state directly (not proxy)
|
||||
const originalState = stateProfileDesa.sejarahDesa;
|
||||
|
||||
// Update form data
|
||||
originalState.update.form.judul = formData.judul;
|
||||
originalState.update.form.deskripsi = formData.deskripsi;
|
||||
|
||||
// Submit
|
||||
const success = await originalState.update.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
toast.success('Data berhasil disimpan');
|
||||
router.push('/admin/desa/profile/profile-desa');
|
||||
} else {
|
||||
toast.error('Gagal menyimpan data');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update sejarah desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update sejarah desa");
|
||||
console.error('Error update sejarah desa:', error);
|
||||
toast.error('Terjadi kesalahan saat update sejarah desa');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => router.back();
|
||||
// 📝 Form Field Handlers
|
||||
const handleJudulChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData(prev => ({ ...prev, judul: e.target.value }));
|
||||
};
|
||||
|
||||
// Loading state
|
||||
if (sejarahState.findUnique.loading || sejarahState.update.loading) {
|
||||
const handleDeskripsiChange = (html: string) => {
|
||||
setFormData(prev => ({ ...prev, deskripsi: html }));
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
router.back();
|
||||
};
|
||||
// 🔄 Loading State
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
<Stack align="center" gap="md">
|
||||
<Loader size="lg" color={colors['blue-button']} />
|
||||
<Text size="lg" fw={500} c="dimmed">
|
||||
Memuat data...
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (sejarahState.findUnique.error) {
|
||||
// ❌ Error State
|
||||
if (loadError) {
|
||||
return (
|
||||
<Box>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="red">
|
||||
<Text fw="bold">Error</Text>
|
||||
<Text>{sejarahState.findUnique.error}</Text>
|
||||
<Alert
|
||||
icon={<IconAlertCircle size={20} />}
|
||||
color="red"
|
||||
title="Terjadi Kesalahan"
|
||||
radius="md"
|
||||
>
|
||||
{loadError}
|
||||
</Alert>
|
||||
<Button
|
||||
onClick={() => router.push('/admin/desa/profile/profile-desa')}
|
||||
variant="outline"
|
||||
>
|
||||
Kembali ke Halaman Utama
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Group mb="md">
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Stack gap="md">
|
||||
{/* Header */}
|
||||
<Group mb="sm">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">Edit Sejarah Desa</Title>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Sejarah Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p="md" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Edit Sejarah Desa</Title>
|
||||
|
||||
{/* Judul */}
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="xl"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Form Title */}
|
||||
<Box>
|
||||
<Title order={3} mb={4}>
|
||||
Informasi Sejarah Desa
|
||||
</Title>
|
||||
</Box>
|
||||
{/* Judul Field */}
|
||||
<TextInput
|
||||
label={<Text fw="bold">Judul</Text>}
|
||||
placeholder="Judul sejarah"
|
||||
defaultValue={sejarahState.update.form.judul}
|
||||
onChange={(e) => sejarahState.update.form.judul = e.currentTarget.value}
|
||||
error={!sejarahState.update.form.judul && "Judul wajib diisi"}
|
||||
label={<Text fw="bold" size="sm">Judul Sejarah</Text>}
|
||||
placeholder="Masukkan judul sejarah desa"
|
||||
value={formData.judul}
|
||||
onChange={handleJudulChange}
|
||||
error={!formData.judul.trim() && 'Judul wajib diisi'}
|
||||
required
|
||||
size="md"
|
||||
radius="md"
|
||||
/>
|
||||
|
||||
{/* Deskripsi */}
|
||||
{/* Deskripsi Field */}
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fw="bold" size="sm" mb={8}>
|
||||
Deskripsi Sejarah
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={sejarahState.update.form.deskripsi}
|
||||
onChange={(val) => sejarahState.update.form.deskripsi = val}
|
||||
value={formData.deskripsi}
|
||||
onChange={handleDeskripsiChange}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Buttons */}
|
||||
<Group>
|
||||
{/* Action Buttons */}
|
||||
<Group justify="right">
|
||||
{/* Tombol Batal */}
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
loading={isSubmitting || sejarahState.update.loading}
|
||||
disabled={!sejarahState.update.form.judul}
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
>
|
||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||
Batal
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={handleBack} disabled={isSubmitting || sejarahState.update.loading}>
|
||||
Batal
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -150,4 +276,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,155 +1,247 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Alert, Box, Button, Center, Group, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { IconAlertCircle, IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
// 🔹 Types
|
||||
interface FormData {
|
||||
visi: string;
|
||||
misi: string;
|
||||
}
|
||||
|
||||
// 🔹 Main Component
|
||||
function Page() {
|
||||
const visiMisiState = useProxy(stateProfileDesa.visiMisiDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [formData, setFormData] = useState<FormData>({ visi: '', misi: '' });
|
||||
const [originalData, setOriginalData] = useState<FormData>({ visi: '', misi: '' });
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
return;
|
||||
}
|
||||
// 🧭 Load Data
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error('ID tidak valid');
|
||||
router.push('/admin/desa/profile/profile-desa');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await visiMisiState.findUnique.load(id);
|
||||
visiMisiState.update.initialize(data);
|
||||
} catch (error) {
|
||||
console.error("Error loading visi misi:", error);
|
||||
toast.error("Gagal memuat data visi misi desa");
|
||||
}
|
||||
};
|
||||
setIsLoading(true);
|
||||
setLoadError(null);
|
||||
|
||||
loadData();
|
||||
try {
|
||||
const data = await stateProfileDesa.visiMisiDesa.findUnique.load(id);
|
||||
if (data) {
|
||||
const initialData: FormData = {
|
||||
visi: data.visi || '',
|
||||
misi: data.misi || '',
|
||||
};
|
||||
setFormData(initialData);
|
||||
setOriginalData(initialData);
|
||||
|
||||
return () => {
|
||||
visiMisiState.update.reset();
|
||||
visiMisiState.findUnique.reset();
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !visiMisiState.update.form.visi.trim()) {
|
||||
toast.error("Visi wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const success = await visiMisiState.update.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update visi misi desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update visi misi desa");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
// set id ke state agar submit pakai endpoint benar
|
||||
stateProfileDesa.visiMisiDesa.update.initialize(data);
|
||||
} else {
|
||||
setLoadError('Data tidak ditemukan');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error load visi misi:', error);
|
||||
setLoadError('Gagal memuat data visi misi desa');
|
||||
toast.error('Gagal memuat data visi misi desa');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => router.back();
|
||||
loadData();
|
||||
|
||||
// Loading state
|
||||
if (visiMisiState.findUnique.loading || visiMisiState.update.loading) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
return () => {
|
||||
stateProfileDesa.visiMisiDesa.update.reset();
|
||||
stateProfileDesa.visiMisiDesa.findUnique.reset();
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
// 🔄 Reset Form
|
||||
const handleResetForm = () => {
|
||||
setFormData(originalData);
|
||||
toast.info('Form dikembalikan ke data awal');
|
||||
};
|
||||
|
||||
// 💾 Submit
|
||||
const handleSubmit = async () => {
|
||||
if (!formData.visi.trim()) {
|
||||
toast.error('Visi wajib diisi');
|
||||
return;
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (visiMisiState.findUnique.error) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="red">
|
||||
<Text fw="bold">Error</Text>
|
||||
<Text>{visiMisiState.findUnique.error}</Text>
|
||||
</Alert>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const originalState = stateProfileDesa.visiMisiDesa;
|
||||
|
||||
// update data form ke state sebelum submit
|
||||
originalState.update.form.visi = formData.visi;
|
||||
originalState.update.form.misi = formData.misi;
|
||||
|
||||
const success = await originalState.update.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success('Data berhasil disimpan');
|
||||
router.push('/admin/desa/profile/profile-desa');
|
||||
} else {
|
||||
toast.error('Gagal menyimpan data');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error update visi misi desa:', error);
|
||||
toast.error('Terjadi kesalahan saat update visi misi desa');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 🧭 Field handlers
|
||||
const handleVisiChange = (html: string) => setFormData(prev => ({ ...prev, visi: html }));
|
||||
const handleMisiChange = (html: string) => setFormData(prev => ({ ...prev, misi: html }));
|
||||
const handleBack = () => router.back();
|
||||
|
||||
// ⏳ Loading
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">Edit Visi Misi Desa</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p="md" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Edit Visi Misi Desa</Title>
|
||||
|
||||
{/* Visi */}
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Visi</Text>
|
||||
<EditEditor
|
||||
value={visiMisiState.update.form.visi}
|
||||
onChange={(val) => visiMisiState.update.form.visi = val}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Misi */}
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Misi</Text>
|
||||
<EditEditor
|
||||
value={visiMisiState.update.form.misi}
|
||||
onChange={(val) => visiMisiState.update.form.misi = val}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Buttons */}
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
loading={isSubmitting || visiMisiState.update.loading}
|
||||
disabled={!visiMisiState.update.form.visi}
|
||||
>
|
||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={handleBack} disabled={isSubmitting || visiMisiState.update.loading}>
|
||||
Batal
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Stack align="center" gap="md">
|
||||
<Loader size="lg" color={colors['blue-button']} />
|
||||
<Text size="lg" fw={500} c="dimmed">
|
||||
Memuat data...
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ❌ Error
|
||||
if (loadError) {
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Alert
|
||||
icon={<IconAlertCircle size={20} />}
|
||||
color="red"
|
||||
title="Terjadi Kesalahan"
|
||||
radius="md"
|
||||
>
|
||||
{loadError}
|
||||
</Alert>
|
||||
<Button
|
||||
onClick={() => router.push('/admin/desa/profile/profile-desa')}
|
||||
variant="outline"
|
||||
>
|
||||
Kembali ke Halaman Utama
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ UI
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Stack gap="md">
|
||||
{/* Header */}
|
||||
<Group mb="sm">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Visi & Misi Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="xl"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Title order={3} mb={4}>
|
||||
Informasi Visi & Misi Desa
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
{/* Visi */}
|
||||
<Box>
|
||||
<Text fw="bold" size="sm" mb={8}>
|
||||
Visi
|
||||
</Text>
|
||||
<EditEditor value={formData.visi} onChange={handleVisiChange} />
|
||||
</Box>
|
||||
|
||||
{/* Misi */}
|
||||
<Box>
|
||||
<Text fw="bold" size="sm" mb={8}>
|
||||
Misi
|
||||
</Text>
|
||||
<EditEditor value={formData.misi} onChange={handleMisiChange} />
|
||||
</Box>
|
||||
|
||||
{/* Actions */}
|
||||
<Group justify="right">
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
Reference in New Issue
Block a user