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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
/* eslint-disable react-hooks/exhaustive-deps */
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function EditGrafikBerdasarkanPendidikan() {
@@ -14,34 +14,59 @@ function EditGrafikBerdasarkanPendidikan() {
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
const id = params.id;
// state lokal untuk form
const [formData, setFormData] = useState({
SD: '',
SMP: '',
SMA: '',
D3: '',
S1: '',
});
useEffect(() => {
if (id) {
stategrafik.findUnique.load(id).then(() => {
const data = stategrafik.findUnique.data;
if (data) {
stategrafik.update.form = {
setFormData({
SD: data.SD || '',
SMP: data.SMP || '',
SMA: data.SMA || '',
D3: data.D3 || '',
S1: data.S1 || '',
};
});
}
});
}
}, [id]);
const handleChange = (field: keyof typeof formData) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({
...prev,
[field]: e.currentTarget.value,
}));
};
const handleSubmit = async () => {
stategrafik.update.id = id;
stategrafik.update.form = { ...formData }; // update global state pas submit aja
await stategrafik.update.submit();
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan');
router.push(
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan'
);
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<Button
variant="subtle"
onClick={() => router.back()}
p="xs"
radius="md"
>
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
@@ -63,36 +88,36 @@ function EditGrafikBerdasarkanPendidikan() {
label="SD"
type="number"
placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.SD}
onChange={(val) => (stategrafik.update.form.SD = val.currentTarget.value)}
value={formData.SD}
onChange={handleChange('SD')}
/>
<TextInput
label="SMP"
type="number"
placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.SMP}
onChange={(val) => (stategrafik.update.form.SMP = val.currentTarget.value)}
value={formData.SMP}
onChange={handleChange('SMP')}
/>
<TextInput
label="SMA"
type="number"
placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.SMA}
onChange={(val) => (stategrafik.update.form.SMA = val.currentTarget.value)}
value={formData.SMA}
onChange={handleChange('SMA')}
/>
<TextInput
label="D3"
type="number"
placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.D3}
onChange={(val) => (stategrafik.update.form.D3 = val.currentTarget.value)}
value={formData.D3}
onChange={handleChange('D3')}
/>
<TextInput
label="S1"
type="number"
placeholder="Masukkan jumlah"
defaultValue={stategrafik.update.form.S1}
onChange={(val) => (stategrafik.update.form.S1 = val.currentTarget.value)}
value={formData.S1}
onChange={handleChange('S1')}
/>
<Group justify="right">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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