feat: add kependudukan seeders, API routes, year filter, and navbar menu
- Add Prisma models: DataBanjar, DistribusiAgama, DistribusiUmur, MigrasiPenduduk, DinamikaPenduduk - Create seeders for all kependudukan models with year 2026 data - Register Kependudukan API routes in route.ts - Update API findMany endpoints to make tahun parameter optional - Add YearFilter reusable component for admin pages - Update 4 kependudukan admin pages with year filter UI - Fix Mantine color array in AdminThemeProvider (add 10th element) - Fix invalid Mantine color scale in paguTable.tsx (gray.50 -> gray.1) - Add Kependudukan menu to navbar-list-menu.ts - Fix Bun JSON import resolution with loadJsonData helper - Update 74 seeder files to use dynamic JSON loading Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -58,14 +58,17 @@ const dataBanjar = proxy({
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => {
|
||||
tahun: undefined as number | undefined,
|
||||
load: async (page = 1, limit = 10, search = "", tahun?: number) => {
|
||||
dataBanjar.findMany.loading = true;
|
||||
dataBanjar.findMany.page = page;
|
||||
dataBanjar.findMany.search = search;
|
||||
dataBanjar.findMany.tahun = tahun;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit, tahun };
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
if (tahun) query.tahun = tahun;
|
||||
|
||||
const res = await ApiFetch.api.kependudukan.databanjar["find-many"].get({ query });
|
||||
|
||||
|
||||
@@ -54,13 +54,14 @@ const distribusiAgama = proxy({
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => {
|
||||
load: async (page = 1, limit = 10, search = "", tahun?: number) => {
|
||||
distribusiAgama.findMany.loading = true;
|
||||
distribusiAgama.findMany.page = page;
|
||||
distribusiAgama.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit, tahun };
|
||||
const query: any = { page, limit };
|
||||
if (tahun) query.tahun = tahun;
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.kependudukan.distribusiagama["find-many"].get({ query });
|
||||
|
||||
@@ -54,13 +54,14 @@ const distribusiUmur = proxy({
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => {
|
||||
load: async (page = 1, limit = 10, search = "", tahun?: number) => {
|
||||
distribusiUmur.findMany.loading = true;
|
||||
distribusiUmur.findMany.page = page;
|
||||
distribusiUmur.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit, tahun };
|
||||
const query: any = { page, limit };
|
||||
if (tahun) query.tahun = tahun;
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.kependudukan.distribusiumur["find-many"].get({ query });
|
||||
|
||||
@@ -60,13 +60,14 @@ const migrasiPenduduk = proxy({
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => {
|
||||
load: async (page = 1, limit = 10, search = "", tahun?: number) => {
|
||||
migrasiPenduduk.findMany.loading = true;
|
||||
migrasiPenduduk.findMany.page = page;
|
||||
migrasiPenduduk.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit, tahun };
|
||||
const query: any = { page, limit };
|
||||
if (tahun) query.tahun = tahun;
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.kependudukan.migrasipenduduk["find-many"].get({ query });
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Select, Group } from '@mantine/core';
|
||||
import { IconCalendar } from '@tabler/icons-react';
|
||||
|
||||
interface YearFilterProps {
|
||||
value?: number;
|
||||
onChange: (year: number | undefined) => void;
|
||||
minYear?: number;
|
||||
maxYear?: number;
|
||||
}
|
||||
|
||||
export function YearFilter({
|
||||
value,
|
||||
onChange,
|
||||
minYear = 2020,
|
||||
maxYear = new Date().getFullYear() + 1,
|
||||
}: YearFilterProps) {
|
||||
const years = Array.from(
|
||||
{ length: maxYear - minYear + 1 },
|
||||
(_, i) => maxYear - i
|
||||
);
|
||||
|
||||
const options = [
|
||||
{ value: '', label: 'Semua Tahun' },
|
||||
...years.map((year) => ({
|
||||
value: year.toString(),
|
||||
label: year.toString(),
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<Select
|
||||
placeholder="Pilih Tahun"
|
||||
data={options}
|
||||
value={value?.toString() ?? ''}
|
||||
onChange={(val) => onChange(val ? parseInt(val) : undefined)}
|
||||
leftSection={<IconCalendar size={18} />}
|
||||
clearable
|
||||
w={200}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default YearFilter;
|
||||
@@ -27,9 +27,12 @@ import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import dataBanjar from '../../_state/kependudukan/data-banjar';
|
||||
import { YearFilter } from '../_components/YearFilter';
|
||||
|
||||
function DataBanjarAdmin() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedYear, setSelectedYear] = useState<number | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
@@ -39,12 +42,18 @@ function DataBanjarAdmin() {
|
||||
value={search}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListDataBanjar search={search} />
|
||||
<Box mt="md">
|
||||
<YearFilter value={selectedYear} onChange={(year) => {
|
||||
setSelectedYear(year);
|
||||
dataBanjar.findMany.page = 1;
|
||||
}} />
|
||||
</Box>
|
||||
<ListDataBanjar search={search} year={selectedYear} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDataBanjar({ search }: { search: string }) {
|
||||
function ListDataBanjar({ search, year }: { search: string; year?: number }) {
|
||||
type DataBanjarType = {
|
||||
id: string;
|
||||
nama: string;
|
||||
@@ -77,8 +86,8 @@ function ListDataBanjar({ search }: { search: string }) {
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
load(page, 10, debouncedSearch, year);
|
||||
}, [page, debouncedSearch, year]);
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
|
||||
@@ -25,12 +25,14 @@ import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { YearFilter } from '../_components/YearFilter';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import distribusiAgama from '../../_state/kependudukan/distribusi-agama';
|
||||
|
||||
function DistribusiAgamaAdmin() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedYear, setSelectedYear] = useState<number | undefined>(undefined);
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
@@ -40,12 +42,18 @@ function DistribusiAgamaAdmin() {
|
||||
value={search}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListDistribusiAgama search={search} />
|
||||
<Box mt="md">
|
||||
<YearFilter value={selectedYear} onChange={(year) => {
|
||||
setSelectedYear(year);
|
||||
distribusiAgama.findMany.page = 1;
|
||||
}} />
|
||||
</Box>
|
||||
<ListDistribusiAgama search={search} year={selectedYear} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDistribusiAgama({ search }: { search: string }) {
|
||||
function ListDistribusiAgama({ search, year }: { search: string; year?: number }) {
|
||||
type DistribusiAgamaType = {
|
||||
id: string;
|
||||
agama: string;
|
||||
@@ -76,8 +84,8 @@ function ListDistribusiAgama({ search }: { search: string }) {
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
load(page, 10, debouncedSearch, year);
|
||||
}, [page, debouncedSearch, year]);
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
|
||||
@@ -25,12 +25,14 @@ import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { YearFilter } from '../_components/YearFilter';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import distribusiUmur from '../../_state/kependudukan/distribusi-umur';
|
||||
|
||||
function DistribusiUmurAdmin() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedYear, setSelectedYear] = useState<number | undefined>(undefined);
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
@@ -40,12 +42,18 @@ function DistribusiUmurAdmin() {
|
||||
value={search}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListDistribusiUmur search={search} />
|
||||
<Box mt="md">
|
||||
<YearFilter value={selectedYear} onChange={(year) => {
|
||||
setSelectedYear(year);
|
||||
distribusiUmur.findMany.page = 1;
|
||||
}} />
|
||||
</Box>
|
||||
<ListDistribusiUmur search={search} year={selectedYear} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDistribusiUmur({ search }: { search: string }) {
|
||||
function ListDistribusiUmur({ search, year }: { search: string; year?: number }) {
|
||||
type DistribusiUmurType = {
|
||||
id: string;
|
||||
rentangUmur: string;
|
||||
@@ -77,8 +85,8 @@ function ListDistribusiUmur({ search }: { search: string }) {
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
load(page, 10, debouncedSearch, year);
|
||||
}, [page, debouncedSearch, year]);
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
|
||||
@@ -24,12 +24,14 @@ import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { YearFilter } from '../_components/YearFilter';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import migrasiPenduduk from '../../_state/kependudukan/migrasi-penduduk';
|
||||
|
||||
function MigrasiPendudukAdmin() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedYear, setSelectedYear] = useState<number | undefined>(undefined);
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
@@ -39,12 +41,18 @@ function MigrasiPendudukAdmin() {
|
||||
value={search}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListMigrasiPenduduk search={search} />
|
||||
<Box mt="md">
|
||||
<YearFilter value={selectedYear} onChange={(year) => {
|
||||
setSelectedYear(year);
|
||||
migrasiPenduduk.findMany.page = 1;
|
||||
}} />
|
||||
</Box>
|
||||
<ListMigrasiPenduduk search={search} year={selectedYear} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListMigrasiPenduduk({ search }: { search: string }) {
|
||||
function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }) {
|
||||
type MigrasiPendudukType = {
|
||||
id: string;
|
||||
jenis: string;
|
||||
@@ -78,8 +86,8 @@ function ListMigrasiPenduduk({ search }: { search: string }) {
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
load(page, 10, debouncedSearch, year);
|
||||
}, [page, debouncedSearch, year]);
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
|
||||
@@ -6,10 +6,15 @@ export default async function dataBanjarFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 100;
|
||||
const search = (context.query.search as string) || '';
|
||||
const tahun = Number(context.query.tahun) || new Date().getFullYear();
|
||||
const tahun = context.query.tahun ? Number(context.query.tahun) : undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { isActive: true, tahun };
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter by tahun hanya jika dikirim
|
||||
if (tahun) {
|
||||
where.tahun = tahun;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
|
||||
@@ -6,10 +6,15 @@ export default async function distribusiAgamaFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 100;
|
||||
const search = (context.query.search as string) || '';
|
||||
const tahun = Number(context.query.tahun) || new Date().getFullYear();
|
||||
const tahun = context.query.tahun ? Number(context.query.tahun) : undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { isActive: true, tahun };
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter by tahun hanya jika dikirim
|
||||
if (tahun) {
|
||||
where.tahun = tahun;
|
||||
}
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
|
||||
@@ -5,10 +5,15 @@ import { Context } from "elysia";
|
||||
export default async function distribusiUmurFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 100;
|
||||
const tahun = Number(context.query.tahun) || new Date().getFullYear();
|
||||
const tahun = context.query.tahun ? Number(context.query.tahun) : undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { isActive: true, tahun };
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter by tahun hanya jika dikirim
|
||||
if (tahun) {
|
||||
where.tahun = tahun;
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
|
||||
@@ -7,7 +7,7 @@ export default async function migrasiPendudukFindMany(context: Context) {
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const jenis = (context.query.jenis as string) || '';
|
||||
const tahun = Number(context.query.tahun) || new Date().getFullYear();
|
||||
const tahun = context.query.tahun ? Number(context.query.tahun) : undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
@@ -16,6 +16,7 @@ export default async function migrasiPendudukFindMany(context: Context) {
|
||||
where.jenis = jenis;
|
||||
}
|
||||
|
||||
// Filter by tahun hanya jika dikirim
|
||||
if (tahun) {
|
||||
where.tanggal = {
|
||||
gte: new Date(`${tahun}-01-01`),
|
||||
|
||||
@@ -23,6 +23,7 @@ import Inovasi from "./_lib/inovasi";
|
||||
import Lingkungan from "./_lib/lingkungan";
|
||||
import LandingPage from "./_lib/landing_page";
|
||||
import Pendidikan from "./_lib/pendidikan";
|
||||
import Kependudukan from "./_lib/kependudukan";
|
||||
import User from "./_lib/user";
|
||||
import Role from "./_lib/user/role";
|
||||
import Search from "./_lib/search";
|
||||
@@ -119,6 +120,7 @@ const ApiServer = new Elysia({ prefix: "/api" })
|
||||
.use(Inovasi)
|
||||
.use(Lingkungan)
|
||||
.use(Pendidikan)
|
||||
.use(Kependudukan)
|
||||
.use(User)
|
||||
.use(Role)
|
||||
.use(Search)
|
||||
|
||||
@@ -21,10 +21,10 @@ function Section({ title, data, badgeColor = 'blue' }: SectionProps) {
|
||||
</Table.Tr>
|
||||
|
||||
{data.map((item, index) => (
|
||||
<Table.Tr
|
||||
key={item.id}
|
||||
bg={index % 2 === 1 ? 'gray.50' : 'white'}
|
||||
style={{
|
||||
<Table.Tr
|
||||
key={item.id}
|
||||
bg={index % 2 === 1 ? 'gray.1' : 'white'}
|
||||
style={{
|
||||
transition: 'background-color 0.2s ease',
|
||||
':hover': {
|
||||
backgroundColor: 'var(--mantine-color-blue-0)',
|
||||
|
||||
@@ -46,6 +46,7 @@ export function AdminThemeProvider({ children, forceTheme }: AdminThemeProviderP
|
||||
tokens.colors.primaryDark,
|
||||
tokens.colors.primaryDark,
|
||||
tokens.colors.primaryDark,
|
||||
tokens.colors.primaryDark,
|
||||
],
|
||||
},
|
||||
primaryColor: 'primary',
|
||||
|
||||
@@ -295,51 +295,82 @@ const navbarListMenu = [
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
name: "Pendidikan",
|
||||
name: "Kependudukan",
|
||||
children: [
|
||||
{
|
||||
id: "8.1",
|
||||
name: "Dashboard Kependudukan",
|
||||
href: "/darmasaba/kependudukan/dashboard"
|
||||
},
|
||||
{
|
||||
id: "8.2",
|
||||
name: "Data Per Banjar",
|
||||
href: "/darmasaba/kependudukan/data-per-banjar"
|
||||
},
|
||||
{
|
||||
id: "8.3",
|
||||
name: "Dinamika Penduduk",
|
||||
href: "/darmasaba/kependudukan/dinamika-penduduk"
|
||||
},
|
||||
{
|
||||
id: "8.4",
|
||||
name: "Distribusi Agama",
|
||||
href: "/darmasaba/kependudukan/distribusi-agama"
|
||||
},
|
||||
{
|
||||
id: "8.5",
|
||||
name: "Distribusi Umur",
|
||||
href: "/darmasaba/kependudukan/distribusi-umur"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
name: "Pendidikan",
|
||||
children: [
|
||||
{
|
||||
id: "9.1",
|
||||
name: "Info Sekolah",
|
||||
href: "/darmasaba/pendidikan/info-sekolah/semua"
|
||||
},
|
||||
{
|
||||
id: "8.2",
|
||||
id: "9.2",
|
||||
name: "Beasiswa Desa",
|
||||
href: "/darmasaba/pendidikan/beasiswa-desa"
|
||||
},
|
||||
{
|
||||
id: "8.3",
|
||||
id: "9.3",
|
||||
name: "Program Pendidikan Anak",
|
||||
href: "/darmasaba/pendidikan/program-pendidikan-anak"
|
||||
},
|
||||
{
|
||||
id: "8.4",
|
||||
id: "9.4",
|
||||
name: "Bimbingan Belajar Desa",
|
||||
href: "/darmasaba/pendidikan/bimbingan-belajar-desa"
|
||||
},
|
||||
{
|
||||
id: "8.5",
|
||||
id: "9.5",
|
||||
name: "Pendidikan Non Formal",
|
||||
href: "/darmasaba/pendidikan/pendidikan-non-formal"
|
||||
},
|
||||
{
|
||||
id: "8.6",
|
||||
id: "9.6",
|
||||
name: "Perpustakaan Digital",
|
||||
href: "/darmasaba/pendidikan/perpustakaan-digital/semua"
|
||||
},
|
||||
{
|
||||
id: "8.7",
|
||||
id: "9.7",
|
||||
name: "Data Pendidikan",
|
||||
href: "/darmasaba/pendidikan/data-pendidikan"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
id: "10",
|
||||
name: "Musik",
|
||||
children: [
|
||||
{
|
||||
id: "9.1",
|
||||
id: "10.1",
|
||||
name: "Musik Desa",
|
||||
href: "/darmasaba/musik/musik-desa"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user