diff --git a/prisma/data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json b/prisma/data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json index f55abd7f..f64b6977 100644 --- a/prisma/data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json +++ b/prisma/data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json @@ -1,51 +1,99 @@ [ - { - "month": "Jan", - "year": 2025, - "totalUnemployment": 160, - "educatedUnemployment": 95, - "uneducatedUnemployment": 65, - "percentageChange": null - }, - { - "month": "Feb", - "year": 2025, - "totalUnemployment": 155, - "educatedUnemployment": 90, - "uneducatedUnemployment": 65, - "percentageChange": -3.1 - }, - { - "month": "Mar", - "year": 2025, - "totalUnemployment": 150, - "educatedUnemployment": 88, - "uneducatedUnemployment": 62, - "percentageChange": -3.2 - }, - { - "month": "Apr", - "year": 2025, - "totalUnemployment": 148, - "educatedUnemployment": 85, - "uneducatedUnemployment": 63, - "percentageChange": -1.3 - }, - { - "month": "Mei", - "year": 2025, - "totalUnemployment": 145, - "educatedUnemployment": 82, - "uneducatedUnemployment": 63, - "percentageChange": -2.0 - }, - { - "month": "Jun", - "year": 2025, - "totalUnemployment": 140, - "educatedUnemployment": 80, - "uneducatedUnemployment": 60, - "percentageChange": -3.4 - } - ] + { + "month": "Jan", + "year": 2025, + "totalUnemployment": 160, + "educatedUnemployment": 95, + "uneducatedUnemployment": 65, + "percentageChange": 0.0 + }, + { + "month": "Feb", + "year": 2025, + "totalUnemployment": 158, + "educatedUnemployment": 93, + "uneducatedUnemployment": 65, + "percentageChange": -1.25 + }, + { + "month": "Mar", + "year": 2025, + "totalUnemployment": 155, + "educatedUnemployment": 91, + "uneducatedUnemployment": 64, + "percentageChange": -1.90 + }, + { + "month": "Apr", + "year": 2025, + "totalUnemployment": 152, + "educatedUnemployment": 89, + "uneducatedUnemployment": 63, + "percentageChange": -1.94 + }, + { + "month": "Mei", + "year": 2025, + "totalUnemployment": 150, + "educatedUnemployment": 88, + "uneducatedUnemployment": 62, + "percentageChange": -1.32 + }, + { + "month": "Jun", + "year": 2025, + "totalUnemployment": 148, + "educatedUnemployment": 87, + "uneducatedUnemployment": 61, + "percentageChange": -1.33 + }, + { + "month": "Jul", + "year": 2025, + "totalUnemployment": 145, + "educatedUnemployment": 85, + "uneducatedUnemployment": 60, + "percentageChange": -2.03 + }, + { + "month": "Agu", + "year": 2025, + "totalUnemployment": 142, + "educatedUnemployment": 84, + "uneducatedUnemployment": 58, + "percentageChange": -2.07 + }, + { + "month": "Sep", + "year": 2025, + "totalUnemployment": 140, + "educatedUnemployment": 83, + "uneducatedUnemployment": 57, + "percentageChange": -1.41 + }, + { + "month": "Okt", + "year": 2025, + "totalUnemployment": 138, + "educatedUnemployment": 82, + "uneducatedUnemployment": 56, + "percentageChange": -1.43 + }, + { + "month": "Nov", + "year": 2025, + "totalUnemployment": 135, + "educatedUnemployment": 80, + "uneducatedUnemployment": 55, + "percentageChange": -2.17 + }, + { + "month": "Des", + "year": 2025, + "totalUnemployment": 132, + "educatedUnemployment": 78, + "uneducatedUnemployment": 54, + "percentageChange": -2.22 + } +] \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4d96e6c7..0c3168ef 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -170,7 +170,7 @@ model KategoriDesaAntiKorupsi { //========================================= SDGS Desa ========================================= // model SdgsDesa { id String @id @default(cuid()) - name String @unique + name String jumlah String image FileStorage? @relation(fields: [imageId], references: [id]) imageId String? @@ -183,7 +183,7 @@ model SdgsDesa { //========================================= APBDes ========================================= // model APBDes { id String @id @default(cuid()) - name String @unique + name String jumlah String image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id]) imageId String? @@ -198,7 +198,7 @@ model APBDes { //========================================= PRESTASI DESA ========================================= // model PrestasiDesa { id String @id @default(cuid()) - name String @unique + name String deskripsi String @db.Text kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id]) kategoriId String @@ -1416,6 +1416,9 @@ model PosisiOrganisasi { pegawai Pegawai[] strukturOrganisasi StrukturOrganisasi[] // Relasi balik StrukturOrganisasiPPID StrukturOrganisasiPPID[] + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@map("posisi_organisasi") } @@ -1566,7 +1569,7 @@ model DataDemografiPekerjaan { model DetailDataPengangguran { id String @id @default(uuid()) @db.Uuid month String @db.VarChar(20) - year DateTime + year Int totalUnemployment Int educatedUnemployment Int uneducatedUnemployment Int diff --git a/prisma/seed.ts b/prisma/seed.ts index add399bc..ab793335 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -376,8 +376,7 @@ import fileStorage from "./data/file-storage.json"; for (const l of apbdes) { await prisma.aPBDes.upsert({ where: { - name: l.name, - jumlah: l.jumlah, + id: l.id, }, update: { name: l.name, @@ -895,12 +894,9 @@ import fileStorage from "./data/file-storage.json"; console.log("hubungan organisasi success ..."); for (const d of detailDataPengangguran) { - // Convert the year to a Date object (using January 1st of the year as the date) - const yearAsDate = new Date(d.year, 0, 1); - await prisma.detailDataPengangguran.upsert({ where: { - month_year: { month: d.month, year: yearAsDate }, + month_year: { month: d.month, year: d.year }, }, update: { totalUnemployment: d.totalUnemployment, @@ -910,7 +906,7 @@ import fileStorage from "./data/file-storage.json"; }, create: { month: d.month, - year: yearAsDate, + year: d.year, totalUnemployment: d.totalUnemployment, educatedUnemployment: d.educatedUnemployment, uneducatedUnemployment: d.uneducatedUnemployment, diff --git a/public/perbekel.png b/public/perbekel.png new file mode 100644 index 00000000..7da92111 Binary files /dev/null and b/public/perbekel.png differ diff --git a/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts b/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts index edda9a97..fd3dd897 100644 --- a/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts +++ b/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts @@ -7,9 +7,13 @@ import { z } from "zod"; const templateApbDesa = z.object({ tahun: z.number().min(4, "Tahun minimal 4 karakter"), - pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"), + pembiayaanIds: z + .array(z.string().uuid()) + .nonempty("Pilih minimal 1 pembiayaan"), belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"), - pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"), + pendapatanIds: z + .array(z.string().uuid()) + .nonempty("Pilih minimal 1 pendapatan"), }); const ApbDesaDefaultForm = { @@ -54,32 +58,46 @@ const ApbDesa = proxy({ }, }, findMany: { - data: null as - | Prisma.ApbDesaGetPayload<{ - include: { - pendapatan: true; - belanja: true; - pembiayaan: true; - }; - }>[] - | null, + data: null as + | Prisma.ApbDesaGetPayload<{ + include: { + pendapatan: true; + belanja: true; + pembiayaan: true; + }; + }>[] + | null, + page: 1, + totalPages: 1, loading: false, - async load() { + search: "", + load: async (page = 1, limit = 10, search = "") => { + ApbDesa.findMany.loading = true; // ✅ Akses langsung via nama path + ApbDesa.findMany.page = page; + ApbDesa.findMany.search = search; + try { - this.loading = true; + const query: any = { page, limit }; + if (search) query.search = search; + const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[ "find-many" - ].get(); - if (res.status === 200) { - this.data = res.data?.data ?? []; + ].get({ query }); + + if (res.status === 200 && res.data?.success) { + ApbDesa.findMany.data = res.data.data ?? []; + ApbDesa.findMany.totalPages = + res.data.totalPages ?? 1; } else { - toast.error(res.data?.message || "Gagal mengambil APB Desa"); + ApbDesa.findMany.data = []; + ApbDesa.findMany.totalPages = 1; } - } catch (error) { - console.error("Find many error:", error); - toast.error("Gagal mengambil APB Desa"); + } catch (err) { + console.error("Gagal fetch APB Desa paginated:", err); + ApbDesa.findMany.data = []; + ApbDesa.findMany.totalPages = 1; } finally { - this.loading = false; + ApbDesa.findMany.loading = false; } }, }, @@ -106,13 +124,13 @@ const ApbDesa = proxy({ throw new Error("Gagal mengambil APB Desa"); } const result = await response.json(); - + if (!result.success) { throw new Error(result.message || "Gagal memuat APB Desa"); } - + const data = result.data; - + this.id = id; this.form = { tahun: data.tahun || 0, @@ -120,7 +138,7 @@ const ApbDesa = proxy({ belanjaIds: data.belanja?.map((b: any) => b.id) || [], pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [], }; - + return data; } catch (error) { console.error("Error loading APB Desa:", error); @@ -189,7 +207,7 @@ const ApbDesa = proxy({ data: null as Prisma.ApbDesaGetPayload<{ include: { pendapatan: true; belanja: true; pembiayaan: true }; }> | null, - + async load(id: string) { try { const response = await fetch( @@ -199,11 +217,11 @@ const ApbDesa = proxy({ throw new Error("Gagal mengambil detail APB Desa"); } const result = await response.json(); - + if (!result.success) { throw new Error(result.message || "Gagal mengambil data"); } - + this.data = result.data; // ✅ fix utama di sini return result.data; } catch (error) { @@ -264,34 +282,32 @@ const pendapatan = proxy({ data: null as any[] | null, page: 1, totalPages: 1, - total: 0, loading: false, - load: async (page = 1, limit = 10) => { - // Change to arrow function - pendapatan.findMany.loading = true; // Use the full path to access the property + search: "", + load: async (page = 1, limit = 10, search = "") => { + pendapatan.findMany.loading = true; // ✅ Akses langsung via nama path pendapatan.findMany.page = page; + pendapatan.findMany.search = search; + try { - const res = - await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[ - "find-many" - ].get({ - query: { page, limit }, - }); + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[ + "find-many" + ].get({ query }); if (res.status === 200 && res.data?.success) { - pendapatan.findMany.data = res.data.data || []; - pendapatan.findMany.total = res.data.total || 0; - pendapatan.findMany.totalPages = res.data.totalPages || 1; + pendapatan.findMany.data = res.data.data ?? []; + pendapatan.findMany.totalPages = + res.data.totalPages ?? 1; } else { - console.error("Failed to load pendapatan:", res.data?.message); pendapatan.findMany.data = []; - pendapatan.findMany.total = 0; pendapatan.findMany.totalPages = 1; } - } catch (error) { - console.error("Error loading pendapatan:", error); + } catch (err) { + console.error("Gagal fetch pendapatan asli desa paginated:", err); pendapatan.findMany.data = []; - pendapatan.findMany.total = 0; pendapatan.findMany.totalPages = 1; } finally { pendapatan.findMany.loading = false; @@ -308,12 +324,15 @@ const pendapatan = proxy({ return null; } try { - const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); + const response = await fetch( + `/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -349,16 +368,19 @@ const pendapatan = proxy({ try { pendapatan.update.loading = true; - const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: this.form.name, - value: this.form.value, - }), - }); + const response = await fetch( + `/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + value: this.form.value, + }), + } + ); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( @@ -495,23 +517,37 @@ const belanja = proxy({ name: string; value: number; }>, + page: 1, + totalPages: 1, loading: false, - async load() { + search: "", + load: async (page = 1, limit = 10, search = "") => { + belanja.findMany.loading = true; // ✅ Akses langsung via nama path + belanja.findMany.page = page; + belanja.findMany.search = search; + try { - this.loading = true; + const query: any = { page, limit }; + if (search) query.search = search; + const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[ "find-many" - ].get(); - if (res.status === 200) { - this.data = res.data?.data ?? []; + ].get({ query }); + + if (res.status === 200 && res.data?.success) { + belanja.findMany.data = res.data.data ?? []; + belanja.findMany.totalPages = + res.data.totalPages ?? 1; } else { - toast.error(res.data?.message || "Gagal mengambil Belanja"); + belanja.findMany.data = []; + belanja.findMany.totalPages = 1; } - } catch (error) { - console.error("Find many error:", error); - toast.error("Gagal mengambil Belanja"); + } catch (err) { + console.error("Gagal fetch Belanja paginated:", err); + belanja.findMany.data = []; + belanja.findMany.totalPages = 1; } finally { - this.loading = false; + belanja.findMany.loading = false; } }, }, @@ -525,12 +561,15 @@ const belanja = proxy({ return null; } try { - const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); + const response = await fetch( + `/api/ekonomi/pendapatanaslidesa/belanja/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -566,16 +605,19 @@ const belanja = proxy({ try { belanja.update.loading = true; - const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: this.form.name, - value: this.form.value, - }), - }); + const response = await fetch( + `/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + value: this.form.value, + }), + } + ); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( @@ -710,23 +752,37 @@ const pembiayaan = proxy({ name: string; value: number; }>, + page: 1, + totalPages: 1, loading: false, - async load() { + search: "", + load: async (page = 1, limit = 10, search = "") => { + pembiayaan.findMany.loading = true; // ✅ Akses langsung via nama path + pembiayaan.findMany.page = page; + pembiayaan.findMany.search = search; + try { - this.loading = true; + const query: any = { page, limit }; + if (search) query.search = search; + const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[ "find-many" - ].get(); - if (res.status === 200) { - this.data = res.data?.data ?? []; + ].get({ query }); + + if (res.status === 200 && res.data?.success) { + pembiayaan.findMany.data = res.data.data ?? []; + pembiayaan.findMany.totalPages = + res.data.totalPages ?? 1; } else { - toast.error(res.data?.message || "Gagal mengambil Pembiayaan"); + pembiayaan.findMany.data = []; + pembiayaan.findMany.totalPages = 1; } - } catch (error) { - console.error("Find many error:", error); - toast.error("Gagal mengambil Pembiayaan"); + } catch (err) { + console.error("Gagal fetch Pembiayaan paginated:", err); + pembiayaan.findMany.data = []; + pembiayaan.findMany.totalPages = 1; } finally { - this.loading = false; + pembiayaan.findMany.loading = false; } }, }, @@ -740,12 +796,15 @@ const pembiayaan = proxy({ return null; } try { - const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); + const response = await fetch( + `/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -781,16 +840,19 @@ const pembiayaan = proxy({ try { pembiayaan.update.loading = true; - const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: this.form.name, - value: this.form.value, - }), - }); + const response = await fetch( + `/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + value: this.form.value, + }), + } + ); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( diff --git a/src/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran.ts b/src/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran.ts index de8d187b..c45ac24a 100644 --- a/src/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran.ts +++ b/src/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran.ts @@ -15,7 +15,8 @@ const templateJumlahPengngguran = z.object({ uneducatedUnemployment: z .number() .min(1, "Pengangguran tidak pendidikan harus diisi"), - percentageChange: z.number().min(0, "Persentase perubahan harus diisi"), + percentageChange: z.number({ invalid_type_error: "Persentase perubahan harus angka" }), + }); type JumlahPengangguran = { @@ -29,7 +30,7 @@ type JumlahPengangguran = { const jumlahPengangguranForm: JumlahPengangguran = { month: "", - year: 0, + year: new Date().getFullYear(), // Default to current year totalUnemployment: 0, educatedUnemployment: 0, uneducatedUnemployment: 0, @@ -60,13 +61,21 @@ const jumlahPengangguran = proxy({ form: jumlahPengangguranForm, loading: false, async create() { - const cek = templateJumlahPengngguran.safeParse( - jumlahPengangguran.create.form - ); + // Ensure all number fields are actual numbers + const formData = { + ...jumlahPengangguran.create.form, + year: Number(jumlahPengangguran.create.form.year) || new Date().getFullYear(), + totalUnemployment: Number(jumlahPengangguran.create.form.totalUnemployment) || 0, + educatedUnemployment: Number(jumlahPengangguran.create.form.educatedUnemployment) || 0, + uneducatedUnemployment: Number(jumlahPengangguran.create.form.uneducatedUnemployment) || 0, + percentageChange: Number(jumlahPengangguran.create.form.percentageChange) || 0, + }; + + const cek = templateJumlahPengngguran.safeParse(formData); if (!cek.success) { const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; + .map((v) => `${v.path.join(".")} (${v.message})`) + .join("\n")}]`; toast.error(err); return null; } @@ -78,7 +87,7 @@ const jumlahPengangguran = proxy({ ].post(jumlahPengangguran.create.form); if (res.status === 200) { - const id = res.data?.data?.id; + const id = res.data?.id; if (id) { toast.success("Success create"); jumlahPengangguran.create.form = { ...jumlahPengangguranForm }; @@ -103,16 +112,40 @@ const jumlahPengangguran = proxy({ omit: { isActive: true }; }>[] | null, - async load() { - const res = - await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[ - "find-many" - ].get(); - if (res.status === 200) { - jumlahPengangguran.findMany.data = res.data?.data ?? []; - } + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + jumlahPengangguran.findMany.loading = true; // ✅ Akses langsung via nama path + jumlahPengangguran.findMany.page = page; + jumlahPengangguran.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[ + "find-many" + ].get({ query }); + + if (res.status === 200 && res.data?.success) { + jumlahPengangguran.findMany.data = res.data.data ?? []; + jumlahPengangguran.findMany.totalPages = + res.data.totalPages ?? 1; + } else { + jumlahPengangguran.findMany.data = []; + jumlahPengangguran.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch jumlah pengangguran paginated:", err); + jumlahPengangguran.findMany.data = []; + jumlahPengangguran.findMany.totalPages = 1; + } finally { + jumlahPengangguran.findMany.loading = false; + } + }, }, - }, findUnique: { data: null as Prisma.DetailDataPengangguranGetPayload<{ diff --git a/src/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi.ts b/src/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi.ts index 7819facb..9fc870d2 100644 --- a/src/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi.ts +++ b/src/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi.ts @@ -161,18 +161,34 @@ const posisiOrganisasi = proxy({ deskripsi: string | null; hierarki: number; }>, - async load() { + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path + posisiOrganisasi.findMany.page = page; + posisiOrganisasi.findMany.search = search; + try { - const res = await ApiFetch.api.ekonomi["struktur-organisasi"][ - "posisi-organisasi" - ]["find-many"].get(); - if (res.status === 200) { - // The API now returns the id field, so we can use it directly - this.data = res.data?.data ?? []; + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + posisiOrganisasi.findMany.data = res.data.data ?? []; + posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1; + } else { + posisiOrganisasi.findMany.data = []; + posisiOrganisasi.findMany.totalPages = 1; } - } catch (error) { - console.error("Find many error:", error); - this.data = []; + } catch (err) { + console.error("Gagal fetch posisi organisasi paginated:", err); + posisiOrganisasi.findMany.data = []; + posisiOrganisasi.findMany.totalPages = 1; + } finally { + posisiOrganisasi.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/keamanan/laporan-publik.ts b/src/app/admin/(dashboard)/_state/keamanan/laporan-publik.ts index dad71d04..993a3a9a 100644 --- a/src/app/admin/(dashboard)/_state/keamanan/laporan-publik.ts +++ b/src/app/admin/(dashboard)/_state/keamanan/laporan-publik.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; @@ -97,13 +98,37 @@ const laporanPublikState = proxy({ include: { penanganan: true }; }>[] | null, - async load() { - const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get(); - if (res.status === 200) { - laporanPublikState.findMany.data = res.data?.data ?? []; - } + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + laporanPublikState.findMany.loading = true; // ✅ Akses langsung via nama path + laporanPublikState.findMany.page = page; + laporanPublikState.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + laporanPublikState.findMany.data = res.data.data ?? []; + laporanPublikState.findMany.totalPages = res.data.totalPages ?? 1; + } else { + laporanPublikState.findMany.data = []; + laporanPublikState.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch laporan publik paginated:", err); + laporanPublikState.findMany.data = []; + laporanPublikState.findMany.totalPages = 1; + } finally { + laporanPublikState.findMany.loading = false; + } + }, }, - }, findUnique: { data: null as Prisma.LaporanPublikGetPayload<{ include: { penanganan: true }; diff --git a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx index 0ece72c8..94d786a9 100644 --- a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx +++ b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; +import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react'; @@ -62,43 +62,50 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { Layanan - - {tabs.map((tab, i) => ( - - + + {tabs.map((tab, i) => ( + - {tab.label} - - - ))} - + + {tab.label} + + + ))} + + {tabs.map((tab, i) => ( Berita Desa - - {tabs.map((tab, i) => ( - - + + {tabs.map((tab, i) => ( + - {tab.label} - - - ))} - + + {tab.label} + + + ))} + + {tabs.map((tab, i) => ( ( - - {item.judul} - + + + {item.judul} + + diff --git a/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx b/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx index e850cec6..4c15f898 100644 --- a/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx @@ -110,7 +110,7 @@ export default function ListImage() { radius="md" onClick={() => { stateFileStorage - .del({ name: v.name }) + .del({ id: v.id }) .finally(() => toast("Foto berhasil dihapus")); }} > @@ -143,14 +143,15 @@ export default function ListImage() { { - stateFileStorage.page = page; - stateFileStorage.load(); + stateFileStorage.load({ page }); }} /> + )} diff --git a/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx b/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx index 8db41797..803a6884 100644 --- a/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; +import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; import { IconPhoto, IconVideo } from '@tabler/icons-react'; @@ -48,43 +48,50 @@ function LayoutTabsGallery({ children }: { children: React.ReactNode }) { Gallery - - {tabs.map((tab, i) => ( - - + + {tabs.map((tab, i) => ( + - {tab.label} - - - ))} - + + {tab.label} + + + ))} + + {tabs.map((tab, i) => ( Pengumuman - - {tabs.map((tab, i) => ( - - - {tab.label} - - - ))} - + {/* ✅ Scroll horizontal wrapper */} + + + {tabs.map((tab, i) => ( + + + {tab.label} + + + ))} + + {tabs.map((tab, i) => ( Potensi - - {tabs.map((tab, i) => ( - - - {tab.label} - - - ))} - + {/* ✅ Scroll horizontal wrapper */} + + + {tabs.map((tab, i) => ( + + + {tab.label} + + + ))} + + {tabs.map((tab, i) => ( Profile Desa - - {tabs.map((tab, i) => ( - - - {tab.label} - - - ))} - - - {tabs.map((tab, i) => ( - + - {/* Konten dummy, bisa diganti sesuai routing */} - <>{children} - - ))} - - - ); + {tabs.map((tab, i) => ( + + + {tab.label} + + + ))} + + + + {tabs.map((tab, i) => ( + + {/* Konten dummy, bisa diganti sesuai routing */} + <>{children} + + ))} + + + ); } -export default LayoutTabsDetail; + export default LayoutTabsDetail; diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx index 6358cb46..dca2dfe2 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx @@ -1,73 +1,150 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { + Stack, + Tabs, + TabsList, + TabsPanel, + TabsTab, + Title, + Tooltip, + ScrollArea, +} from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { + IconFileAnalytics, + IconCoins, + IconShoppingCart, + IconWallet, +} from '@tabler/icons-react'; function LayoutTabs({ children }: { children: React.ReactNode }) { - const router = useRouter() - const pathname = usePathname() + const router = useRouter(); + const pathname = usePathname(); + const tabs = [ { label: "APB Desa", value: "apbdesa", - href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa" + href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa", + icon: , + tooltip: "Lihat ringkasan Anggaran Pendapatan dan Belanja Desa", }, { label: "Pendapatan", value: "pendapatan", - href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan" + href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan", + icon: , + tooltip: "Kelola data pendapatan desa", }, { label: "Belanja", value: "belanja", - href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja" + href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja", + icon: , + tooltip: "Atur data belanja desa", }, { label: "Pembiayaan", value: "pembiayaan", - href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan" + href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan", + icon: , + tooltip: "Kelola data pembiayaan desa", }, - ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find((tab) => tab.href === pathname); + const [activeTab, setActiveTab] = useState( + currentTab?.value || tabs[0].value + ); const handleTabChange = (value: string | null) => { - const tab = tabs.find(t => t.value === value) + const tab = tabs.find((t) => t.value === value); if (tab) { - router.push(tab.href) + router.push(tab.href); } - setActiveTab(value) - } + setActiveTab(value); + }; useEffect(() => { - const match = tabs.find(tab => tab.href === pathname) + const match = tabs.find((tab) => tab.href === pathname); if (match) { - setActiveTab(match.value) + setActiveTab(match.value); } - }, [pathname]) + }, [pathname]); return ( - - Pendapatan Asli Desa - - - {tabs.map((e, i) => ( - {e.label} - ))} - - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + + Pendapatan Asli Desa + + + + {/* ✅ Scroll horizontal wrapper */} + + + {tabs.map((tab, i) => ( + + + {tab.label} + + + ))} + + + + {tabs.map((tab, i) => ( + + {children} ))} - {children} ); } -export default LayoutTabs; \ No newline at end of file +export default LayoutTabs; diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx index dbe04cf3..7d07fd77 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx @@ -3,7 +3,19 @@ /* eslint-disable react-hooks/exhaustive-deps */ import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + MultiSelect, + Paper, + Skeleton, + Stack, + Text, + TextInput, + Title, + Tooltip, + Group, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -23,7 +35,7 @@ function EditAPBDesa() { pembiayaanIds: apbState.update.form.pembiayaanIds || [], }); - // Load APB desa by id saat pertama kali + // Load APB desa by id useEffect(() => { const loadAPBdesa = async () => { const id = params?.id as string; @@ -46,12 +58,10 @@ function EditAPBDesa() { }; loadAPBdesa(); - }, [params?.id]); // ✅ hapus beritaState dari dependency + }, [params?.id]); const handleSubmit = async () => { - try { - // Update global state with form data apbState.update.form = { ...apbState.update.form, tahun: Number(formData.tahun), @@ -70,65 +80,95 @@ function EditAPBDesa() { }; return ( - - - - - - - Edit APB Desa + + {/* Header */} + + + + + + Edit APB Desa + + + + {/* Form Card */} + + + {/* Tahun */} { - setFormData({ ...formData, tahun: val.target.value }); - }} - label={Tahun} - placeholder="masukkan tahun" + onChange={(e) => + setFormData({ ...formData, tahun: e.target.value }) + } + label={Tahun} + placeholder="Masukkan tahun anggaran" + required /> + + {/* Selects */} { - setFormData({ ...formData, pendapatanIds: ids }); - }} + onSelectionChange={(ids) => + setFormData({ ...formData, pendapatanIds: ids }) + } /> + { - setFormData({ ...formData, belanjaIds: ids }); - }} + onSelectionChange={(ids) => + setFormData({ ...formData, belanjaIds: ids }) + } /> + { - setFormData({ ...formData, pembiayaanIds: ids }); - }} + onSelectionChange={(ids) => + setFormData({ ...formData, pembiayaanIds: ids }) + } /> - + + {/* Save Button */} + + + ); + /* --- Sub Components --- */ - /* Select Pendapatan */ - interface SelectPendapatanProps { - selectedIds: string[]; - onSelectionChange: (ids: string[]) => void; - } - - function SelectPendapatan({ - selectedIds = [], - onSelectionChange, - }: SelectPendapatanProps) { + function SelectPendapatan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) { const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan); useShallowEffect(() => { - pendapatanState.findMany.load().then(() => { - console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data); - }); + pendapatanState.findMany.load(); }, []); if (!pendapatanState.findMany.data) { @@ -137,10 +177,10 @@ function EditAPBDesa() { return ( Pendapatan} - data={pendapatanState.findMany.data.map(p => ({ + label={Pendapatan} + data={pendapatanState.findMany.data.map((p: any) => ({ value: p.id, - label: p.name + label: p.name, }))} value={selectedIds} onChange={onSelectionChange} @@ -152,22 +192,11 @@ function EditAPBDesa() { ); } - /* Select Belanja */ - interface SelectBelanjaProps { - selectedIds: string[]; - onSelectionChange: (ids: string[]) => void; - } - - function SelectBelanja({ - selectedIds = [], - onSelectionChange, - }: SelectBelanjaProps) { + function SelectBelanja({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) { const belanjaState = useProxy(PendapatanAsliDesa.belanja); useShallowEffect(() => { - belanjaState.findMany.load().then(() => { - console.log("Belanja berhasil dimuat:", belanjaState.findMany.data); - }); + belanjaState.findMany.load(); }, []); if (!belanjaState.findMany.data) { @@ -176,10 +205,10 @@ function EditAPBDesa() { return ( Belanja} - data={belanjaState.findMany.data.map(b => ({ + label={Belanja} + data={belanjaState.findMany.data.map((b: any) => ({ value: b.id, - label: b.name + label: b.name, }))} value={selectedIds} onChange={onSelectionChange} @@ -191,22 +220,11 @@ function EditAPBDesa() { ); } - /* Select Pembiayaan */ - interface SelectPembiayaanProps { - selectedIds: string[]; - onSelectionChange: (ids: string[]) => void; - } - - function SelectPembiayaan({ - selectedIds = [], - onSelectionChange, - }: SelectPembiayaanProps) { + function SelectPembiayaan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) { const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan); useShallowEffect(() => { - pembiayaanState.findMany.load().then(() => { - console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data); - }); + pembiayaanState.findMany.load(); }, []); if (!pembiayaanState.findMany.data) { @@ -215,10 +233,10 @@ function EditAPBDesa() { return ( Pembiayaan} - data={pembiayaanState.findMany.data.map(b => ({ - value: b.id, - label: b.name + label={Pembiayaan} + data={pembiayaanState.findMany.data.map((p: any) => ({ + value: p.id, + label: p.name, }))} value={selectedIds} onChange={onSelectionChange} diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx index c9cd4336..ee57dc50 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx @@ -2,148 +2,204 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Skeleton, + Stack, + Text, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; - - function DetailAPBDesa() { - const apbState = useProxy(PendapatanAsliDesa.ApbDesa) - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() + const apbState = useProxy(PendapatanAsliDesa.ApbDesa); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - console.log("PARAM ID:", params?.id) - apbState.findUnique.load(params?.id as string) - }, []) + apbState.findUnique.load(params?.id as string); + }, []); - const formatRupiah = (value: number) => { - return new Intl.NumberFormat('id-ID', { + const formatRupiah = (value: number) => + new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0, }).format(value); - }; const handleHapus = () => { if (selectedId) { - apbState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa") + apbState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push( + '/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa' + ); } - } + }; if (!apbState.findUnique.data) { return ( - + - ) + ); } + const data = apbState.findUnique.data; + return ( - - - - - - - Detail APB Desa - {apbState.findUnique.data ? ( - - - - Tahun - {apbState.findUnique.data?.tahun} - - - - Detail Pembiayaan - {(apbState.findUnique.data?.pembiayaan || []).map((item) => ( - - {item.name}: {formatRupiah(Number(item.value))} - - ))} - - Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || []) - .reduce((sum, item) => sum + Number(item.value), 0))} + + + + + + + Detail APB Desa + + + + + + + Tahun + + + {data.tahun} + + + + + + + Detail Pembiayaan + + {(data?.pembiayaan || []).map((item) => ( + + {item.name}: {formatRupiah(Number(item.value))} - - - - - Detail Belanja - {(apbState.findUnique.data?.belanja || []).map((item) => ( - - {item.name}: {formatRupiah(Number(item.value))} - - ))} - - Total: {formatRupiah((apbState.findUnique.data?.belanja || []) - .reduce((sum, item) => sum + Number(item.value), 0))} + ))} + + Total:{' '} + {formatRupiah( + (data?.pembiayaan || []).reduce( + (sum, item) => sum + Number(item.value), + 0 + ) + )} + + + + + + + + Detail Belanja + + {(data?.belanja || []).map((item) => ( + + {item.name}: {formatRupiah(Number(item.value))} - - - - - Detail Pendapatan - {(apbState.findUnique.data?.pendapatan || []).map((item) => ( - - {item.name}: {formatRupiah(Number(item.value))} - - ))} - - Total: {formatRupiah((apbState.findUnique.data?.pendapatan || []) - .reduce((sum, item) => sum + Number(item.value), 0))} + ))} + + Total:{' '} + {formatRupiah( + (data?.belanja || []).reduce( + (sum, item) => sum + Number(item.value), + 0 + ) + )} + + + + + + + + Detail Pendapatan + + {(data?.pendapatan || []).map((item) => ( + + {item.name}: {formatRupiah(Number(item.value))} - - - + ))} + + Total:{' '} + {formatRupiah( + (data?.pendapatan || []).reduce( + (sum, item) => sum + Number(item.value), + 0 + ) + )} + + + + + + + + + - - - - ) : null} + + + + - {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus APB Desa ini?' + text="Apakah Anda yakin ingin menghapus APB Desa ini?" /> ); diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx index 3ee52586..174d0c9a 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx @@ -1,15 +1,28 @@ -'use client' +'use client'; + import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + MultiSelect, + Paper, + Skeleton, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; function CreateAPBDesa() { - const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa) - const router = useRouter() + const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa); + const router = useRouter(); const resetForm = () => { apbDesaState.create.form = { @@ -17,74 +30,101 @@ function CreateAPBDesa() { pendapatanIds: [], belanjaIds: [], pembiayaanIds: [], - } - } + }; + }; const handleSubmit = async () => { - await apbDesaState.create.submit() - resetForm() - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa") - } + await apbDesaState.create.submit(); + resetForm(); + router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa'); + }; return ( - - - - - - - Create APB Desa + + {/* Header */} + + + + + + Tambah APB Desa + + + + {/* Form */} + + { apbDesaState.create.form.tahun = Number(val.target.value); }} - label={Tahun} - placeholder="masukkan tahun" + label={Tahun} + placeholder="Masukkan tahun anggaran" + required /> + { apbDesaState.create.form.pendapatanIds = ids; }} /> + { apbDesaState.create.form.belanjaIds = ids; }} /> + { apbDesaState.create.form.pembiayaanIds = ids; }} /> - + + {/* Action */} + + + ); -/* Select Pendapatan */ + /* ---------- Select Pendapatan ---------- */ interface SelectPendapatanProps { selectedIds: string[]; onSelectionChange: (ids: string[]) => void; } - - function SelectPendapatan({ - selectedIds = [], - onSelectionChange, - }: SelectPendapatanProps) { + function SelectPendapatan({ selectedIds = [], onSelectionChange }: SelectPendapatanProps) { const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan); useShallowEffect(() => { - pendapatanState.findMany.load().then(() => { - console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data); - }); + pendapatanState.findMany.load(); }, []); if (!pendapatanState.findMany.data) { @@ -93,10 +133,10 @@ function CreateAPBDesa() { return ( Pendapatan} - data={pendapatanState.findMany.data.map(p => ({ + label={Pendapatan} + data={pendapatanState.findMany.data.map((p) => ({ value: p.id, - label: p.name + label: p.name, }))} value={selectedIds} onChange={onSelectionChange} @@ -108,22 +148,16 @@ function CreateAPBDesa() { ); } - /* Select Belanja */ + /* ---------- Select Belanja ---------- */ interface SelectBelanjaProps { selectedIds: string[]; onSelectionChange: (ids: string[]) => void; } - - function SelectBelanja({ - selectedIds = [], - onSelectionChange, - }: SelectBelanjaProps) { + function SelectBelanja({ selectedIds = [], onSelectionChange }: SelectBelanjaProps) { const belanjaState = useProxy(PendapatanAsliDesa.belanja); useShallowEffect(() => { - belanjaState.findMany.load().then(() => { - console.log("Belanja berhasil dimuat:", belanjaState.findMany.data); - }); + belanjaState.findMany.load(); }, []); if (!belanjaState.findMany.data) { @@ -132,10 +166,10 @@ function CreateAPBDesa() { return ( Belanja} - data={belanjaState.findMany.data.map(b => ({ + label={Belanja} + data={belanjaState.findMany.data.map((b) => ({ value: b.id, - label: b.name + label: b.name, }))} value={selectedIds} onChange={onSelectionChange} @@ -147,22 +181,16 @@ function CreateAPBDesa() { ); } - /* Select Pembiayaan */ + /* ---------- Select Pembiayaan ---------- */ interface SelectPembiayaanProps { selectedIds: string[]; onSelectionChange: (ids: string[]) => void; } - - function SelectPembiayaan({ - selectedIds = [], - onSelectionChange, - }: SelectPembiayaanProps) { + function SelectPembiayaan({ selectedIds = [], onSelectionChange }: SelectPembiayaanProps) { const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan); useShallowEffect(() => { - pembiayaanState.findMany.load().then(() => { - console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data); - }); + pembiayaanState.findMany.load(); }, []); if (!pembiayaanState.findMany.data) { @@ -171,10 +199,10 @@ function CreateAPBDesa() { return ( Pembiayaan} - data={pembiayaanState.findMany.data.map(b => ({ + label={Pembiayaan} + data={pembiayaanState.findMany.data.map((b) => ({ value: b.id, - label: b.name + label: b.name, }))} value={selectedIds} onChange={onSelectionChange} diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx index 11b7e353..ff0c6464 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx @@ -1,23 +1,38 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Paper, + Pagination, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; +import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa'; - function APBDesa() { const [search, setSearch] = useState(""); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -28,77 +43,147 @@ function APBDesa() { } function ListAPBDesa({ search }: { search: string }) { - const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa) + const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa); const router = useRouter(); - const formatRupiah = (value: number) => { - return new Intl.NumberFormat('id-ID', { - style: 'currency', - currency: 'IDR', + const { + data, + page, + totalPages, + loading, + load, + } = apbDesaState.findMany; + + const formatRupiah = (value: number) => + new Intl.NumberFormat("id-ID", { + style: "currency", + currency: "IDR", minimumFractionDigits: 0, }).format(value); - }; useShallowEffect(() => { - apbDesaState.findMany.load(); - }, []) + load(page, 10, search); + }, [page, search]); - const filteredData = (apbDesaState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.tahun.toString().toLowerCase().includes(keyword) || - item.pembiayaan.map((item) => item.value.toString()).includes(keyword) || - item.belanja.map((item) => item.value.toString()).includes(keyword) || - item.pendapatan.map((item) => item.value.toString()).includes(keyword) - ); - }); + const filteredData = data || []; - if (!apbDesaState.findMany.data) { + if (loading || !data) { return ( - + - ) + ); } + return ( - - - - - - Tahun - Pembiayaan - Belanja - Pendapatan - Detail - - - - {filteredData.map((item) => ( - - {item.tahun} - {formatRupiah(item.pembiayaan.reduce((sum, item) => sum + Number(item.value), 0))} - {formatRupiah(item.belanja.reduce((sum, item) => sum + Number(item.value), 0))} - {formatRupiah(item.pendapatan.reduce((sum, item) => sum + Number(item.value), 0))} - - - + + + + List APB Desa + + + + + + +
+ + + Tahun + Pembiayaan + Belanja + Pendapatan + Aksi - ))} - -
+ + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + {item.tahun} + + {formatRupiah( + item.pembiayaan.reduce( + (sum, val) => sum + Number(val.value), + 0 + ) + )} + + + {formatRupiah( + item.belanja.reduce( + (sum, val) => sum + Number(val.value), + 0 + ) + )} + + + {formatRupiah( + item.pendapatan.reduce( + (sum, val) => sum + Number(val.value), + 0 + ) + )} + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data APB Desa yang cocok + +
+
+
+ )} +
+ +
+
+ { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: "smooth" }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
); } diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx index 33bca9e2..ea3729d2 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx @@ -2,7 +2,16 @@ 'use client' import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -72,41 +81,72 @@ function EditBelanja() { } return ( - - - - + + {/* Header */} + + + + + + Edit Jenis Belanja + + - - - Edit Jenis Pendapatan - { - setFormData({ ...formData, name: val.target.value }); - }} - label={Nama Jenis Pendapatan} - placeholder='Masukkan nama Jenis Pendapatan' - /> - Nilai} - placeholder='Masukkan nilai' - value={formatRupiah(formData.value)} - onChange={(val) => { - const raw = val.currentTarget.value; - const cleanValue = unformatRupiah(raw); - setFormData({ ...formData, value: cleanValue }); - }} - /> - - - - - - + {/* Card */} + + + setFormData({ ...formData, name: e.target.value })} + required + /> + + { + const raw = e.currentTarget.value; + const cleanValue = unformatRupiah(raw); + setFormData({ ...formData, value: cleanValue }); + }} + required + /> + + + + + + + ); } -export default EditBelanja; \ No newline at end of file +export default EditBelanja; diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx index c03cb35c..76bc332c 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx @@ -1,17 +1,30 @@ -'use client' +'use client'; + import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; +import { toast } from 'react-toastify'; function CreateBelanja() { - const belanjaState = useProxy(PendapatanAsliDesa.belanja) - const router = useRouter() + const belanjaState = useProxy(PendapatanAsliDesa.belanja); + const router = useRouter(); const formatRupiah = (value: number | string) => { - const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); + const number = + typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', @@ -25,48 +38,83 @@ function CreateBelanja() { const resetForm = () => { belanjaState.create.form = { - name: "", + name: '', value: 0, - } - } + }; + }; const handleSubmit = async () => { - await belanjaState.create.submit(); - resetForm() - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja") - } - return ( - - - - + if (!belanjaState.create.form.name || !belanjaState.create.form.value) { + return toast.warn('Lengkapi semua field terlebih dahulu'); + } - - - Create Jenis Belanja + await belanjaState.create.submit(); + resetForm(); + router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja'); + }; + + return ( + + {/* Header dengan back button */} + + + + + + Tambah Jenis Belanja + + + + {/* Card Form */} + + Nama Jenis Belanja} + placeholder="Masukkan nama jenis belanja" value={belanjaState.create.form.name} - onChange={(val) => { - belanjaState.create.form.name = val.target.value; - }} - label={Nama Jenis Belanja} - placeholder='Masukkan nama jenis belanja' + onChange={(e) => (belanjaState.create.form.name = e.target.value)} + required /> + Nilai} + placeholder="Masukkan nilai belanja" value={formatRupiah(belanjaState.create.form.value)} - onChange={(val) => { - const raw = val.currentTarget.value; - const cleanValue = unformatRupiah(raw); - belanjaState.create.form.value = cleanValue; + onChange={(e) => { + const raw = e.currentTarget.value; + belanjaState.create.form.value = unformatRupiah(raw); }} - label={Nilai} - placeholder='Masukkan nilai' + required /> - - + + + diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx index d63f0ce7..467c8142 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx @@ -1,24 +1,41 @@ 'use client' + import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +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 PendapatanAsliDesa from '../../../_state/ekonomi/PADesa'; -import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; - function Belanja() { const [search, setSearch] = useState(""); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -29,108 +46,175 @@ function Belanja() { } function ListBelanja({ search }: { search: string }) { - const belanjaState = useProxy(PendapatanAsliDesa.belanja) + const belanjaState = useProxy(PendapatanAsliDesa.belanja); const router = useRouter(); - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - - const formatRupiah = (value: number) => { - return new Intl.NumberFormat('id-ID', { + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + + const { + data, + loading, + load, + page, + totalPages, + } = belanjaState.findMany; + + const formatRupiah = (value: number) => + new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0, }).format(value); - }; - const totalBelanja = belanjaState.findMany.data.reduce((sum, item) => sum + item.value, 0); + const totalBelanja = data?.reduce((sum, item) => sum + item.value, 0) || 0; const handleDelete = () => { if (selectedId) { - belanjaState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - belanjaState.findMany.load() + belanjaState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + load(page, 10, search); } - } + }; useShallowEffect(() => { - belanjaState.findMany.load(); - }, []) + load(page, 10, search); + }, [page, search]); - const filteredData = (belanjaState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.value.toString().toLowerCase().includes(keyword) - ); - }); + const filteredData = data || []; - if (!belanjaState.findMany.data) { + if (loading || !data) { return ( - + - ) + ); } + return ( - - - - - - Nama - Nilai - Persentase - Edit - Delete - - - - {filteredData.map((item) => ( - - {item.name} - {formatRupiah(item.value)} - {((item.value / totalBelanja) * 100).toFixed(0)}% - - - - - - + + + Daftar Belanja + + + + + + +
+ + + Nama + Nilai + Persentase + Aksi - ))} - - - Total - - - {formatRupiah(belanjaState.findMany.data.reduce((total, item) => total + item.value, 0))} - - - -
+ + + {filteredData.length > 0 ? ( + <> + {filteredData.map((item) => ( + + + + {item.name} + + + {formatRupiah(item.value)} + + {totalBelanja > 0 + ? ((item.value / totalBelanja) * 100).toFixed(0) + '%' + : '0%'} + + + + + + + + + + + + + ))} + + + Total + + + {formatRupiah(totalBelanja)} + + + + ) : ( + + +
+ Tidak ada data belanja 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 belanja ini?' + text="Apakah anda yakin ingin menghapus belanja ini?" />
); diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx index 79ef3847..0492b8ee 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx @@ -2,7 +2,16 @@ 'use client' import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -20,7 +29,7 @@ function EditPembiayaan() { }); const formatRupiah = (value: number | string) => { - const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); + const number = typeof value === 'number' ? value : Number(value.toString().replace(/\D/g, '')); return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', @@ -46,8 +55,8 @@ function EditPembiayaan() { }); } } catch (error) { - console.error("Error loading pembiayaan:", error); - toast.error("Gagal memuat data pembiayaan"); + console.error('Error loading pembiayaan:', error); + toast.error('Gagal memuat data pembiayaan'); } }; @@ -60,53 +69,84 @@ function EditPembiayaan() { ...pembiayaanState.update.form, name: formData.name, value: Number(formData.value), - } + }; await pembiayaanState.update.update(); - toast.success("Jenis Pembiayaan berhasil diperbarui!"); - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"); + toast.success('Jenis Pembiayaan berhasil diperbarui!'); + router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan'); } catch (error) { - console.error("Error updating jenis pembiayaan:", error); - toast.error("Terjadi kesalahan saat memperbarui jenis pembiayaan"); + console.error('Error updating jenis pembiayaan:', error); + toast.error('Terjadi kesalahan saat memperbarui jenis pembiayaan'); } - } + }; return ( - - - - + + {/* Header dengan Back Button */} + + + + + + Edit Jenis Pembiayaan + + - - - Edit Jenis Pembiayaan - { - setFormData({ ...formData, name: val.target.value }); - }} - label={Nama Jenis Pembiayaan} - placeholder='Masukkan nama Jenis Pembiayaan' - /> - Nilai} - placeholder='Masukkan nilai' - value={formatRupiah(formData.value)} - onChange={(val) => { - const raw = val.currentTarget.value; - const cleanValue = unformatRupiah(raw); - setFormData({ ...formData, value: cleanValue }); - }} - /> - - - - - - + {/* Card Form */} + + + setFormData({ ...formData, name: e.target.value })} + required + /> + + { + const raw = e.currentTarget.value; + const cleanValue = unformatRupiah(raw); + setFormData({ ...formData, value: cleanValue }); + }} + required + /> + + + + + + + ); } -export default EditPembiayaan; \ No newline at end of file +export default EditPembiayaan; diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx index 4a81d990..ff6e17ff 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx @@ -1,18 +1,30 @@ -'use client' +'use client'; import React from 'react'; import { useProxy } from 'valtio/utils'; import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import { useRouter } from 'next/navigation'; import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Title, + TextInput, + Text, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; +import { toast } from 'react-toastify'; function CreatePembiayaan() { - const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan) - const router = useRouter() + const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan); + const router = useRouter(); const formatRupiah = (value: number | string) => { - const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); + const number = + typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', @@ -26,48 +38,85 @@ function CreatePembiayaan() { const resetForm = () => { pembiayaanState.create.form = { - name: "", + name: '', value: 0, - } - } + }; + }; const handleSubmit = async () => { - await pembiayaanState.create.submit(); - resetForm() - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan") - } - return ( - - - - + if (!pembiayaanState.create.form.name || !pembiayaanState.create.form.value) { + return toast.warn('Nama dan nilai wajib diisi'); + } - - - Create Jenis Pembiayaan + await pembiayaanState.create.submit(); + resetForm(); + router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan'); + }; + + return ( + + {/* Header */} + + + + + + Tambah Jenis Pembiayaan + + + + {/* Form Card */} + + Nama Jenis Pembiayaan} + placeholder="Masukkan nama jenis pembiayaan" value={pembiayaanState.create.form.name} - onChange={(val) => { - pembiayaanState.create.form.name = val.target.value; + onChange={(e) => { + pembiayaanState.create.form.name = e.currentTarget.value; }} - label={Nama Jenis Pembiayaan} - placeholder='Masukkan nama jenis pembiayaan' + required /> + Nilai} + placeholder="Masukkan nilai" value={formatRupiah(pembiayaanState.create.form.value)} - onChange={(val) => { - const raw = val.currentTarget.value; - const cleanValue = unformatRupiah(raw); - pembiayaanState.create.form.value = cleanValue; + onChange={(e) => { + const raw = e.currentTarget.value; + pembiayaanState.create.form.value = unformatRupiah(raw); }} - label={Nilai} - placeholder='Masukkan nilai' + required /> - - + + + diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx index 4f31cafc..627f5894 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx @@ -1,14 +1,31 @@ 'use client' -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip, + Pagination, +} from '@mantine/core'; import React, { useState } from 'react'; import HeaderSearch from '../../../_com/header'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa'; import { useProxy } from 'valtio/utils'; import { useRouter } from 'next/navigation'; import { useShallowEffect } from '@mantine/hooks'; import colors from '@/con/colors'; -import JudulList from '../../../_com/judulList'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; function Pembiayaan() { @@ -16,8 +33,8 @@ function Pembiayaan() { return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -28,111 +45,171 @@ function Pembiayaan() { } function ListPembiayaan({ search }: { search: string }) { - const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan) + const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan); const router = useRouter(); - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); - const formatRupiah = (value: number) => { - return new Intl.NumberFormat('id-ID', { + const { + data, + page, + totalPages, + loading, + load, + } = pembiayaanState.findMany; + + const formatRupiah = (value: number) => + new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0, }).format(value); - }; - const totalPembiayaan = pembiayaanState.findMany.data.reduce((sum, item) => sum + item.value, 0); + const totalPembiayaan = (data || []).reduce((sum, item) => sum + item.value, 0); const handleDelete = () => { if (selectedId) { - pembiayaanState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - pembiayaanState.findMany.load() + pembiayaanState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + load(page, 10, search); } - } + }; useShallowEffect(() => { - pembiayaanState.findMany.load(); - }, []) + load(page, 10, search); + }, [page, search]); - const filteredData = (pembiayaanState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.value.toString().toLowerCase().includes(keyword) - ); - }); - - if (!pembiayaanState.findMany.data) { + if (loading || !data) { return ( - + - ) + ); } + + const filteredData = data || []; + return ( - - - - - - Nama - Nilai - Persentase - Edit - Delete - - - - {filteredData.map((item) => ( - - {item.name} - {formatRupiah(item.value)} - {((item.value / totalPembiayaan) * 100).toFixed(0)}% - - - - - - + + + Daftar Pembiayaan + + + + + + +
+ + + Nama + Nilai + Persentase + Aksi - ))} - - - Total - - - {formatRupiah(pembiayaanState.findMany.data.reduce((total, item) => total + item.value, 0))} - - - -
+ + + {filteredData.length > 0 ? ( + <> + {filteredData.map((item) => ( + + + + {item.name} + + + {formatRupiah(item.value)} + + {totalPembiayaan > 0 + ? ((item.value / totalPembiayaan) * 100).toFixed(0) + '%' + : '0%'} + + + + + + + + + ))} + {/* Total Row */} + + + Total + + {formatRupiah(totalPembiayaan)} + + + ) : ( + + +
+ Tidak ada data pembiayaan 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 pembiayaan ini?' + text="Apakah anda yakin ingin menghapus pembiayaan ini?" />
- ) + ); } export default Pembiayaan; diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx index 2ffc7285..1970c267 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx @@ -2,7 +2,16 @@ 'use client' import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -20,7 +29,7 @@ function EditPendapatan() { }); const formatRupiah = (value: number | string) => { - const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); + const number = typeof value === 'number' ? value : Number(value.toString().replace(/\D/g, '')); return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', @@ -28,15 +37,13 @@ function EditPendapatan() { }).format(number); }; - const unformatRupiah = (value: string) => { - return Number(value.replace(/\D/g, '')); - }; + const unformatRupiah = (value: string) => Number(value.replace(/\D/g, '')); useEffect(() => { - const loadPendapatan = async () => { - const id = params?.id as string; - if (!id) return; + const id = params?.id as string; + if (!id) return; + const loadPendapatan = async () => { try { const data = await pendapatanState.update.load(id); if (data) { @@ -46,8 +53,8 @@ function EditPendapatan() { }); } } catch (error) { - console.error("Error loading pendapatan:", error); - toast.error("Gagal memuat data pendapatan"); + console.error('Error loading pendapatan:', error); + toast.error('Gagal memuat data pendapatan'); } }; @@ -60,53 +67,84 @@ function EditPendapatan() { ...pendapatanState.update.form, name: formData.name, value: Number(formData.value), - } + }; await pendapatanState.update.update(); - toast.success("Jenis Pendapatan berhasil diperbarui!"); - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"); + toast.success('Jenis Pendapatan berhasil diperbarui!'); + router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan'); } catch (error) { - console.error("Error updating jenis pendapatan:", error); - toast.error("Terjadi kesalahan saat memperbarui jenis pendapatan"); + console.error('Error updating jenis pendapatan:', error); + toast.error('Terjadi kesalahan saat memperbarui jenis pendapatan'); } - } + }; return ( - - - - + + {/* Header with Back Button */} + + + + + + Edit Jenis Pendapatan + + - - - Edit Jenis Pendapatan - { - setFormData({ ...formData, name: val.target.value }); - }} - label={Nama Jenis Pendapatan} - placeholder='Masukkan nama Jenis Pendapatan' - /> - Nilai} - placeholder='Masukkan nilai' - value={formatRupiah(formData.value)} - onChange={(val) => { - const raw = val.currentTarget.value; - const cleanValue = unformatRupiah(raw); - setFormData({ ...formData, value: cleanValue }); - }} - /> - - - - - - + {/* Card Form */} + + + setFormData({ ...formData, name: e.target.value })} + required + /> + + { + const raw = e.currentTarget.value; + const cleanValue = unformatRupiah(raw); + setFormData({ ...formData, value: cleanValue }); + }} + required + /> + + + + + + + ); } -export default EditPendapatan; \ No newline at end of file +export default EditPendapatan; diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx index fdf9a7b0..9ff6882f 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx @@ -1,14 +1,24 @@ -'use client' +'use client'; import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; function CreatePendapatan() { - const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan) - const router = useRouter() + const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan); + const router = useRouter(); const formatRupiah = (value: number | string) => { const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, '')); @@ -25,48 +35,90 @@ function CreatePendapatan() { const resetForm = () => { pendapatanState.create.form = { - name: "", + name: '', value: 0, - } - } + }; + }; const handleSubmit = async () => { await pendapatanState.create.submit(); - resetForm() - router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan") - } - return ( - - - - + resetForm(); + router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan'); + }; - - - Create Jenis Pendapatan + return ( + + {/* Header dengan tombol back + judul */} + + + + + + Tambah Jenis Pendapatan + + + + {/* Card Form */} + + { pendapatanState.create.form.name = val.target.value; }} - label={Nama Jenis Pendapatan} - placeholder='Masukkan nama jenis pendapatan' + label={ + + Nama Jenis Pendapatan + + } + placeholder="Masukkan nama jenis pendapatan" + required /> + { const raw = val.currentTarget.value; const cleanValue = unformatRupiah(raw); pendapatanState.create.form.value = cleanValue; }} - label={Nilai} - placeholder='Masukkan nilai' + label={ + + Nilai + + } + placeholder="Masukkan nilai" + required /> - - + + + diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx index 0bde96df..47ba7b86 100644 --- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx @@ -1,16 +1,33 @@ 'use client' + import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +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 PendapatanAsliDesa from '../../../_state/ekonomi/PADesa'; -import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; - function Pendapatan() { const [search, setSearch] = useState(""); @@ -18,7 +35,7 @@ function Pendapatan() { } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -29,105 +46,166 @@ function Pendapatan() { } function ListPendapatan({ search }: { search: string }) { - const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan) + const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan); const router = useRouter(); - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - - const formatRupiah = (value: number) => { - return new Intl.NumberFormat('id-ID', { + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + + const { + data, + page, + totalPages, + loading, + load, + } = pendapatanState.findMany; + + const formatRupiah = (value: number) => + new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0, }).format(value); - }; - const handleDelete = () => { if (selectedId) { - pendapatanState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - pendapatanState.findMany.load() + pendapatanState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + load(page, 10, search); } - } + }; useShallowEffect(() => { - pendapatanState.findMany.load(); - }, []) + load(page, 10, search); + }, [page, search]); - const filteredData = (pendapatanState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.value.toString().toLowerCase().includes(keyword) - ); - }); + const filteredData = data || []; - if (!pendapatanState.findMany.data) { + if (loading || !data) { return ( - + - ) + ); } + + const totalValue = filteredData.reduce((total, item) => total + item.value, 0); + return ( - - - - - - Nama - Nilai - Edit - Delete - - - - {filteredData.map((item) => ( - - {item.name} - {formatRupiah(item.value)} - - - - - - + + + Daftar Pendapatan + + + + + + +
+ + + Nama + Nilai + Edit + Delete - ))} - - - Total - - - {formatRupiah(pendapatanState.findMany.data.reduce((total, item) => total + item.value, 0))} - - - -
+ + + {filteredData.length > 0 ? ( + <> + {filteredData.map((item) => ( + + + + {item.name} + + + {formatRupiah(item.value)} + + + + + + + + ))} + + {/* Row total */} + + + Total + + + {formatRupiah(totalValue)} + + + + ) : ( + + +
+ Tidak ada data pendapatan 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 pendapatan ini?' + text="Apakah anda yakin ingin menghapus pendapatan ini?" />
); diff --git a/src/app/admin/(dashboard)/ekonomi/jumlah-penduduk-miskin/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/jumlah-penduduk-miskin/create/page.tsx index c9369275..7845d646 100644 --- a/src/app/admin/(dashboard)/ekonomi/jumlah-penduduk-miskin/create/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/jumlah-penduduk-miskin/create/page.tsx @@ -17,7 +17,7 @@ function CreateJumlahPendudukMiskin() { const resetForm = () => { stateJPM.create.form = { - year: 0, + year: new Date().getFullYear(), // Default to current year totalPoorPopulation: 0, } } @@ -48,10 +48,11 @@ function CreateJumlahPendudukMiskin() { { - stateJPM.create.form.year = Number(val.currentTarget.value); + const value = val.currentTarget.value; + stateJPM.create.form.year = value ? Number(value) : 0; }} /> { - const total = formData.educatedUnemployment + formData.uneducatedUnemployment; - - // Ambil data bulan sebelumnya - const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']; - const currentIndex = monthOrder.findIndex( - (m) => m.toLowerCase() === formData.month.toLowerCase() - ); - - let percentageChange = 0; - if (currentIndex > 0) { - const prevMonth = monthOrder[currentIndex - 1]; - const prev = await stateDetail.findByMonthYear.load({ - month: prevMonth, - year: formData.year, - }); - - if (prev?.totalUnemployment) { - percentageChange = Number( - (((total - prev.totalUnemployment) / prev.totalUnemployment) * 100).toFixed(1) - ); - } - } + const [formData, setFormData] = useState<{ + month: string; + year: number; + educatedUnemployment: number; + uneducatedUnemployment: number; + totalUnemployment: number; + percentageChange: number | null; + }>({ + month: "", + year: new Date().getFullYear(), + educatedUnemployment: 0, + uneducatedUnemployment: 0, + totalUnemployment: 0, + percentageChange: 0, + }); + // Update form data and recalculate totals + const updateFormData = async (updates: Partial) => { + const newData = { ...formData, ...updates }; + const { total, percentageChange } = await calculateTotalAndChange(); + setFormData({ - ...formData, + ...newData, totalUnemployment: total, percentageChange, }); + }; + const calculateTotalAndChange = async () => { + const total = formData.educatedUnemployment + formData.uneducatedUnemployment; + + // Calculate percentage change based on previous month's data + let percentageChange = 0; + const monthOrder = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"]; + const currentMonthIndex = monthOrder.indexOf(formData.month); + + if (currentMonthIndex !== -1) { + let prevMonthIndex = currentMonthIndex - 1; + let prevYear = formData.year; + + if (prevMonthIndex < 0) { + prevMonthIndex = 11; + prevYear--; + } + + const prevMonth = monthOrder[prevMonthIndex]; + + // Get previous month's data + const prevData = await stateDetail.findByMonthYear.load({ + month: prevMonth, + year: prevYear, + }); + + if (prevData && prevData.totalUnemployment > 0) { + const change = ((total - prevData.totalUnemployment) / prevData.totalUnemployment) * 100; + percentageChange = parseFloat(change.toFixed(1)); + } + } + return { total, percentageChange }; }; @@ -60,25 +80,28 @@ function EditDetailDataPengangguran() { const loadDetail = async () => { const id = params?.id as string; if (!id) return; - + try { await stateDetail.findUnique.load(id); // ambil by ID const data = stateDetail.findUnique.data; - + if (data) { - // Convert year from Date to number - const yearValue = data.year instanceof Date ? data.year.getFullYear() : data.year; - + + // Convert year from Date to number if needed + const yearValue = data.year && typeof data.year === 'object' && 'getFullYear' in data.year + ? (data.year as Date).getFullYear() + : Number(data.year); + // Set the ID for update stateDetail.update.id = id; - + // Update Valtio state with converted year - stateDetail.update.form = { + stateDetail.update.form = { ...data, year: yearValue, percentageChange: data.percentageChange || 0 // Ensure it's always a number }; - + // Update local formData with converted year setFormData({ month: data.month, @@ -94,10 +117,9 @@ function EditDetailDataPengangguran() { toast.error("Gagal memuat data detail"); } }; - + loadDetail(); }, [params?.id]); - const handleSubmit = async () => { const { total, percentageChange } = await calculateTotalAndChange(); @@ -107,11 +129,11 @@ function EditDetailDataPengangguran() { totalUnemployment: total, percentageChange, }; - + const success = await stateDetail.update.submit(); if (success) { toast.success("Detail data pengangguran berhasil diperbarui!"); - router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran"); + router.push("/admin/ekonomi/jumlah-pengangguran"); } } catch (error) { console.error("Error updating:", error); @@ -130,41 +152,38 @@ function EditDetailDataPengangguran() { Edit Detail Data Pengangguran - (setFormData({ - ...formData, - month: val.currentTarget.value - }))} + onChange={async (val) => { + await updateFormData({ month: val || "" }); + }} /> - (setFormData({ - ...formData, - year: Number(val.currentTarget.value) - }))} + onChange={async (val) => { + await updateFormData({ year: Number(val) }); + }} /> (setFormData({ - ...formData, - educatedUnemployment: Number(val.currentTarget.value) - }))} + onChange={async (val) => { + const value = Number(val.currentTarget.value) || 0; + await updateFormData({ educatedUnemployment: value }); + }} /> (setFormData({ - ...formData, - uneducatedUnemployment: Number(val.currentTarget.value) - }))} + onChange={async (val) => { + const value = Number(val.currentTarget.value) || 0; + await updateFormData({ uneducatedUnemployment: value }); + }} /> Total Otomatis: {formData.totalUnemployment} diff --git a/src/app/admin/(dashboard)/ekonomi/jumlah-pengangguran/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/jumlah-pengangguran/[id]/page.tsx index 1681bf6f..85c7dffc 100644 --- a/src/app/admin/(dashboard)/ekonomi/jumlah-pengangguran/[id]/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/jumlah-pengangguran/[id]/page.tsx @@ -59,11 +59,16 @@ function DetailJumlahPengangguran() { Perubahan - {stateDetail.findUnique.data?.percentageChange} + + {stateDetail.findUnique.data?.percentageChange !== null && + stateDetail.findUnique.data?.percentageChange !== undefined + ? `${stateDetail.findUnique.data.percentageChange}%` + : 'Tidak ada data perubahan'} + Tahun - {stateDetail.findUnique.data?.year ? new Date(stateDetail.findUnique.data.year).getFullYear() : ''} + {stateDetail.findUnique.data?.year || ''} Bulan @@ -76,14 +81,14 @@ function DetailJumlahPengangguran() { - Tambah Detail Data Pengangguran - - Tambah Data Pengangguran + + setFormData({ ...formData, status: e?.valueOf() as Status })} - label={Status Laporan Publik} - placeholder='Masukkan status LaporanPublik' + onChange={(e) => setFormData({ ...formData, status: e as Status })} + label={Status Laporan Publik} + placeholder="Pilih status laporan publik" data={[ { value: "Selesai", label: "Selesai" }, { value: "Proses", label: "Proses" }, { value: "Gagal", label: "Gagal" }, ]} + required /> + setFormData({ ...formData, kronologi: e.target.value })} - label={Kronologi Laporan Publik} - placeholder='Masukkan kronologi LaporanPublik' + label={Kronologi Laporan Publik} + placeholder="Masukkan kronologi laporan publik" /> - Penanganan Laporan Publik + - Deskripsi Laporan Publik + Penanganan Laporan Publik { @@ -139,8 +165,20 @@ function EditLaporanPublik() { }} /> - - + + + diff --git a/src/app/admin/(dashboard)/keamanan/laporan-publik/[id]/page.tsx b/src/app/admin/(dashboard)/keamanan/laporan-publik/[id]/page.tsx index 08e36f92..ad7d93af 100644 --- a/src/app/admin/(dashboard)/keamanan/laporan-publik/[id]/page.tsx +++ b/src/app/admin/(dashboard)/keamanan/laporan-publik/[id]/page.tsx @@ -1,126 +1,173 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Skeleton, + Stack, + Text, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; import laporanPublikState from '../../../_state/keamanan/laporan-publik'; -// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import { useState } from 'react'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; function DetailLaporanPublik() { - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - const stateLaporan = useProxy(laporanPublikState) - const params = useParams() + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const stateLaporan = useProxy(laporanPublikState); + const params = useParams(); const router = useRouter(); useShallowEffect(() => { - stateLaporan.findUnique.load(params?.id as string) - }, []) + stateLaporan.findUnique.load(params?.id as string); + }, []); const handleDelete = () => { if (selectedId) { - stateLaporan.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/keamanan/laporan-publik") + stateLaporan.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push('/admin/keamanan/laporan-publik'); } - } + }; if (!stateLaporan.findUnique.data) { return ( - + - ) + ); } + + const data = stateLaporan.findUnique.data; + return ( - - - - - - - Detail Laporan Publik - - + + {/* Tombol Kembali */} + + + + + + Detail Laporan Publik + + + + - Judul Laporan Publik - {stateLaporan.findUnique.data?.judul} + Judul Laporan Publik + {data.judul || '-'} + - Tanggal Laporan Publik - - {stateLaporan.findUnique.data?.tanggalWaktu - ? new Date(stateLaporan.findUnique.data.tanggalWaktu).toLocaleString('id-ID') + Tanggal Laporan Publik + + {data.tanggalWaktu + ? new Date(data.tanggalWaktu).toLocaleString('id-ID') : '-'} + - Lokasi - {stateLaporan.findUnique.data?.lokasi} + Lokasi + {data.lokasi || '-'} + - Status - {stateLaporan.findUnique.data?.status} + Status + {data.status || '-'} + - Kronologi - - {stateLaporan.findUnique.data?.kronologi || '-'} - + Kronologi + {data.kronologi || '-'} - Penanganan - {stateLaporan.findUnique.data?.penanganan?.map((item, index) => ( - - Deskripsi Penanganan - - - ))} - {!stateLaporan.findUnique.data?.penanganan?.length && ( - Belum ada penanganan - )} - - + + + + - - + variant="light" + radius="md" + size="md" + > + + + + - {/* Modal Konfirmasi Hapus */} - setModalHapus(false)} - onConfirm={handleDelete} - text='Apakah anda yakin ingin menghapus laporan publik ini?' - /> + + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleDelete} + text="Apakah anda yakin ingin menghapus laporan publik ini?" + /> ); } diff --git a/src/app/admin/(dashboard)/keamanan/laporan-publik/create/page.tsx b/src/app/admin/(dashboard)/keamanan/laporan-publik/create/page.tsx index 2f810e84..1c18852f 100644 --- a/src/app/admin/(dashboard)/keamanan/laporan-publik/create/page.tsx +++ b/src/app/admin/(dashboard)/keamanan/laporan-publik/create/page.tsx @@ -1,101 +1,142 @@ -'use client' +'use client'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Select, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { DateTimePicker } from '@mantine/dates'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; import CreateEditor from '../../../_com/createEditor'; import laporanPublikState from '../../../_state/keamanan/laporan-publik'; -export type Status = "Selesai" | "Proses" | "Gagal"; + +export type Status = 'Selesai' | 'Proses' | 'Gagal'; function CreateLaporanPublik() { - const stateLaporan = useProxy(laporanPublikState) + const stateLaporan = useProxy(laporanPublikState); const router = useRouter(); const resetForm = () => { stateLaporan.create.form = { - judul: "", - lokasi: "", - tanggalWaktu: "", - status: "Proses" as Status, - penanganan: "", - kronologi: "", - } - } + judul: '', + lokasi: '', + tanggalWaktu: '', + status: 'Proses' as Status, + penanganan: '', + kronologi: '', + }; + }; const handleSubmit = async () => { await stateLaporan.create.create(); resetForm(); router.push('/admin/keamanan/laporan-publik'); - } + }; return ( - - - - + + {/* Header with Back Button */} + + + + + + Tambah Laporan Publik + + - - - Create Laporan Publik + {/* Form Card */} + + stateLaporan.create.form.judul = e.target.value} - label={Judul Laporan Publik} - placeholder='Masukkan judul LaporanPublik' + onChange={(e) => (stateLaporan.create.form.judul = e.target.value)} + label={Judul Laporan Publik} + placeholder="Masukkan judul laporan publik" + required /> + stateLaporan.create.form.lokasi = e.target.value} - label={Lokasi Laporan Publik} - placeholder='Masukkan lokasi LaporanPublik' + onChange={(e) => (stateLaporan.create.form.lokasi = e.target.value)} + label={Lokasi Laporan Publik} + placeholder="Masukkan lokasi laporan publik" + required /> + Tanggal Laporan Publik} value={ stateLaporan.create.form.tanggalWaktu ? new Date(stateLaporan.create.form.tanggalWaktu) : null } onChange={(val) => { - if (val) { - stateLaporan.create.form.tanggalWaktu = val.toString(); - } else { - stateLaporan.create.form.tanggalWaktu = ""; // Reset kalau dikosongkan - } + stateLaporan.create.form.tanggalWaktu = val ? val.toString() : ''; }} />