Merge pull request #21 from bipprojectbali/tasks/kependudukan/fix-typescript-types-and-cleanup-20260413-1500

refactor(kependudukan): improve TypeScript types and clean up code
This commit is contained in:
2026-04-13 15:01:39 +08:00
committed by GitHub
28 changed files with 213 additions and 134 deletions

View File

@@ -11,6 +11,11 @@ const compat = new FlatCompat({
const eslintConfig = [ const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"), ...compat.extends("next/core-web-vitals", "next/typescript"),
{
rules: {
"@typescript-eslint/no-explicit-any": "warn",
},
},
]; ];
export default eslintConfig; export default eslintConfig;

View File

@@ -1,8 +1,28 @@
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { loadJsonData } from "../../load-json"; import { loadJsonData } from "../../load-json";
const posisiOrganisasiBumDes = loadJsonData("ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json"); interface PosisiOrganisasi {
const pegawai = loadJsonData("ekonomi/struktur-organisasi/pegawai-bumdes.json"); id: string;
nama: string;
deskripsi: string;
hierarki: number;
parentId: string | null;
}
interface PegawaiBumDes {
id: string;
namaLengkap: string;
gelarAkademik: string;
tanggalMasuk: string;
email: string;
telepon: string;
alamat: string;
posisiId: string;
isActive: boolean;
}
const posisiOrganisasiBumDes = loadJsonData<PosisiOrganisasi[][]>("ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json");
const pegawai = loadJsonData<PegawaiBumDes[]>("ekonomi/struktur-organisasi/pegawai-bumdes.json");
export async function seedStrukturBumdes() { export async function seedStrukturBumdes() {
const flattenedPosisi = posisiOrganisasiBumDes.flat(); const flattenedPosisi = posisiOrganisasiBumDes.flat();

View File

@@ -1,8 +1,29 @@
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { loadJsonData } from "../../../load-json"; import { loadJsonData } from "../../../load-json";
const pegawaiPpid = loadJsonData("ppid/struktur-ppid/pegawai-PPID.json"); interface PegawaiPPID {
const posisiOrganisasiPPID = loadJsonData("ppid/struktur-ppid/posisi-organisasi-PPID.json"); id: string;
namaLengkap: string;
gelarAkademik: string;
tanggalMasuk: string;
email: string;
telepon: string;
alamat: string;
imageName?: string;
posisiId: string;
isActive: boolean;
}
interface PosisiOrganisasiPPID {
id: string;
nama: string;
deskripsi: string;
hierarki: number;
parentId: string | null;
}
const pegawaiPpid = loadJsonData<PegawaiPPID[]>("ppid/struktur-ppid/pegawai-PPID.json");
const posisiOrganisasiPPID = loadJsonData<PosisiOrganisasiPPID[][]>("ppid/struktur-ppid/posisi-organisasi-PPID.json");
export async function seedPegawaiPpid() { export async function seedPegawaiPpid() {

View File

@@ -1,16 +1,35 @@
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { proxy } from "valtio"; import { proxy } from "valtio";
interface DashboardSummary {
tahun: number;
summary: {
totalPenduduk: number;
totalKK: number;
totalKelahiran: number;
totalKemiskinan: number;
};
dinamika: {
kelahiran: number;
kematian: number;
pindahMasuk: number;
pindahKeluar: number;
};
agama: unknown[];
umur: unknown[];
banjar: unknown[];
}
const kependudukanDashboard = proxy({ const kependudukanDashboard = proxy({
summary: { summary: {
data: null as any, data: null as DashboardSummary | null,
loading: false, loading: false,
async load() { async load() {
kependudukanDashboard.summary.loading = true; kependudukanDashboard.summary.loading = true;
try { try {
const res = await ApiFetch.api.kependudukan.dashboard.summary.get(); const res = await ApiFetch.api.kependudukan.dashboard.summary.get();
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
kependudukanDashboard.summary.data = res.data.data; kependudukanDashboard.summary.data = res.data.data as unknown as DashboardSummary;
} else { } else {
kependudukanDashboard.summary.data = null; kependudukanDashboard.summary.data = null;
} }

View File

@@ -53,7 +53,7 @@ const dataBanjar = proxy({
}, },
findMany: { findMany: {
data: null as any[] | null, data: null as unknown[] | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
loading: false, loading: false,
@@ -66,7 +66,7 @@ const dataBanjar = proxy({
dataBanjar.findMany.tahun = tahun; dataBanjar.findMany.tahun = tahun;
try { try {
const query: any = { page, limit }; const query: Record<string, string | number> = { page, limit };
if (search) query.search = search; if (search) query.search = search;
if (tahun) query.tahun = tahun; if (tahun) query.tahun = tahun;
@@ -90,7 +90,7 @@ const dataBanjar = proxy({
}, },
findUnique: { findUnique: {
data: null as any | null, data: null as unknown | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch(`/api/kependudukan/databanjar/${id}`); const res = await fetch(`/api/kependudukan/databanjar/${id}`);

View File

@@ -49,7 +49,7 @@ const distribusiAgama = proxy({
}, },
findMany: { findMany: {
data: null as any[] | null, data: null as unknown[] | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
loading: false, loading: false,
@@ -60,7 +60,7 @@ const distribusiAgama = proxy({
distribusiAgama.findMany.search = search; distribusiAgama.findMany.search = search;
try { try {
const query: any = { page, limit }; const query: Record<string, string | number> = { page, limit };
if (tahun) query.tahun = tahun; if (tahun) query.tahun = tahun;
if (search) query.search = search; if (search) query.search = search;
@@ -84,7 +84,7 @@ const distribusiAgama = proxy({
}, },
findUnique: { findUnique: {
data: null as any | null, data: null as unknown | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch(`/api/kependudukan/distribusiagama/${id}`); const res = await fetch(`/api/kependudukan/distribusiagama/${id}`);

View File

@@ -49,7 +49,7 @@ const distribusiUmur = proxy({
}, },
findMany: { findMany: {
data: null as any[] | null, data: null as unknown[] | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
loading: false, loading: false,
@@ -60,7 +60,7 @@ const distribusiUmur = proxy({
distribusiUmur.findMany.search = search; distribusiUmur.findMany.search = search;
try { try {
const query: any = { page, limit }; const query: Record<string, string | number> = { page, limit };
if (tahun) query.tahun = tahun; if (tahun) query.tahun = tahun;
if (search) query.search = search; if (search) query.search = search;
@@ -84,7 +84,7 @@ const distribusiUmur = proxy({
}, },
findUnique: { findUnique: {
data: null as any | null, data: null as unknown | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch(`/api/kependudukan/distribusiumur/${id}`); const res = await fetch(`/api/kependudukan/distribusiumur/${id}`);

View File

@@ -55,7 +55,7 @@ const migrasiPenduduk = proxy({
}, },
findMany: { findMany: {
data: null as any[] | null, data: null as unknown[] | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
loading: false, loading: false,
@@ -66,7 +66,7 @@ const migrasiPenduduk = proxy({
migrasiPenduduk.findMany.search = search; migrasiPenduduk.findMany.search = search;
try { try {
const query: any = { page, limit }; const query: Record<string, string | number> = { page, limit };
if (tahun) query.tahun = tahun; if (tahun) query.tahun = tahun;
if (search) query.search = search; if (search) query.search = search;
@@ -90,7 +90,7 @@ const migrasiPenduduk = proxy({
}, },
findUnique: { findUnique: {
data: null as any | null, data: null as unknown | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch(`/api/kependudukan/migrasipenduduk/${id}`); const res = await fetch(`/api/kependudukan/migrasipenduduk/${id}`);

View File

@@ -1,4 +1,4 @@
import { Select, Group } from '@mantine/core'; import { Select } from '@mantine/core';
import { IconCalendar } from '@tabler/icons-react'; import { IconCalendar } from '@tabler/icons-react';
interface YearFilterProps { interface YearFilterProps {

View File

@@ -20,7 +20,7 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import dataBanjar from '../../../_state/kependudukan/data-banjar'; import dataBanjar from '../../../_state/kependudukan/data-banjar';
interface FormData { interface DataBanjarForm {
nama: string; nama: string;
penduduk: number; penduduk: number;
kk: number; kk: number;
@@ -33,14 +33,14 @@ export default function EditDataBanjar() {
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
const stateDataBanjar = useProxy(dataBanjar); const stateDataBanjar = useProxy(dataBanjar);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<DataBanjarForm>({
nama: '', nama: '',
penduduk: 0, penduduk: 0,
kk: 0, kk: 0,
miskin: 0, miskin: 0,
tahun: new Date().getFullYear(), tahun: new Date().getFullYear(),
}); });
const [originalData, setOriginalData] = useState<FormData>({ const [originalData, setOriginalData] = useState<DataBanjarForm>({
nama: '', nama: '',
penduduk: 0, penduduk: 0,
kk: 0, kk: 0,
@@ -72,7 +72,7 @@ export default function EditDataBanjar() {
stateDataBanjar.update.id = id; stateDataBanjar.update.id = id;
await stateDataBanjar.findUnique.load(id); await stateDataBanjar.findUnique.load(id);
const data = stateDataBanjar.findUnique.data; const data = stateDataBanjar.findUnique.data as DataBanjarForm | null;
if (data) { if (data) {
setFormData({ setFormData({
nama: data.nama ?? '', nama: data.nama ?? '',
@@ -101,14 +101,22 @@ export default function EditDataBanjar() {
}, [id]); }, [id]);
const handleChange = useCallback( const handleChange = useCallback(
(field: keyof FormData) => (field: keyof DataBanjarForm) =>
(value: any) => { (value: string | number | undefined) => {
const val = const val =
field === 'penduduk' || field === 'kk' || field === 'miskin' || field === 'tahun' field === 'penduduk' || field === 'kk' || field === 'miskin' || field === 'tahun'
? Number(value || 0) ? Number(value || 0)
: value; : value;
setFormData((prev) => ({ ...prev, [field]: val })); setFormData((prev) => ({ ...prev, [field]: val as never }));
},
[]
);
const handleChangeText = useCallback(
(field: keyof DataBanjarForm) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({ ...prev, [field]: e.currentTarget.value as never }));
}, },
[] []
); );
@@ -173,7 +181,7 @@ export default function EditDataBanjar() {
label="Nama Banjar" label="Nama Banjar"
placeholder="Masukkan nama banjar" placeholder="Masukkan nama banjar"
value={formData.nama} value={formData.nama}
onChange={handleChange('nama')} onChange={handleChangeText('nama')}
required required
/> />

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -27,10 +25,6 @@ function CreateDataBanjar() {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const yearOptions = Array.from({ length: 10 }, (_, i) => ({
value: String(currentYear - i),
label: String(currentYear - i),
}));
const isFormValid = () => { const isFormValid = () => {
return ( return (

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
@@ -89,7 +88,7 @@ function ListDataBanjar({ search, year }: { search: string; year?: number }) {
load(page, 10, debouncedSearch, year); load(page, 10, debouncedSearch, year);
}, [page, debouncedSearch, year]); }, [page, debouncedSearch, year]);
const filteredData = data || []; const filteredData = (data as DataBanjarType[]) || [];
if (loading || !data) { if (loading || !data) {
return ( return (
@@ -140,7 +139,7 @@ function ListDataBanjar({ search, year }: { search: string; year?: number }) {
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: DataBanjarType) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd>{item.nama}</TableTd> <TableTd>{item.nama}</TableTd>
<TableTd>{item.penduduk.toLocaleString('id-ID')}</TableTd> <TableTd>{item.penduduk.toLocaleString('id-ID')}</TableTd>
@@ -198,7 +197,7 @@ function ListDataBanjar({ search, year }: { search: string; year?: number }) {
<Box hiddenFrom="md"> <Box hiddenFrom="md">
<Stack gap="xs"> <Stack gap="xs">
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: DataBanjarType) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm"> <Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Box> <Box>

View File

@@ -9,7 +9,6 @@ import {
Loader, Loader,
Paper, Paper,
Stack, Stack,
TextInput,
Title, Title,
NumberInput, NumberInput,
Select Select
@@ -21,7 +20,7 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import distribusiAgama from '../../../_state/kependudukan/distribusi-agama'; import distribusiAgama from '../../../_state/kependudukan/distribusi-agama';
interface FormData { interface DistribusiAgamaForm {
agama: string; agama: string;
jumlah: number; jumlah: number;
tahun: number; tahun: number;
@@ -32,12 +31,12 @@ export default function EditDistribusiAgama() {
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
const stateDistribusiAgama = useProxy(distribusiAgama); const stateDistribusiAgama = useProxy(distribusiAgama);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<DistribusiAgamaForm>({
agama: '', agama: '',
jumlah: 0, jumlah: 0,
tahun: new Date().getFullYear(), tahun: new Date().getFullYear(),
}); });
const [originalData, setOriginalData] = useState<FormData>({ const [originalData, setOriginalData] = useState<DistribusiAgamaForm>({
agama: '', agama: '',
jumlah: 0, jumlah: 0,
tahun: new Date().getFullYear(), tahun: new Date().getFullYear(),
@@ -78,7 +77,7 @@ export default function EditDistribusiAgama() {
stateDistribusiAgama.update.id = id; stateDistribusiAgama.update.id = id;
await stateDistribusiAgama.findUnique.load(id); await stateDistribusiAgama.findUnique.load(id);
const data = stateDistribusiAgama.findUnique.data; const data = stateDistribusiAgama.findUnique.data as DistribusiAgamaForm | null;
if (data) { if (data) {
setFormData({ setFormData({
agama: data.agama ?? '', agama: data.agama ?? '',
@@ -102,15 +101,18 @@ export default function EditDistribusiAgama() {
loadData(); loadData();
}, [id]); }, [id]);
const handleChange = useCallback( const handleChangeNumber = useCallback(
(field: keyof FormData) => (field: keyof DistribusiAgamaForm) =>
(value: any) => { (value: string | number) => {
const val = setFormData((prev) => ({ ...prev, [field]: (typeof value === 'string' ? Number(value) || 0 : (value ?? 0)) as never }));
field === 'jumlah' || field === 'tahun' },
? Number(value || 0) []
: value; );
setFormData((prev) => ({ ...prev, [field]: val })); const handleChangeSelect = useCallback(
(field: keyof DistribusiAgamaForm) =>
(value: string | null) => {
setFormData((prev) => ({ ...prev, [field]: (value || '') as never }));
}, },
[] []
); );
@@ -174,7 +176,7 @@ export default function EditDistribusiAgama() {
placeholder="Pilih agama" placeholder="Pilih agama"
data={agamaOptions} data={agamaOptions}
value={formData.agama} value={formData.agama}
onChange={handleChange('agama')} onChange={handleChangeSelect('agama')}
required required
searchable searchable
/> />
@@ -183,7 +185,7 @@ export default function EditDistribusiAgama() {
label="Jumlah" label="Jumlah"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
value={formData.jumlah} value={formData.jumlah}
onChange={handleChange('jumlah')} onChange={handleChangeNumber('jumlah')}
min={0} min={0}
required required
/> />
@@ -193,7 +195,7 @@ export default function EditDistribusiAgama() {
placeholder="Pilih tahun" placeholder="Pilih tahun"
data={yearOptions} data={yearOptions}
value={String(formData.tahun)} value={String(formData.tahun)}
onChange={handleChange('tahun')} onChange={handleChangeSelect('tahun')}
required required
/> />

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import colors from '@/con/colors'; import colors from '@/con/colors';
@@ -10,7 +8,6 @@ import {
Loader, Loader,
Paper, Paper,
Stack, Stack,
TextInput,
Title, Title,
NumberInput, NumberInput,
Select Select

View File

@@ -1,11 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
Box, Box,
Button, Button,
Center, Center,
Flex,
Group, Group,
Pagination, Pagination,
Paper, Paper,
@@ -23,7 +21,7 @@ import {
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { YearFilter } from '../_components/YearFilter'; import { YearFilter } from '../_components/YearFilter';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
@@ -87,7 +85,7 @@ function ListDistribusiAgama({ search, year }: { search: string; year?: number }
load(page, 10, debouncedSearch, year); load(page, 10, debouncedSearch, year);
}, [page, debouncedSearch, year]); }, [page, debouncedSearch, year]);
const filteredData = data || []; const filteredData = (data as DistribusiAgamaType[]) || [];
if (loading || !data) { if (loading || !data) {
return ( return (
@@ -136,7 +134,7 @@ function ListDistribusiAgama({ search, year }: { search: string; year?: number }
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: DistribusiAgamaType) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd>{item.agama}</TableTd> <TableTd>{item.agama}</TableTd>
<TableTd>{item.jumlah.toLocaleString('id-ID')}</TableTd> <TableTd>{item.jumlah.toLocaleString('id-ID')}</TableTd>
@@ -192,7 +190,7 @@ function ListDistribusiAgama({ search, year }: { search: string; year?: number }
<Box hiddenFrom="md"> <Box hiddenFrom="md">
<Stack gap="xs"> <Stack gap="xs">
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: DistribusiAgamaType) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm"> <Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Box> <Box>

View File

@@ -20,7 +20,7 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import distribusiUmur from '../../../_state/kependudukan/distribusi-umur'; import distribusiUmur from '../../../_state/kependudukan/distribusi-umur';
interface FormData { interface DistribusiUmurForm {
rentangUmur: string; rentangUmur: string;
jumlah: number; jumlah: number;
tahun: number; tahun: number;
@@ -31,12 +31,12 @@ export default function EditDistribusiUmur() {
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
const stateDistribusiUmur = useProxy(distribusiUmur); const stateDistribusiUmur = useProxy(distribusiUmur);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<DistribusiUmurForm>({
rentangUmur: '', rentangUmur: '',
jumlah: 0, jumlah: 0,
tahun: new Date().getFullYear(), tahun: new Date().getFullYear(),
}); });
const [originalData, setOriginalData] = useState<FormData>({ const [originalData, setOriginalData] = useState<DistribusiUmurForm>({
rentangUmur: '', rentangUmur: '',
jumlah: 0, jumlah: 0,
tahun: new Date().getFullYear(), tahun: new Date().getFullYear(),
@@ -78,7 +78,7 @@ export default function EditDistribusiUmur() {
stateDistribusiUmur.update.id = id; stateDistribusiUmur.update.id = id;
await stateDistribusiUmur.findUnique.load(id); await stateDistribusiUmur.findUnique.load(id);
const data = stateDistribusiUmur.findUnique.data; const data = stateDistribusiUmur.findUnique.data as DistribusiUmurForm | null;
if (data) { if (data) {
setFormData({ setFormData({
rentangUmur: data.rentangUmur ?? '', rentangUmur: data.rentangUmur ?? '',
@@ -102,15 +102,18 @@ export default function EditDistribusiUmur() {
loadData(); loadData();
}, [id]); }, [id]);
const handleChange = useCallback( const handleChangeNumber = useCallback(
(field: keyof FormData) => (field: keyof DistribusiUmurForm) =>
(value: any) => { (value: string | number) => {
const val = setFormData((prev) => ({ ...prev, [field]: (typeof value === 'string' ? Number(value) || 0 : (value ?? 0)) as never }));
field === 'jumlah' || field === 'tahun' },
? Number(value || 0) []
: value; );
setFormData((prev) => ({ ...prev, [field]: val })); const handleChangeSelect = useCallback(
(field: keyof DistribusiUmurForm) =>
(value: string | null) => {
setFormData((prev) => ({ ...prev, [field]: (value || '') as never }));
}, },
[] []
); );
@@ -174,7 +177,7 @@ export default function EditDistribusiUmur() {
placeholder="Pilih rentang umur" placeholder="Pilih rentang umur"
data={rentangUmurOptions} data={rentangUmurOptions}
value={formData.rentangUmur} value={formData.rentangUmur}
onChange={handleChange('rentangUmur')} onChange={handleChangeSelect('rentangUmur')}
required required
searchable searchable
/> />
@@ -183,7 +186,7 @@ export default function EditDistribusiUmur() {
label="Jumlah" label="Jumlah"
placeholder="Masukkan jumlah" placeholder="Masukkan jumlah"
value={formData.jumlah} value={formData.jumlah}
onChange={handleChange('jumlah')} onChange={handleChangeNumber('jumlah')}
min={0} min={0}
required required
/> />
@@ -193,7 +196,7 @@ export default function EditDistribusiUmur() {
placeholder="Pilih tahun" placeholder="Pilih tahun"
data={yearOptions} data={yearOptions}
value={String(formData.tahun)} value={String(formData.tahun)}
onChange={handleChange('tahun')} onChange={handleChangeSelect('tahun')}
required required
/> />

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import colors from '@/con/colors'; import colors from '@/con/colors';

View File

@@ -1,11 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
Box, Box,
Button, Button,
Center, Center,
Flex,
Group, Group,
Pagination, Pagination,
Paper, Paper,
@@ -88,7 +86,7 @@ function ListDistribusiUmur({ search, year }: { search: string; year?: number })
load(page, 10, debouncedSearch, year); load(page, 10, debouncedSearch, year);
}, [page, debouncedSearch, year]); }, [page, debouncedSearch, year]);
const filteredData = data || []; const filteredData = (data as DistribusiUmurType[]) || [];
if (loading || !data) { if (loading || !data) {
return ( return (
@@ -137,7 +135,7 @@ function ListDistribusiUmur({ search, year }: { search: string; year?: number })
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: DistribusiUmurType) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd>{item.rentangUmur}</TableTd> <TableTd>{item.rentangUmur}</TableTd>
<TableTd>{item.jumlah.toLocaleString('id-ID')}</TableTd> <TableTd>{item.jumlah.toLocaleString('id-ID')}</TableTd>
@@ -193,7 +191,7 @@ function ListDistribusiUmur({ search, year }: { search: string; year?: number })
<Box hiddenFrom="md"> <Box hiddenFrom="md">
<Stack gap="xs"> <Stack gap="xs">
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: DistribusiUmurType) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm"> <Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Box> <Box>

View File

@@ -22,7 +22,7 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import migrasiPenduduk from '../../../_state/kependudukan/migrasi-penduduk'; import migrasiPenduduk from '../../../_state/kependudukan/migrasi-penduduk';
interface FormData { interface MigrasiPendudukForm {
jenis: string; jenis: string;
nama: string; nama: string;
tanggal: string; tanggal: string;
@@ -36,7 +36,7 @@ export default function EditMigrasiPenduduk() {
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
const stateMigrasiPenduduk = useProxy(migrasiPenduduk); const stateMigrasiPenduduk = useProxy(migrasiPenduduk);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<MigrasiPendudukForm>({
jenis: '', jenis: '',
nama: '', nama: '',
tanggal: '', tanggal: '',
@@ -44,7 +44,7 @@ export default function EditMigrasiPenduduk() {
alasan: '', alasan: '',
jenisKelamin: '', jenisKelamin: '',
}); });
const [originalData, setOriginalData] = useState<FormData>({ const [originalData, setOriginalData] = useState<MigrasiPendudukForm>({
jenis: '', jenis: '',
nama: '', nama: '',
tanggal: '', tanggal: '',
@@ -81,7 +81,7 @@ export default function EditMigrasiPenduduk() {
stateMigrasiPenduduk.update.id = id; stateMigrasiPenduduk.update.id = id;
await stateMigrasiPenduduk.findUnique.load(id); await stateMigrasiPenduduk.findUnique.load(id);
const data = stateMigrasiPenduduk.findUnique.data; const data = stateMigrasiPenduduk.findUnique.data as MigrasiPendudukForm | null;
if (data) { if (data) {
setFormData({ setFormData({
jenis: data.jenis ?? '', jenis: data.jenis ?? '',
@@ -111,11 +111,18 @@ export default function EditMigrasiPenduduk() {
loadData(); loadData();
}, [id]); }, [id]);
const handleChange = useCallback( const handleChangeText = useCallback(
(field: keyof FormData) => (field: keyof MigrasiPendudukForm) =>
(value: any) => { (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const val = value || ''; setFormData((prev) => ({ ...prev, [field]: e.currentTarget.value as never }));
setFormData((prev) => ({ ...prev, [field]: val })); },
[]
);
const handleChangeSelect = useCallback(
(field: keyof MigrasiPendudukForm) =>
(value: string | null) => {
setFormData((prev) => ({ ...prev, [field]: (value || '') as never }));
}, },
[] []
); );
@@ -182,7 +189,7 @@ export default function EditMigrasiPenduduk() {
placeholder="Pilih jenis migrasi" placeholder="Pilih jenis migrasi"
data={jenisOptions} data={jenisOptions}
value={formData.jenis} value={formData.jenis}
onChange={handleChange('jenis')} onChange={handleChangeSelect('jenis')}
required required
/> />
@@ -190,7 +197,7 @@ export default function EditMigrasiPenduduk() {
label="Nama" label="Nama"
placeholder="Masukkan nama lengkap" placeholder="Masukkan nama lengkap"
value={formData.nama} value={formData.nama}
onChange={handleChange('nama')} onChange={handleChangeText('nama')}
required required
/> />
@@ -211,7 +218,7 @@ export default function EditMigrasiPenduduk() {
label={formData.jenis === 'MASUK' ? 'Asal' : 'Tujuan'} label={formData.jenis === 'MASUK' ? 'Asal' : 'Tujuan'}
placeholder={formData.jenis === 'MASUK' ? 'Masukkan asal' : 'Masukkan tujuan'} placeholder={formData.jenis === 'MASUK' ? 'Masukkan asal' : 'Masukkan tujuan'}
value={formData.asalTujuan} value={formData.asalTujuan}
onChange={handleChange('asalTujuan')} onChange={handleChangeText('asalTujuan')}
required required
/> />
@@ -219,7 +226,7 @@ export default function EditMigrasiPenduduk() {
label="Alasan" label="Alasan"
placeholder="Masukkan alasan (opsional)" placeholder="Masukkan alasan (opsional)"
value={formData.alasan} value={formData.alasan}
onChange={handleChange('alasan')} onChange={handleChangeText('alasan')}
autosize autosize
minRows={2} minRows={2}
/> />
@@ -229,7 +236,7 @@ export default function EditMigrasiPenduduk() {
placeholder="Pilih jenis kelamin" placeholder="Pilih jenis kelamin"
data={jenisKelaminOptions} data={jenisKelaminOptions}
value={formData.jenisKelamin} value={formData.jenisKelamin}
onChange={handleChange('jenisKelamin')} onChange={handleChangeSelect('jenisKelamin')}
/> />
<Group justify="flex-end"> <Group justify="flex-end">

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import colors from '@/con/colors'; import colors from '@/con/colors';

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
@@ -89,7 +88,7 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
load(page, 10, debouncedSearch, year); load(page, 10, debouncedSearch, year);
}, [page, debouncedSearch, year]); }, [page, debouncedSearch, year]);
const filteredData = data || []; const filteredData = (data as MigrasiPendudukType[]) || [];
if (loading || !data) { if (loading || !data) {
return ( return (
@@ -152,7 +151,7 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: MigrasiPendudukType) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Text <Text
@@ -218,7 +217,7 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
<Box hiddenFrom="md"> <Box hiddenFrom="md">
<Stack gap="xs"> <Stack gap="xs">
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item: MigrasiPendudukType) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm"> <Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Box> <Box>

View File

@@ -10,7 +10,6 @@ export default async function dashboardSummary() {
totalKK, totalKK,
totalKelahiran, totalKelahiran,
totalKemiskinan, totalKemiskinan,
kelahiranData,
kematianData, kematianData,
pindahMasukData, pindahMasukData,
pindahKeluarData, pindahKeluarData,

View File

@@ -1,38 +1,34 @@
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia"; import { Context } from "elysia";
type FormCreate = Prisma.MigrasiPendudukGetPayload<{ type FormCreate = {
select: { jenis: string;
jenis: true; nama: string;
nama: true; tanggal: string;
tanggal: true; asalTujuan: string;
asalTujuan: true; alasan: string | null;
alasan: true;
jenisKelamin: true;
} }
}>
export default async function migrasiPendudukCreate(context: Context) { export default async function migrasiPendudukCreate(context: Context) {
const body = context.body as FormCreate; const body = context.body as FormCreate;
const isMasuk = body.jenis === 'MASUK';
const created = await prisma.migrasiPenduduk.create({ const created = await prisma.migrasiPenduduk.create({
data: { data: {
jenis: body.jenis, jenis: body.jenis as 'MASUK' | 'KELUAR',
nama: body.nama, nama: body.nama,
tanggal: new Date(body.tanggal), tanggal: new Date(body.tanggal),
asalTujuan: body.asalTujuan, asal: isMasuk ? body.asalTujuan : undefined,
tujuan: !isMasuk ? body.asalTujuan : undefined,
alasan: body.alasan, alasan: body.alasan,
jenisKelamin: body.jenisKelamin,
}, },
select: { select: {
id: true, id: true,
jenis: true, jenis: true,
nama: true, nama: true,
tanggal: true, tanggal: true,
asalTujuan: true,
alasan: true, alasan: true,
jenisKelamin: true,
} }
}); });
return { return {

View File

@@ -11,13 +11,12 @@ export default async function migrasiPendudukUpdate(context: Context) {
} }
} }
const {jenis, nama, tanggal, asalTujuan, alasan, jenisKelamin} = context.body as { const {jenis, nama, tanggal, asalTujuan, alasan} = context.body as {
jenis: string; jenis: string;
nama: string; nama: string;
tanggal: string; tanggal: string;
asalTujuan: string; asalTujuan: string;
alasan?: string; alasan?: string;
jenisKelamin?: string;
} }
const existing = await prisma.migrasiPenduduk.findUnique({ const existing = await prisma.migrasiPenduduk.findUnique({
@@ -36,12 +35,12 @@ export default async function migrasiPendudukUpdate(context: Context) {
const updated = await prisma.migrasiPenduduk.update({ const updated = await prisma.migrasiPenduduk.update({
where: { id }, where: { id },
data: { data: {
jenis, jenis: jenis as 'MASUK' | 'KELUAR',
nama, nama,
tanggal: new Date(tanggal), tanggal: new Date(tanggal),
asalTujuan, asal: jenis === 'MASUK' ? asalTujuan : undefined,
tujuan: jenis === 'KELUAR' ? asalTujuan : undefined,
alasan, alasan,
jenisKelamin,
}, },
}) })

View File

@@ -1,8 +1,7 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, SimpleGrid, Skeleton, Group, Badge, Center, Image } from '@mantine/core'; import { Stack, Box, Paper, Text, Title, SimpleGrid, Skeleton, Group, Badge, Center } from '@mantine/core';
import { IconUsers, IconHome, IconBasket, IconCoin, IconDatabaseOff } from '@tabler/icons-react'; import { IconUsers, IconHome, IconBasket, IconCoin, IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import kependudukanDashboard from '@/app/admin/(dashboard)/_state/kependudukan/dashboard'; import kependudukanDashboard from '@/app/admin/(dashboard)/_state/kependudukan/dashboard';

View File

@@ -2,7 +2,6 @@
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, Table, Center } from '@mantine/core'; import { Stack, Box, Paper, Text, Title, Skeleton, Table, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react'; import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import dataBanjar from '@/app/admin/(dashboard)/_state/kependudukan/data-banjar'; import dataBanjar from '@/app/admin/(dashboard)/_state/kependudukan/data-banjar';
@@ -15,7 +14,16 @@ function Page() {
state.findMany.load() state.findMany.load()
}, []) }, [])
const data = state.findMany.data || []; interface DataBanjarItem {
id: string;
nama: string;
penduduk: number;
kk: number;
miskin: number;
tahun: number;
}
const data = (state.findMany.data as DataBanjarItem[]) || [];
if (state.findMany.loading) { if (state.findMany.loading) {
return ( return (

View File

@@ -2,7 +2,6 @@
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, Flex, ColorSwatch, Center } from '@mantine/core'; import { Stack, Box, Paper, Text, Title, Skeleton, Flex, ColorSwatch, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react'; import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import distribusiAgama from '@/app/admin/(dashboard)/_state/kependudukan/distribusi-agama'; import distribusiAgama from '@/app/admin/(dashboard)/_state/kependudukan/distribusi-agama';
@@ -16,7 +15,14 @@ function Page() {
state.findMany.load() state.findMany.load()
}, []) }, [])
const data = state.findMany.data || []; interface DistribusiAgamaItem {
id: string;
agama: string;
jumlah: number;
tahun: number;
}
const data = (state.findMany.data as DistribusiAgamaItem[]) || [];
if (state.findMany.loading) { if (state.findMany.loading) {
return ( return (

View File

@@ -2,7 +2,6 @@
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, Center } from '@mantine/core'; import { Stack, Box, Paper, Text, Title, Skeleton, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react'; import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import distribusiUmur from '@/app/admin/(dashboard)/_state/kependudukan/distribusi-umur'; import distribusiUmur from '@/app/admin/(dashboard)/_state/kependudukan/distribusi-umur';
@@ -16,7 +15,14 @@ function Page() {
state.findMany.load() state.findMany.load()
}, []) }, [])
const data = state.findMany.data || []; interface DistribusiUmurItem {
id: string;
rentangUmur: string;
jumlah: number;
tahun: number;
}
const data = (state.findMany.data as DistribusiUmurItem[]) || [];
if (state.findMany.loading) { if (state.findMany.loading) {
return ( return (