diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..95a1463b --- /dev/null +++ b/biome.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "experimentalScannerIgnores": [ + "node_modules", + ".next", + "out", + "public" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "warn", + "noUnusedImports": "warn" + }, + "suspicious": { + "noExplicitAny": "warn" + }, + "style": { + "noNonNullAssertion": "warn" + }, + "complexity": { + "noForEach": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "always" + } + } +} diff --git a/src/app/admin/(dashboard)/_state/kependudukan/dashboard.ts b/src/app/admin/(dashboard)/_state/kependudukan/dashboard.ts new file mode 100644 index 00000000..894ccbee --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kependudukan/dashboard.ts @@ -0,0 +1,27 @@ +import ApiFetch from "@/lib/api-fetch"; +import { proxy } from "valtio"; + +const kependudukanDashboard = proxy({ + summary: { + data: null as any, + loading: false, + async load() { + kependudukanDashboard.summary.loading = true; + try { + const res = await ApiFetch.api.kependudukan.dashboard.summary.get(); + if (res.status === 200 && res.data?.success) { + kependudukanDashboard.summary.data = res.data.data; + } else { + kependudukanDashboard.summary.data = null; + } + } catch (err) { + console.error("Gagal fetch dashboard summary:", err); + kependudukanDashboard.summary.data = null; + } finally { + kependudukanDashboard.summary.loading = false; + } + }, + }, +}); + +export default kependudukanDashboard; diff --git a/src/app/admin/(dashboard)/_state/kependudukan/data-banjar.ts b/src/app/admin/(dashboard)/_state/kependudukan/data-banjar.ts new file mode 100644 index 00000000..7267863c --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kependudukan/data-banjar.ts @@ -0,0 +1,205 @@ +import ApiFetch from "@/lib/api-fetch"; +import { proxy } from "valtio"; +import { toast } from "react-toastify"; +import { z } from "zod"; + +const templateDataBanjar = z.object({ + nama: z.string().min(1, "Nama banjar harus diisi"), + penduduk: z.number().min(0, "Jumlah penduduk harus diisi"), + kk: z.number().min(0, "Jumlah KK harus diisi"), + miskin: z.number().min(0, "Jumlah penduduk miskin harus diisi"), + tahun: z.number().min(2000, "Tahun harus diisi"), +}); + +const dataBanjar = proxy({ + create: { + form: { + nama: "", + penduduk: 0, + kk: 0, + miskin: 0, + tahun: new Date().getFullYear(), + }, + loading: false, + async create() { + const cek = templateDataBanjar.safeParse(dataBanjar.create.form); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + try { + dataBanjar.create.loading = true; + const res = await ApiFetch.api.kependudukan.databanjar["create"].post(dataBanjar.create.form); + + if (res.status === 200) { + const id = res.data?.data?.id; + if (id) { + toast.success("Sukses menambahkan data banjar"); + dataBanjar.create.form = { nama: "", penduduk: 0, kk: 0, miskin: 0, tahun: new Date().getFullYear() }; + dataBanjar.findMany.load(); + return id; + } + } + toast.error("Gagal menambahkan data"); + return null; + } catch (error) { + console.log((error as Error).message); + return null; + } finally { + dataBanjar.create.loading = false; + } + }, + }, + + findMany: { + data: null as any[] | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => { + dataBanjar.findMany.loading = true; + dataBanjar.findMany.page = page; + dataBanjar.findMany.search = search; + + try { + const query: any = { page, limit, tahun }; + if (search) query.search = search; + + const res = await ApiFetch.api.kependudukan.databanjar["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + dataBanjar.findMany.data = res.data.data ?? []; + dataBanjar.findMany.totalPages = res.data.totalPages ?? 1; + } else { + dataBanjar.findMany.data = []; + dataBanjar.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch data banjar paginated:", err); + dataBanjar.findMany.data = []; + dataBanjar.findMany.totalPages = 1; + } finally { + dataBanjar.findMany.loading = false; + } + }, + }, + + findUnique: { + data: null as any | null, + async load(id: string) { + try { + const res = await fetch(`/api/kependudukan/databanjar/${id}`); + if (res.ok) { + const data = await res.json(); + dataBanjar.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data banjar:", res.statusText); + dataBanjar.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data banjar:", error); + dataBanjar.findUnique.data = null; + } + }, + }, + + update: { + id: "", + form: { + nama: "", + penduduk: 0, + kk: 0, + miskin: 0, + tahun: new Date().getFullYear(), + }, + loading: false, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + const formData = { + nama: this.form.nama, + penduduk: this.form.penduduk, + kk: this.form.kk, + miskin: this.form.miskin, + tahun: this.form.tahun, + }; + + const cek = templateDataBanjar.safeParse(formData); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + + try { + this.loading = true; + const res = await fetch(`/api/kependudukan/databanjar/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const result = await res.json(); + + if (!res.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + + toast.success("Berhasil update data!"); + await dataBanjar.findMany.load(); + return result.data; + } catch (error) { + console.error("Update error:", error); + toast.error("Gagal update data banjar"); + throw error; + } finally { + this.loading = false; + } + }, + }, + + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + dataBanjar.delete.loading = true; + + const response = await fetch( + `/api/kependudukan/databanjar/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Data banjar berhasil dihapus"); + await dataBanjar.findMany.load(); + } else { + toast.error(result?.message || "Gagal menghapus data banjar"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus data banjar"); + } finally { + dataBanjar.delete.loading = false; + } + }, + }, +}); + +export default dataBanjar; diff --git a/src/app/admin/(dashboard)/_state/kependudukan/distribusi-agama.ts b/src/app/admin/(dashboard)/_state/kependudukan/distribusi-agama.ts new file mode 100644 index 00000000..0ba69e07 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kependudukan/distribusi-agama.ts @@ -0,0 +1,197 @@ +import ApiFetch from "@/lib/api-fetch"; +import { proxy } from "valtio"; +import { toast } from "react-toastify"; +import { z } from "zod"; + +const templateDistribusiAgama = z.object({ + agama: z.string().min(1, "Agama harus diisi"), + jumlah: z.number().min(0, "Jumlah harus diisi"), + tahun: z.number().min(2000, "Tahun harus diisi"), +}); + +const distribusiAgama = proxy({ + create: { + form: { + agama: "", + jumlah: 0, + tahun: new Date().getFullYear(), + }, + loading: false, + async create() { + const cek = templateDistribusiAgama.safeParse(distribusiAgama.create.form); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + try { + distribusiAgama.create.loading = true; + const res = await ApiFetch.api.kependudukan.distribusiagama["create"].post(distribusiAgama.create.form); + + if (res.status === 200) { + const id = res.data?.data?.id; + if (id) { + toast.success("Sukses menambahkan distribusi agama"); + distribusiAgama.create.form = { agama: "", jumlah: 0, tahun: new Date().getFullYear() }; + distribusiAgama.findMany.load(); + return id; + } + } + toast.error("Gagal menambahkan data"); + return null; + } catch (error) { + console.log((error as Error).message); + return null; + } finally { + distribusiAgama.create.loading = false; + } + }, + }, + + findMany: { + data: null as any[] | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => { + distribusiAgama.findMany.loading = true; + distribusiAgama.findMany.page = page; + distribusiAgama.findMany.search = search; + + try { + const query: any = { page, limit, tahun }; + if (search) query.search = search; + + const res = await ApiFetch.api.kependudukan.distribusiagama["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + distribusiAgama.findMany.data = res.data.data ?? []; + distribusiAgama.findMany.totalPages = res.data.totalPages ?? 1; + } else { + distribusiAgama.findMany.data = []; + distribusiAgama.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch distribusi agama paginated:", err); + distribusiAgama.findMany.data = []; + distribusiAgama.findMany.totalPages = 1; + } finally { + distribusiAgama.findMany.loading = false; + } + }, + }, + + findUnique: { + data: null as any | null, + async load(id: string) { + try { + const res = await fetch(`/api/kependudukan/distribusiagama/${id}`); + if (res.ok) { + const data = await res.json(); + distribusiAgama.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch distribusiAgama:", res.statusText); + distribusiAgama.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching distribusiAgama:", error); + distribusiAgama.findUnique.data = null; + } + }, + }, + + update: { + id: "", + form: { + agama: "", + jumlah: 0, + tahun: new Date().getFullYear(), + }, + loading: false, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + const formData = { + agama: this.form.agama, + jumlah: this.form.jumlah, + tahun: this.form.tahun, + }; + + const cek = templateDistribusiAgama.safeParse(formData); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + + try { + this.loading = true; + const res = await fetch(`/api/kependudukan/distribusiagama/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const result = await res.json(); + + if (!res.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + + toast.success("Berhasil update data!"); + await distribusiAgama.findMany.load(); + return result.data; + } catch (error) { + console.error("Update error:", error); + toast.error("Gagal update data distribusi agama"); + throw error; + } finally { + this.loading = false; + } + }, + }, + + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + distribusiAgama.delete.loading = true; + + const response = await fetch( + `/api/kependudukan/distribusiagama/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Distribusi agama berhasil dihapus"); + await distribusiAgama.findMany.load(); + } else { + toast.error(result?.message || "Gagal menghapus distribusi agama"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus distribusi agama"); + } finally { + distribusiAgama.delete.loading = false; + } + }, + }, +}); + +export default distribusiAgama; diff --git a/src/app/admin/(dashboard)/_state/kependudukan/distribusi-umur.ts b/src/app/admin/(dashboard)/_state/kependudukan/distribusi-umur.ts new file mode 100644 index 00000000..98752a3b --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kependudukan/distribusi-umur.ts @@ -0,0 +1,197 @@ +import ApiFetch from "@/lib/api-fetch"; +import { proxy } from "valtio"; +import { toast } from "react-toastify"; +import { z } from "zod"; + +const templateDistribusiUmur = z.object({ + rentangUmur: z.string().min(1, "Rentang umur harus diisi"), + jumlah: z.number().min(0, "Jumlah harus diisi"), + tahun: z.number().min(2000, "Tahun harus diisi"), +}); + +const distribusiUmur = proxy({ + create: { + form: { + rentangUmur: "", + jumlah: 0, + tahun: new Date().getFullYear(), + }, + loading: false, + async create() { + const cek = templateDistribusiUmur.safeParse(distribusiUmur.create.form); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + try { + distribusiUmur.create.loading = true; + const res = await ApiFetch.api.kependudukan.distribusiumur["create"].post(distribusiUmur.create.form); + + if (res.status === 200) { + const id = res.data?.data?.id; + if (id) { + toast.success("Sukses menambahkan distribusi umur"); + distribusiUmur.create.form = { rentangUmur: "", jumlah: 0, tahun: new Date().getFullYear() }; + distribusiUmur.findMany.load(); + return id; + } + } + toast.error("Gagal menambahkan data"); + return null; + } catch (error) { + console.log((error as Error).message); + return null; + } finally { + distribusiUmur.create.loading = false; + } + }, + }, + + findMany: { + data: null as any[] | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => { + distribusiUmur.findMany.loading = true; + distribusiUmur.findMany.page = page; + distribusiUmur.findMany.search = search; + + try { + const query: any = { page, limit, tahun }; + if (search) query.search = search; + + const res = await ApiFetch.api.kependudukan.distribusiumur["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + distribusiUmur.findMany.data = res.data.data ?? []; + distribusiUmur.findMany.totalPages = res.data.totalPages ?? 1; + } else { + distribusiUmur.findMany.data = []; + distribusiUmur.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch distribusi umur paginated:", err); + distribusiUmur.findMany.data = []; + distribusiUmur.findMany.totalPages = 1; + } finally { + distribusiUmur.findMany.loading = false; + } + }, + }, + + findUnique: { + data: null as any | null, + async load(id: string) { + try { + const res = await fetch(`/api/kependudukan/distribusiumur/${id}`); + if (res.ok) { + const data = await res.json(); + distribusiUmur.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch distribusi umur:", res.statusText); + distribusiUmur.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching distribusi umur:", error); + distribusiUmur.findUnique.data = null; + } + }, + }, + + update: { + id: "", + form: { + rentangUmur: "", + jumlah: 0, + tahun: new Date().getFullYear(), + }, + loading: false, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + const formData = { + rentangUmur: this.form.rentangUmur, + jumlah: this.form.jumlah, + tahun: this.form.tahun, + }; + + const cek = templateDistribusiUmur.safeParse(formData); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + + try { + this.loading = true; + const res = await fetch(`/api/kependudukan/distribusiumur/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const result = await res.json(); + + if (!res.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + + toast.success("Berhasil update data!"); + await distribusiUmur.findMany.load(); + return result.data; + } catch (error) { + console.error("Update error:", error); + toast.error("Gagal update data distribusi umur"); + throw error; + } finally { + this.loading = false; + } + }, + }, + + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + distribusiUmur.delete.loading = true; + + const response = await fetch( + `/api/kependudukan/distribusiumur/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Distribusi umur berhasil dihapus"); + await distribusiUmur.findMany.load(); + } else { + toast.error(result?.message || "Gagal menghapus distribusi umur"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus distribusi umur"); + } finally { + distribusiUmur.delete.loading = false; + } + }, + }, +}); + +export default distribusiUmur; diff --git a/src/app/admin/(dashboard)/_state/kependudukan/migrasi-penduduk.ts b/src/app/admin/(dashboard)/_state/kependudukan/migrasi-penduduk.ts new file mode 100644 index 00000000..30d8bc91 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kependudukan/migrasi-penduduk.ts @@ -0,0 +1,209 @@ +import ApiFetch from "@/lib/api-fetch"; +import { proxy } from "valtio"; +import { toast } from "react-toastify"; +import { z } from "zod"; + +const templateMigrasiPenduduk = z.object({ + jenis: z.string().min(1, "Jenis migrasi harus diisi"), + nama: z.string().min(1, "Nama harus diisi"), + tanggal: z.string().min(1, "Tanggal harus diisi"), + asalTujuan: z.string().min(1, "Asal/Tujuan harus diisi"), + alasan: z.string().optional(), + jenisKelamin: z.string().optional(), +}); + +const migrasiPenduduk = proxy({ + create: { + form: { + jenis: "", + nama: "", + tanggal: "", + asalTujuan: "", + alasan: "", + jenisKelamin: "", + }, + loading: false, + async create() { + const cek = templateMigrasiPenduduk.safeParse(migrasiPenduduk.create.form); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + try { + migrasiPenduduk.create.loading = true; + const res = await ApiFetch.api.kependudukan.migrasipenduduk["create"].post(migrasiPenduduk.create.form); + + if (res.status === 200) { + const id = res.data?.data?.id; + if (id) { + toast.success("Sukses menambahkan data migrasi penduduk"); + migrasiPenduduk.create.form = { jenis: "", nama: "", tanggal: "", asalTujuan: "", alasan: "", jenisKelamin: "" }; + migrasiPenduduk.findMany.load(); + return id; + } + } + toast.error("Gagal menambahkan data"); + return null; + } catch (error) { + console.log((error as Error).message); + return null; + } finally { + migrasiPenduduk.create.loading = false; + } + }, + }, + + findMany: { + data: null as any[] | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "", tahun = new Date().getFullYear()) => { + migrasiPenduduk.findMany.loading = true; + migrasiPenduduk.findMany.page = page; + migrasiPenduduk.findMany.search = search; + + try { + const query: any = { page, limit, tahun }; + if (search) query.search = search; + + const res = await ApiFetch.api.kependudukan.migrasipenduduk["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + migrasiPenduduk.findMany.data = res.data.data ?? []; + migrasiPenduduk.findMany.totalPages = res.data.totalPages ?? 1; + } else { + migrasiPenduduk.findMany.data = []; + migrasiPenduduk.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch migrasi penduduk paginated:", err); + migrasiPenduduk.findMany.data = []; + migrasiPenduduk.findMany.totalPages = 1; + } finally { + migrasiPenduduk.findMany.loading = false; + } + }, + }, + + findUnique: { + data: null as any | null, + async load(id: string) { + try { + const res = await fetch(`/api/kependudukan/migrasipenduduk/${id}`); + if (res.ok) { + const data = await res.json(); + migrasiPenduduk.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch migrasi penduduk:", res.statusText); + migrasiPenduduk.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching migrasi penduduk:", error); + migrasiPenduduk.findUnique.data = null; + } + }, + }, + + update: { + id: "", + form: { + jenis: "", + nama: "", + tanggal: "", + asalTujuan: "", + alasan: "", + jenisKelamin: "", + }, + loading: false, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + const formData = { + jenis: this.form.jenis, + nama: this.form.nama, + tanggal: this.form.tanggal, + asalTujuan: this.form.asalTujuan, + alasan: this.form.alasan, + jenisKelamin: this.form.jenisKelamin, + }; + + const cek = templateMigrasiPenduduk.safeParse(formData); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + + try { + this.loading = true; + const res = await fetch(`/api/kependudukan/migrasipenduduk/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const result = await res.json(); + + if (!res.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + + toast.success("Berhasil update data!"); + await migrasiPenduduk.findMany.load(); + return result.data; + } catch (error) { + console.error("Update error:", error); + toast.error("Gagal update data migrasi penduduk"); + throw error; + } finally { + this.loading = false; + } + }, + }, + + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + migrasiPenduduk.delete.loading = true; + + const response = await fetch( + `/api/kependudukan/migrasipenduduk/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Data migrasi penduduk berhasil dihapus"); + await migrasiPenduduk.findMany.load(); + } else { + toast.error(result?.message || "Gagal menghapus data migrasi penduduk"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus data migrasi penduduk"); + } finally { + migrasiPenduduk.delete.loading = false; + } + }, + }, +}); + +export default migrasiPenduduk; diff --git a/src/app/admin/(dashboard)/kependudukan/data-banjar/[id]/page.tsx b/src/app/admin/(dashboard)/kependudukan/data-banjar/[id]/page.tsx new file mode 100644 index 00000000..2bfbffa1 --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/data-banjar/[id]/page.tsx @@ -0,0 +1,249 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + Title, + NumberInput, + TextInput +} from '@mantine/core'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; +import dataBanjar from '../../../_state/kependudukan/data-banjar'; + +interface FormData { + nama: string; + penduduk: number; + kk: number; + miskin: number; + tahun: number; +} + +export default function EditDataBanjar() { + const router = useRouter(); + const { id } = useParams() as { id: string }; + const stateDataBanjar = useProxy(dataBanjar); + const [isSubmitting, setIsSubmitting] = useState(false); + const [formData, setFormData] = useState({ + nama: '', + penduduk: 0, + kk: 0, + miskin: 0, + tahun: new Date().getFullYear(), + }); + const [originalData, setOriginalData] = useState({ + nama: '', + penduduk: 0, + kk: 0, + miskin: 0, + tahun: new Date().getFullYear(), + }); + + const currentYear = new Date().getFullYear(); + + const isFormValid = () => { + return ( + formData.nama?.trim() !== '' && + formData.penduduk !== null && + formData.penduduk >= 0 && + formData.kk !== null && + formData.kk >= 0 && + formData.miskin !== null && + formData.miskin >= 0 && + formData.tahun !== null + ); + }; + + useEffect(() => { + if (!id) return; + + const loadData = async () => { + try { + setIsSubmitting(true); + stateDataBanjar.update.id = id; + await stateDataBanjar.findUnique.load(id); + + const data = stateDataBanjar.findUnique.data; + if (data) { + setFormData({ + nama: data.nama ?? '', + penduduk: Number(data.penduduk ?? 0), + kk: Number(data.kk ?? 0), + miskin: Number(data.miskin ?? 0), + tahun: Number(data.tahun ?? currentYear), + }); + setOriginalData({ + nama: data.nama ?? '', + penduduk: Number(data.penduduk ?? 0), + kk: Number(data.kk ?? 0), + miskin: Number(data.miskin ?? 0), + tahun: Number(data.tahun ?? currentYear), + }); + } + } catch (error) { + console.error('Error loading data:', error); + toast.error('Gagal memuat data'); + } finally { + setIsSubmitting(false); + } + }; + + loadData(); + }, [id]); + + const handleChange = useCallback( + (field: keyof FormData) => + (value: any) => { + const val = + field === 'penduduk' || field === 'kk' || field === 'miskin' || field === 'tahun' + ? Number(value || 0) + : value; + + setFormData((prev) => ({ ...prev, [field]: val })); + }, + [] + ); + + const handleResetForm = () => { + setFormData({ + nama: originalData.nama, + penduduk: Number(originalData.penduduk), + kk: Number(originalData.kk), + miskin: Number(originalData.miskin), + tahun: Number(originalData.tahun), + }); + toast.info("Form dikembalikan ke data awal"); + }; + + const handleSubmit = async () => { + try { + setIsSubmitting(true); + stateDataBanjar.update.id = id; + stateDataBanjar.update.form = { ...formData }; + + await stateDataBanjar.update.submit(); + + toast.success('Data berhasil diperbarui'); + router.push('/admin/kependudukan/data-banjar'); + } catch (error) { + console.error('Error updating data:', error); + toast.error('Gagal memperbarui data'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Edit Data Banjar + + + + {/* Form Card */} + + + + + + + + + + + + + + + + {/* Tombol Simpan */} + + + + + + ); +} diff --git a/src/app/admin/(dashboard)/kependudukan/data-banjar/create/page.tsx b/src/app/admin/(dashboard)/kependudukan/data-banjar/create/page.tsx new file mode 100644 index 00000000..64e8584f --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/data-banjar/create/page.tsx @@ -0,0 +1,189 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + Title, + NumberInput, + TextInput +} from '@mantine/core'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import dataBanjar from '../../../_state/kependudukan/data-banjar'; +import { toast } from 'react-toastify'; + +function CreateDataBanjar() { + const stateDataBanjar = useProxy(dataBanjar); + const router = useRouter(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const currentYear = new Date().getFullYear(); + const yearOptions = Array.from({ length: 10 }, (_, i) => ({ + value: String(currentYear - i), + label: String(currentYear - i), + })); + + const isFormValid = () => { + return ( + stateDataBanjar.create.form.nama?.trim() !== '' && + stateDataBanjar.create.form.penduduk !== null && + stateDataBanjar.create.form.penduduk >= 0 && + stateDataBanjar.create.form.kk !== null && + stateDataBanjar.create.form.kk >= 0 && + stateDataBanjar.create.form.miskin !== null && + stateDataBanjar.create.form.miskin >= 0 && + stateDataBanjar.create.form.tahun !== null + ); + }; + + const resetForm = () => { + stateDataBanjar.create.form = { + nama: '', + penduduk: 0, + kk: 0, + miskin: 0, + tahun: currentYear, + }; + }; + + const handleSubmit = async () => { + try { + const id = await stateDataBanjar.create.create(); + if (id) { + resetForm(); + router.push('/admin/kependudukan/data-banjar'); + } + } catch (error) { + console.error('Error creating data banjar:', error); + toast.error('Terjadi kesalahan saat menambah data banjar'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Tambah Data Banjar + + + + {/* Form */} + + + { + stateDataBanjar.create.form.nama = e.currentTarget.value; + }} + required + /> + + { + stateDataBanjar.create.form.penduduk = Number(val || 0); + }} + min={0} + required + /> + + { + stateDataBanjar.create.form.kk = Number(val || 0); + }} + min={0} + required + /> + + { + stateDataBanjar.create.form.miskin = Number(val || 0); + }} + min={0} + required + /> + + { + stateDataBanjar.create.form.tahun = Number(val || currentYear); + }} + min={2000} + max={currentYear + 1} + required + /> + + + + + {/* Tombol Simpan */} + + + + + + ); +} + +export default CreateDataBanjar; diff --git a/src/app/admin/(dashboard)/kependudukan/data-banjar/page.tsx b/src/app/admin/(dashboard)/kependudukan/data-banjar/page.tsx new file mode 100644 index 00000000..f25f6c62 --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/data-banjar/page.tsx @@ -0,0 +1,304 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client' +import colors from '@/con/colors'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title +} from '@mantine/core'; +import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; +import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../_com/header'; +import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; +import dataBanjar from '../../_state/kependudukan/data-banjar'; + +function DataBanjarAdmin() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e: React.ChangeEvent) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListDataBanjar({ search }: { search: string }) { + type DataBanjarType = { + id: string; + nama: string; + penduduk: number; + kk: number; + miskin: number; + tahun: number; + }; + + const router = useRouter(); + const stateDataBanjar = useProxy(dataBanjar); + const [modalHapus, setModalHapus] = useState(false); + const [debouncedSearch] = useDebouncedValue(search, 1000); + const [selectedId, setSelectedId] = useState(null); + + const { + data, + page, + totalPages, + loading, + load, + } = stateDataBanjar.findMany; + + const handleDelete = () => { + if (selectedId) { + stateDataBanjar.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + } + }; + + useShallowEffect(() => { + load(page, 10, debouncedSearch); + }, [page, debouncedSearch]); + + const filteredData = data || []; + + if (loading || !data) { + return ( + + + + ); + } + + return ( + + + + + List Data Banjar + + + + + {/* Desktop Table */} + + + + + Nama Banjar + Penduduk + KK + Miskin + Tahun + Edit + Hapus + + + + {filteredData.length > 0 ? ( + filteredData.map((item: DataBanjarType) => ( + + {item.nama} + {item.penduduk.toLocaleString('id-ID')} + {item.kk.toLocaleString('id-ID')} + {item.miskin.toLocaleString('id-ID')} + {item.tahun} + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data banjar yang cocok + +
+
+
+ )} +
+
+
+ + {/* Mobile Card */} + + + {filteredData.length > 0 ? ( + filteredData.map((item: DataBanjarType) => ( + + + + + Nama Banjar + + + {item.nama} + + + + + Jumlah Penduduk + + + {item.penduduk.toLocaleString('id-ID')} + + + + + KK + + + {item.kk.toLocaleString('id-ID')} + + + + + Penduduk Miskin + + + {item.miskin.toLocaleString('id-ID')} + + + + + Tahun + + + {item.tahun} + + + + + + + + + )) + ) : ( +
+ + Tidak ada data banjar yang cocok + +
+ )} +
+
+
+ + {/* Pagination */} +
+ { + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+ + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleDelete} + text="Apakah anda yakin ingin menghapus data banjar ini?" + /> +
+ ); +} + +export default DataBanjarAdmin; diff --git a/src/app/admin/(dashboard)/kependudukan/distribusi-agama/[id]/page.tsx b/src/app/admin/(dashboard)/kependudukan/distribusi-agama/[id]/page.tsx new file mode 100644 index 00000000..f382d689 --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/distribusi-agama/[id]/page.tsx @@ -0,0 +1,232 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + TextInput, + Title, + NumberInput, + Select +} from '@mantine/core'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; +import distribusiAgama from '../../../_state/kependudukan/distribusi-agama'; + +interface FormData { + agama: string; + jumlah: number; + tahun: number; +} + +export default function EditDistribusiAgama() { + const router = useRouter(); + const { id } = useParams() as { id: string }; + const stateDistribusiAgama = useProxy(distribusiAgama); + const [isSubmitting, setIsSubmitting] = useState(false); + const [formData, setFormData] = useState({ + agama: '', + jumlah: 0, + tahun: new Date().getFullYear(), + }); + const [originalData, setOriginalData] = useState({ + agama: '', + jumlah: 0, + tahun: new Date().getFullYear(), + }); + + const currentYear = new Date().getFullYear(); + const yearOptions = Array.from({ length: 10 }, (_, i) => ({ + value: String(currentYear - i), + label: String(currentYear - i), + })); + + const agamaOptions = [ + { value: 'HINDU', label: 'Hindu' }, + { value: 'ISLAM', label: 'Islam' }, + { value: 'KRISTEN', label: 'Kristen' }, + { value: 'KRISTEN_PROTESTAN', label: 'Kristen Protestan' }, + { value: 'KRISTEN_KATOLIK', label: 'Kristen Katolik' }, + { value: 'BUDDHA', label: 'Buddha' }, + { value: 'KONGHUCU', label: 'Konghucu' }, + { value: 'LAINNYA', label: 'Lainnya' }, + ]; + + const isFormValid = () => { + return ( + formData.agama?.trim() !== '' && + formData.jumlah !== null && + formData.jumlah >= 0 && + formData.tahun !== null + ); + }; + + useEffect(() => { + if (!id) return; + + const loadData = async () => { + try { + setIsSubmitting(true); + stateDistribusiAgama.update.id = id; + await stateDistribusiAgama.findUnique.load(id); + + const data = stateDistribusiAgama.findUnique.data; + if (data) { + setFormData({ + agama: data.agama ?? '', + jumlah: Number(data.jumlah ?? 0), + tahun: Number(data.tahun ?? currentYear), + }); + setOriginalData({ + agama: data.agama ?? '', + jumlah: Number(data.jumlah ?? 0), + tahun: Number(data.tahun ?? currentYear), + }); + } + } catch (error) { + console.error('Error loading data:', error); + toast.error('Gagal memuat data'); + } finally { + setIsSubmitting(false); + } + }; + + loadData(); + }, [id]); + + const handleChange = useCallback( + (field: keyof FormData) => + (value: any) => { + const val = + field === 'jumlah' || field === 'tahun' + ? Number(value || 0) + : value; + + setFormData((prev) => ({ ...prev, [field]: val })); + }, + [] + ); + + const handleResetForm = () => { + setFormData({ + agama: originalData.agama, + jumlah: Number(originalData.jumlah), + tahun: Number(originalData.tahun), + }); + toast.info("Form dikembalikan ke data awal"); + }; + + const handleSubmit = async () => { + try { + setIsSubmitting(true); + stateDistribusiAgama.update.id = id; + stateDistribusiAgama.update.form = { ...formData }; + + await stateDistribusiAgama.update.submit(); + + toast.success('Data berhasil diperbarui'); + router.push('/admin/kependudukan/distribusi-agama'); + } catch (error) { + console.error('Error updating data:', error); + toast.error('Gagal memperbarui data'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Edit Distribusi Agama + + + + {/* Form Card */} + + + + + + + + {/* Tombol Simpan */} + + + + + + ); +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/kependudukan/distribusi-agama/create/page.tsx b/src/app/admin/(dashboard)/kependudukan/distribusi-agama/create/page.tsx new file mode 100644 index 00000000..928d281d --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/distribusi-agama/create/page.tsx @@ -0,0 +1,174 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + TextInput, + Title, + NumberInput, + Select +} from '@mantine/core'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import distribusiAgama from '../../../_state/kependudukan/distribusi-agama'; +import { toast } from 'react-toastify'; + +function CreateDistribusiAgama() { + const stateDistribusiAgama = useProxy(distribusiAgama); + const router = useRouter(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const agamaOptions = [ + { value: 'HINDU', label: 'Hindu' }, + { value: 'ISLAM', label: 'Islam' }, + { value: 'KRISTEN', label: 'Kristen' }, + { value: 'KRISTEN_PROTESTAN', label: 'Kristen Protestan' }, + { value: 'KRISTEN_KATOLIK', label: 'Kristen Katolik' }, + { value: 'BUDDHA', label: 'Buddha' }, + { value: 'KONGHUCU', label: 'Konghucu' }, + { value: 'LAINNYA', label: 'Lainnya' }, + ]; + + const currentYear = new Date().getFullYear(); + const yearOptions = Array.from({ length: 10 }, (_, i) => ({ + value: String(currentYear - i), + label: String(currentYear - i), + })); + + const isFormValid = () => { + return ( + stateDistribusiAgama.create.form.agama?.trim() !== '' && + stateDistribusiAgama.create.form.jumlah !== null && + stateDistribusiAgama.create.form.jumlah >= 0 && + stateDistribusiAgama.create.form.tahun !== null + ); + }; + + const resetForm = () => { + stateDistribusiAgama.create.form = { + agama: '', + jumlah: 0, + tahun: currentYear, + }; + }; + + const handleSubmit = async () => { + try { + const id = await stateDistribusiAgama.create.create(); + if (id) { + resetForm(); + router.push('/admin/kependudukan/distribusi-agama'); + } + } catch (error) { + console.error('Error creating distribusi agama:', error); + toast.error('Terjadi kesalahan saat menambah distribusi agama'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Tambah Distribusi Agama + + + + {/* Form */} + + + { + stateDistribusiAgama.create.form.tahun = Number(val || currentYear); + }} + required + /> + + + + + {/* Tombol Simpan */} + + + + + + ); +} + +export default CreateDistribusiAgama; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/kependudukan/distribusi-agama/page.tsx b/src/app/admin/(dashboard)/kependudukan/distribusi-agama/page.tsx new file mode 100644 index 00000000..d9037cf1 --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/distribusi-agama/page.tsx @@ -0,0 +1,283 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client' +import colors from '@/con/colors'; +import { + Box, + Button, + Center, + Flex, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title +} from '@mantine/core'; +import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; +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 HeaderSearch from '../../_com/header'; +import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; +import distribusiAgama from '../../_state/kependudukan/distribusi-agama'; + +function DistribusiAgamaAdmin() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e: React.ChangeEvent) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListDistribusiAgama({ search }: { search: string }) { + type DistribusiAgamaType = { + id: string; + agama: string; + jumlah: number; + tahun: number; + }; + + const router = useRouter(); + const stateDistribusiAgama = useProxy(distribusiAgama); + const [modalHapus, setModalHapus] = useState(false); + const [debouncedSearch] = useDebouncedValue(search, 1000); + const [selectedId, setSelectedId] = useState(null); + + const { + data, + page, + totalPages, + loading, + load, + } = stateDistribusiAgama.findMany; + + const handleDelete = () => { + if (selectedId) { + stateDistribusiAgama.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + } + }; + + useShallowEffect(() => { + load(page, 10, debouncedSearch); + }, [page, debouncedSearch]); + + const filteredData = data || []; + + if (loading || !data) { + return ( + + + + ); + } + + return ( + + + + + List Distribusi Agama + + + + + {/* Desktop Table */} + + + + + Agama + Jumlah + Tahun + Edit + Hapus + + + + {filteredData.length > 0 ? ( + filteredData.map((item: DistribusiAgamaType) => ( + + {item.agama} + {item.jumlah.toLocaleString('id-ID')} + {item.tahun} + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data distribusi agama yang cocok + +
+
+
+ )} +
+
+
+ + {/* Mobile Card */} + + + {filteredData.length > 0 ? ( + filteredData.map((item: DistribusiAgamaType) => ( + + + + + Agama + + + {item.agama} + + + + + Jumlah + + + {item.jumlah.toLocaleString('id-ID')} + + + + + Tahun + + + {item.tahun} + + + + + + + + + )) + ) : ( +
+ + Tidak ada data distribusi agama yang cocok + +
+ )} +
+
+
+ + {/* Pagination */} +
+ { + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+ + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleDelete} + text="Apakah anda yakin ingin menghapus data distribusi agama ini?" + /> +
+ ); +} + +export default DistribusiAgamaAdmin; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/kependudukan/distribusi-umur/[id]/page.tsx b/src/app/admin/(dashboard)/kependudukan/distribusi-umur/[id]/page.tsx new file mode 100644 index 00000000..26f32af3 --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/distribusi-umur/[id]/page.tsx @@ -0,0 +1,232 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + Title, + NumberInput, + Select +} from '@mantine/core'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; +import distribusiUmur from '../../../_state/kependudukan/distribusi-umur'; + +interface FormData { + rentangUmur: string; + jumlah: number; + tahun: number; +} + +export default function EditDistribusiUmur() { + const router = useRouter(); + const { id } = useParams() as { id: string }; + const stateDistribusiUmur = useProxy(distribusiUmur); + const [isSubmitting, setIsSubmitting] = useState(false); + const [formData, setFormData] = useState({ + rentangUmur: '', + jumlah: 0, + tahun: new Date().getFullYear(), + }); + const [originalData, setOriginalData] = useState({ + rentangUmur: '', + jumlah: 0, + tahun: new Date().getFullYear(), + }); + + const currentYear = new Date().getFullYear(); + const yearOptions = Array.from({ length: 10 }, (_, i) => ({ + value: String(currentYear - i), + label: String(currentYear - i), + })); + + const rentangUmurOptions = [ + { value: '0-5', label: '0-5 Tahun' }, + { value: '6-12', label: '6-12 Tahun' }, + { value: '13-17', label: '13-17 Tahun' }, + { value: '18-25', label: '18-25 Tahun' }, + { value: '26-35', label: '26-35 Tahun' }, + { value: '36-45', label: '36-45 Tahun' }, + { value: '46-55', label: '46-55 Tahun' }, + { value: '56-65', label: '56-65 Tahun' }, + { value: '65+', label: '65+ Tahun' }, + ]; + + const isFormValid = () => { + return ( + formData.rentangUmur?.trim() !== '' && + formData.jumlah !== null && + formData.jumlah >= 0 && + formData.tahun !== null + ); + }; + + useEffect(() => { + if (!id) return; + + const loadData = async () => { + try { + setIsSubmitting(true); + stateDistribusiUmur.update.id = id; + await stateDistribusiUmur.findUnique.load(id); + + const data = stateDistribusiUmur.findUnique.data; + if (data) { + setFormData({ + rentangUmur: data.rentangUmur ?? '', + jumlah: Number(data.jumlah ?? 0), + tahun: Number(data.tahun ?? currentYear), + }); + setOriginalData({ + rentangUmur: data.rentangUmur ?? '', + jumlah: Number(data.jumlah ?? 0), + tahun: Number(data.tahun ?? currentYear), + }); + } + } catch (error) { + console.error('Error loading data:', error); + toast.error('Gagal memuat data'); + } finally { + setIsSubmitting(false); + } + }; + + loadData(); + }, [id]); + + const handleChange = useCallback( + (field: keyof FormData) => + (value: any) => { + const val = + field === 'jumlah' || field === 'tahun' + ? Number(value || 0) + : value; + + setFormData((prev) => ({ ...prev, [field]: val })); + }, + [] + ); + + const handleResetForm = () => { + setFormData({ + rentangUmur: originalData.rentangUmur, + jumlah: Number(originalData.jumlah), + tahun: Number(originalData.tahun), + }); + toast.info("Form dikembalikan ke data awal"); + }; + + const handleSubmit = async () => { + try { + setIsSubmitting(true); + stateDistribusiUmur.update.id = id; + stateDistribusiUmur.update.form = { ...formData }; + + await stateDistribusiUmur.update.submit(); + + toast.success('Data berhasil diperbarui'); + router.push('/admin/kependudukan/distribusi-umur'); + } catch (error) { + console.error('Error updating data:', error); + toast.error('Gagal memperbarui data'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Edit Distribusi Umur + + + + {/* Form Card */} + + + + + + + + {/* Tombol Simpan */} + + + + + + ); +} diff --git a/src/app/admin/(dashboard)/kependudukan/distribusi-umur/create/page.tsx b/src/app/admin/(dashboard)/kependudukan/distribusi-umur/create/page.tsx new file mode 100644 index 00000000..cfa3abd2 --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/distribusi-umur/create/page.tsx @@ -0,0 +1,174 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + Title, + NumberInput, + Select +} from '@mantine/core'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import distribusiUmur from '../../../_state/kependudukan/distribusi-umur'; +import { toast } from 'react-toastify'; + +function CreateDistribusiUmur() { + const stateDistribusiUmur = useProxy(distribusiUmur); + const router = useRouter(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const rentangUmurOptions = [ + { value: '0-5', label: '0-5 Tahun' }, + { value: '6-12', label: '6-12 Tahun' }, + { value: '13-17', label: '13-17 Tahun' }, + { value: '18-25', label: '18-25 Tahun' }, + { value: '26-35', label: '26-35 Tahun' }, + { value: '36-45', label: '36-45 Tahun' }, + { value: '46-55', label: '46-55 Tahun' }, + { value: '56-65', label: '56-65 Tahun' }, + { value: '65+', label: '65+ Tahun' }, + ]; + + const currentYear = new Date().getFullYear(); + const yearOptions = Array.from({ length: 10 }, (_, i) => ({ + value: String(currentYear - i), + label: String(currentYear - i), + })); + + const isFormValid = () => { + return ( + stateDistribusiUmur.create.form.rentangUmur?.trim() !== '' && + stateDistribusiUmur.create.form.jumlah !== null && + stateDistribusiUmur.create.form.jumlah >= 0 && + stateDistribusiUmur.create.form.tahun !== null + ); + }; + + const resetForm = () => { + stateDistribusiUmur.create.form = { + rentangUmur: '', + jumlah: 0, + tahun: currentYear, + }; + }; + + const handleSubmit = async () => { + try { + const id = await stateDistribusiUmur.create.create(); + if (id) { + resetForm(); + router.push('/admin/kependudukan/distribusi-umur'); + } + } catch (error) { + console.error('Error creating distribusi umur:', error); + toast.error('Terjadi kesalahan saat menambah distribusi umur'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Tambah Distribusi Umur + + + + {/* Form */} + + + { + stateDistribusiUmur.create.form.tahun = Number(val || currentYear); + }} + required + /> + + + + + {/* Tombol Simpan */} + + + + + + ); +} + +export default CreateDistribusiUmur; diff --git a/src/app/admin/(dashboard)/kependudukan/distribusi-umur/page.tsx b/src/app/admin/(dashboard)/kependudukan/distribusi-umur/page.tsx new file mode 100644 index 00000000..dad6483e --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/distribusi-umur/page.tsx @@ -0,0 +1,284 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client' +import colors from '@/con/colors'; +import { + Box, + Button, + Center, + Flex, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title +} from '@mantine/core'; +import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; +import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../_com/header'; +import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; +import distribusiUmur from '../../_state/kependudukan/distribusi-umur'; + +function DistribusiUmurAdmin() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e: React.ChangeEvent) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListDistribusiUmur({ search }: { search: string }) { + type DistribusiUmurType = { + id: string; + rentangUmur: string; + jumlah: number; + tahun: number; + }; + + const router = useRouter(); + const stateDistribusiUmur = useProxy(distribusiUmur); + const [modalHapus, setModalHapus] = useState(false); + const [debouncedSearch] = useDebouncedValue(search, 1000); + const [selectedId, setSelectedId] = useState(null); + + + const { + data, + page, + totalPages, + loading, + load, + } = stateDistribusiUmur.findMany; + + const handleDelete = () => { + if (selectedId) { + stateDistribusiUmur.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + } + }; + + useShallowEffect(() => { + load(page, 10, debouncedSearch); + }, [page, debouncedSearch]); + + const filteredData = data || []; + + if (loading || !data) { + return ( + + + + ); + } + + return ( + + + + + List Distribusi Umur + + + + + {/* Desktop Table */} + + + + + Rentang Umur + Jumlah + Tahun + Edit + Hapus + + + + {filteredData.length > 0 ? ( + filteredData.map((item: DistribusiUmurType) => ( + + {item.rentangUmur} + {item.jumlah.toLocaleString('id-ID')} + {item.tahun} + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data distribusi umur yang cocok + +
+
+
+ )} +
+
+
+ + {/* Mobile Card */} + + + {filteredData.length > 0 ? ( + filteredData.map((item: DistribusiUmurType) => ( + + + + + Rentang Umur + + + {item.rentangUmur} + + + + + Jumlah + + + {item.jumlah.toLocaleString('id-ID')} + + + + + Tahun + + + {item.tahun} + + + + + + + + + )) + ) : ( +
+ + Tidak ada data distribusi umur yang cocok + +
+ )} +
+
+
+ + {/* Pagination */} +
+ { + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+ + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleDelete} + text="Apakah anda yakin ingin menghapus data distribusi umur ini?" + /> +
+ ); +} + +export default DistribusiUmurAdmin; diff --git a/src/app/admin/(dashboard)/kependudukan/migrasi-penduduk/[id]/page.tsx b/src/app/admin/(dashboard)/kependudukan/migrasi-penduduk/[id]/page.tsx new file mode 100644 index 00000000..2618bfcb --- /dev/null +++ b/src/app/admin/(dashboard)/kependudukan/migrasi-penduduk/[id]/page.tsx @@ -0,0 +1,267 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client'; + +import colors from '@/con/colors'; +import { + Box, + Button, + Group, + Loader, + Paper, + Stack, + Title, + TextInput, + Select, + Textarea +} from '@mantine/core'; +import { DatePickerInput } from '@mantine/dates'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; +import migrasiPenduduk from '../../../_state/kependudukan/migrasi-penduduk'; + +interface FormData { + jenis: string; + nama: string; + tanggal: string; + asalTujuan: string; + alasan: string; + jenisKelamin: string; +} + +export default function EditMigrasiPenduduk() { + const router = useRouter(); + const { id } = useParams() as { id: string }; + const stateMigrasiPenduduk = useProxy(migrasiPenduduk); + const [isSubmitting, setIsSubmitting] = useState(false); + const [formData, setFormData] = useState({ + jenis: '', + nama: '', + tanggal: '', + asalTujuan: '', + alasan: '', + jenisKelamin: '', + }); + const [originalData, setOriginalData] = useState({ + jenis: '', + nama: '', + tanggal: '', + asalTujuan: '', + alasan: '', + jenisKelamin: '', + }); + + const jenisOptions = [ + { value: 'MASUK', label: 'Masuk' }, + { value: 'KELUAR', label: 'Keluar' }, + ]; + + const jenisKelaminOptions = [ + { value: 'L', label: 'Laki-laki' }, + { value: 'P', label: 'Perempuan' }, + ]; + + const isFormValid = () => { + return ( + formData.jenis?.trim() !== '' && + formData.nama?.trim() !== '' && + formData.tanggal?.trim() !== '' && + formData.asalTujuan?.trim() !== '' + ); + }; + + useEffect(() => { + if (!id) return; + + const loadData = async () => { + try { + setIsSubmitting(true); + stateMigrasiPenduduk.update.id = id; + await stateMigrasiPenduduk.findUnique.load(id); + + const data = stateMigrasiPenduduk.findUnique.data; + if (data) { + setFormData({ + jenis: data.jenis ?? '', + nama: data.nama ?? '', + tanggal: data.tanggal ?? '', + asalTujuan: data.asalTujuan ?? '', + alasan: data.alasan ?? '', + jenisKelamin: data.jenisKelamin ?? '', + }); + setOriginalData({ + jenis: data.jenis ?? '', + nama: data.nama ?? '', + tanggal: data.tanggal ?? '', + asalTujuan: data.asalTujuan ?? '', + alasan: data.alasan ?? '', + jenisKelamin: data.jenisKelamin ?? '', + }); + } + } catch (error) { + console.error('Error loading data:', error); + toast.error('Gagal memuat data'); + } finally { + setIsSubmitting(false); + } + }; + + loadData(); + }, [id]); + + const handleChange = useCallback( + (field: keyof FormData) => + (value: any) => { + const val = value || ''; + setFormData((prev) => ({ ...prev, [field]: val })); + }, + [] + ); + + const handleResetForm = () => { + setFormData({ + jenis: originalData.jenis, + nama: originalData.nama, + tanggal: originalData.tanggal, + asalTujuan: originalData.asalTujuan, + alasan: originalData.alasan, + jenisKelamin: originalData.jenisKelamin, + }); + toast.info("Form dikembalikan ke data awal"); + }; + + const handleSubmit = async () => { + try { + setIsSubmitting(true); + stateMigrasiPenduduk.update.id = id; + stateMigrasiPenduduk.update.form = { ...formData }; + + await stateMigrasiPenduduk.update.submit(); + + toast.success('Data berhasil diperbarui'); + router.push('/admin/kependudukan/migrasi-penduduk'); + } catch (error) { + console.error('Error updating data:', error); + toast.error('Gagal memperbarui data'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {/* Header */} + + + + Edit Migrasi Penduduk + + + + {/* Form Card */} + + +