diff --git a/public/mangupuraaward.jpeg b/public/mangupuraaward.jpeg new file mode 100644 index 00000000..22606461 Binary files /dev/null and b/public/mangupuraaward.jpeg differ diff --git a/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts b/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts index 0a452cdd..0cd22ea0 100644 --- a/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts +++ b/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts @@ -6,145 +6,176 @@ import { z } from "zod"; const templateForm = z.object({ name: z.string().min(3, "Nama minimal 3 karakter"), - nik: z.string().min(3, "NIK minimal 3 karakter"), - notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"), + nik: z + .string() + .min(3, "NIK minimal 3 karakter") + .max(16, "NIK maksimal 16 angka"), + notelp: z + .string() + .min(3, "Nomor Telepon minimal 3 karakter") + .max(15, "Nomor Telepon maksimal 15 angka"), alamat: z.string().min(3, "Alamat minimal 3 karakter"), email: z.string().min(3, "Email minimal 3 karakter"), jenisInformasiDimintaId: z.string().nonempty(), caraMemperolehInformasiId: z.string().nonempty(), caraMemperolehSalinanInformasiId: z.string().nonempty(), -}) +}); const jenisInformasiDiminta = proxy({ - findMany: { - data: null as - | null - | Prisma.JenisInformasiDimintaGetPayload<{ omit: { isActive: true } }>[], - async load(){ - const res = await ApiFetch.api.ppid.permohonaninformasipublik.jenisInformasi["find-many"].get(); - if (res.status === 200) { - jenisInformasiDiminta.findMany.data = res.data?.data ?? []; - } - } - } -}) + findMany: { + data: null as + | null + | Prisma.JenisInformasiDimintaGetPayload<{ omit: { isActive: true } }>[], + async load() { + const res = + await ApiFetch.api.ppid.permohonaninformasipublik.jenisInformasi[ + "find-many" + ].get(); + if (res.status === 200) { + jenisInformasiDiminta.findMany.data = res.data?.data ?? []; + } + }, + }, +}); const caraMemperolehInformasi = proxy({ - findMany: { - data: null as - | null - | Prisma.CaraMemperolehInformasiGetPayload<{ omit: { isActive: true } }>[], - async load() { - const res = await ApiFetch.api.ppid.permohonaninformasipublik.memperolehInformasi["find-many"].get(); - if (res.status === 200) { - caraMemperolehInformasi.findMany.data = res.data?.data ?? []; - } - } - } -}) + findMany: { + data: null as + | null + | Prisma.CaraMemperolehInformasiGetPayload<{ + omit: { isActive: true }; + }>[], + async load() { + const res = + await ApiFetch.api.ppid.permohonaninformasipublik.memperolehInformasi[ + "find-many" + ].get(); + if (res.status === 200) { + caraMemperolehInformasi.findMany.data = res.data?.data ?? []; + } + }, + }, +}); const caraMemperolehSalinanInformasi = proxy({ - findMany: { - data: null as - | null - | Prisma.CaraMemperolehSalinanInformasiGetPayload<{ omit: { isActive: true } }>[], - async load() { - const res = await ApiFetch.api.ppid.permohonaninformasipublik.salinanInformasi["find-many"].get(); - if (res.status === 200) { - caraMemperolehSalinanInformasi.findMany.data = res.data?.data ?? []; - } - } - } -}) -console.log(caraMemperolehSalinanInformasi) + findMany: { + data: null as + | null + | Prisma.CaraMemperolehSalinanInformasiGetPayload<{ + omit: { isActive: true }; + }>[], + async load() { + const res = + await ApiFetch.api.ppid.permohonaninformasipublik.salinanInformasi[ + "find-many" + ].get(); + if (res.status === 200) { + caraMemperolehSalinanInformasi.findMany.data = res.data?.data ?? []; + } + }, + }, +}); +console.log(caraMemperolehSalinanInformasi); -type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<{ +type PermohonanInformasiPublikForm = + Prisma.PermohonanInformasiPublikGetPayload<{ select: { - name: true; - nik: true; - notelp: true; - alamat: true; - email: true; - jenisInformasiDimintaId: true; - caraMemperolehInformasiId: true; - caraMemperolehSalinanInformasiId: true; + name: true; + nik: true; + notelp: true; + alamat: true; + email: true; + jenisInformasiDimintaId: true; + caraMemperolehInformasiId: true; + caraMemperolehSalinanInformasiId: true; }; -}>; + }>; const statepermohonanInformasiPublik = proxy({ - create: { - form: {} as PermohonanInformasiPublikForm, - loading: false, - async create(){ - const cek = templateForm.safeParse(statepermohonanInformasiPublik.create.form); - if(!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - return toast.error(err); - } - try { - statepermohonanInformasiPublik.create.loading = true; - const res = await ApiFetch.api.ppid.permohonaninformasipublik["create"].post(statepermohonanInformasiPublik.create.form); - if (res.status === 200) { - statepermohonanInformasiPublik.findMany.load(); - return toast.success("Sukses menambahkan"); - } - return toast.error("failed create"); - } catch (error) { - console.log((error as Error).message); - } finally { - statepermohonanInformasiPublik.create.loading = false; - } + create: { + form: {} as PermohonanInformasiPublikForm, + loading: false, + async create() { + const cek = templateForm.safeParse( + statepermohonanInformasiPublik.create.form + ); + + if (!cek.success) { + toast.error(cek.error.issues.map((i) => i.message).join("\n")); + return false; // ⬅️ tambahkan return false + } + + try { + statepermohonanInformasiPublik.create.loading = true; + const res = await ApiFetch.api.ppid.permohonaninformasipublik[ + "create" + ].post(statepermohonanInformasiPublik.create.form); + + if (res.data?.success === false) { + toast.error(res.data?.message); + return false; // ⬅️ gagal } + + toast.success("Sukses menambahkan"); + return true; // ⬅️ sukses + } catch { + toast.error("Terjadi kesalahan server"); + return false; + } finally { + statepermohonanInformasiPublik.create.loading = false; + } }, - findMany: { - data: null as - | Prisma.PermohonanInformasiPublikGetPayload<{ include: { - caraMemperolehSalinanInformasi: true, - jenisInformasiDiminta: true, - caraMemperolehInformasi: true, - } }>[] - | null, - async load() { - const res = await ApiFetch.api.ppid.permohonaninformasipublik["find-many"].get(); - if (res.status === 200) { - statepermohonanInformasiPublik.findMany.data = res.data?.data ?? []; - } - } - }, - findUnique: { - data: null as Prisma.PermohonanInformasiPublikGetPayload<{ + }, + findMany: { + data: null as + | Prisma.PermohonanInformasiPublikGetPayload<{ include: { - jenisInformasiDiminta: true, - caraMemperolehInformasi: true, - caraMemperolehSalinanInformasi: true, + caraMemperolehSalinanInformasi: true; + jenisInformasiDiminta: true; + caraMemperolehInformasi: true; }; - }> | null, - async load(id: string) { - try { - const res = await fetch(`/api/ppid/permohonaninformasipublik/${id}`); - if (res.ok) { - const data = await res.json(); - statepermohonanInformasiPublik.findUnique.data = data.data ?? null; - } else { - console.error("Failed to fetch program inovasi:", res.statusText); - statepermohonanInformasiPublik.findUnique.data = null; - } - } catch (error) { - console.error("Error fetching program inovasi:", error); - statepermohonanInformasiPublik.findUnique.data = null; - } - }, - }, - -}) + }>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.permohonaninformasipublik[ + "find-many" + ].get(); + if (res.status === 200) { + statepermohonanInformasiPublik.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.PermohonanInformasiPublikGetPayload<{ + include: { + jenisInformasiDiminta: true; + caraMemperolehInformasi: true; + caraMemperolehSalinanInformasi: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/ppid/permohonaninformasipublik/${id}`); + if (res.ok) { + const data = await res.json(); + statepermohonanInformasiPublik.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch program inovasi:", res.statusText); + statepermohonanInformasiPublik.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching program inovasi:", error); + statepermohonanInformasiPublik.findUnique.data = null; + } + }, + }, +}); const statepermohonanInformasiPublikForm = proxy({ - statepermohonanInformasiPublik, - jenisInformasiDiminta, - caraMemperolehInformasi, - caraMemperolehSalinanInformasi, -}) + statepermohonanInformasiPublik, + jenisInformasiDiminta, + caraMemperolehInformasi, + caraMemperolehSalinanInformasi, +}); export default statepermohonanInformasiPublikForm; diff --git a/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts b/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts index fc316fa9..b1545785 100644 --- a/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts +++ b/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts @@ -5,82 +5,99 @@ import { proxy } from "valtio"; import { z } from "zod"; const templateForm = z.object({ - name: z.string().min(3, "Nama minimal 3 karakter"), - email: z.string().min(3, "Email minimal 3 karakter"), - notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"), - alasan: z.string().min(3, "Alasan minimal 3 karakter"), -}) + name: z.string().min(3, "Nama minimal 3 karakter"), + email: z.string().min(3, "Email minimal 3 karakter"), + notelp: z + .string() + .min(3, "Nomor Telepon minimal 3 karakter") + .max(15, "Nomor Telepon maksimal 15 angka"), + alasan: z.string().min(3, "Alasan minimal 3 karakter"), +}); -type PermohonanKeberatanInformasiForm = Prisma.FormulirPermohonanKeberatanGetPayload<{ +type PermohonanKeberatanInformasiForm = + Prisma.FormulirPermohonanKeberatanGetPayload<{ select: { - name: true; - email: true; - notelp: true; - alasan: true; + name: true; + email: true; + notelp: true; + alasan: true; }; -}>; + }>; const permohonanKeberatanInformasi = proxy({ - create: { - form: {} as PermohonanKeberatanInformasiForm, - loading: false, - async create(){ - const cek = templateForm.safeParse(permohonanKeberatanInformasi.create.form); - if(!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - return toast.error(err); - } - try { - permohonanKeberatanInformasi.create.loading = true; - const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["create"].post(permohonanKeberatanInformasi.create.form); - if (res.status === 200) { - permohonanKeberatanInformasi.findMany.load(); - return toast.success("Sukses menambahkan"); - } - return toast.error("failed create"); - } catch (error) { - console.log((error as Error).message); - } finally { - permohonanKeberatanInformasi.create.loading = false; - } - }, - }, - findMany: { - data: null as - | Prisma.FormulirPermohonanKeberatanGetPayload<{omit: {isActive: true}}>[] - | null, - async load() { - const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["find-many"].get(); - if (res.status === 200) { - permohonanKeberatanInformasi.findMany.data = res.data?.data ?? []; - } - } - }, - findUnique: { - data: null as Prisma.FormulirPermohonanKeberatanGetPayload<{ - omit: { - isActive: true; - }; - }> | null, - async load(id: string) { - try { - const res = await fetch(`/api/ppid/permohonankeberataninformasipublik/${id}`); - if (res.ok) { - const data = await res.json(); - permohonanKeberatanInformasi.findUnique.data = data.data ?? null; - } else { - console.error("Failed to fetch permohonan keberatan informasi:", res.statusText); - permohonanKeberatanInformasi.findUnique.data = null; - } - } catch (error) { - console.error("Error fetching permohonan keberatan informasi:", error); - permohonanKeberatanInformasi.findUnique.data = null; - } - }, + create: { + form: {} as PermohonanKeberatanInformasiForm, + loading: false, + async create() { + const cek = templateForm.safeParse( + permohonanKeberatanInformasi.create.form + ); + if (!cek.success) { + toast.error(cek.error.issues.map((i) => i.message).join("\n")); + return false; // ⬅️ tambahkan return false } + try { + permohonanKeberatanInformasi.create.loading = true; + const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik[ + "create" + ].post(permohonanKeberatanInformasi.create.form); + if (res.data?.success === false) { + toast.error(res.data?.message); + return false; // ⬅️ gagal + } + + toast.success("Sukses menambahkan"); + return true; // ⬅️ sukses + } catch { + toast.error("Terjadi kesalahan server"); + return false; + } finally { + permohonanKeberatanInformasi.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.FormulirPermohonanKeberatanGetPayload<{ + omit: { isActive: true }; + }>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik[ + "find-many" + ].get(); + if (res.status === 200) { + permohonanKeberatanInformasi.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.FormulirPermohonanKeberatanGetPayload<{ + omit: { + isActive: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch( + `/api/ppid/permohonankeberataninformasipublik/${id}` + ); + if (res.ok) { + const data = await res.json(); + permohonanKeberatanInformasi.findUnique.data = data.data ?? null; + } else { + console.error( + "Failed to fetch permohonan keberatan informasi:", + res.statusText + ); + permohonanKeberatanInformasi.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching permohonan keberatan informasi:", error); + permohonanKeberatanInformasi.findUnique.data = null; + } + }, + }, }); export default permohonanKeberatanInformasi; - diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_lib/layoutTabs.tsx index 0125ef2e..90270f01 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_lib/layoutTabs.tsx @@ -20,9 +20,9 @@ function LayoutTabs({ children }: { children: React.ReactNode }) { icon: }, { - label: "Grafik Hasil Kepuasan Masyarakat", - value: "grafikhasilkepuasan", - href: "/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan", + label: "Penderita Penyakit", + value: "penderitapenyakit", + href: "/admin/kesehatan/data-kesehatan-warga/penderita_penyakit", icon: }, { diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/[id]/edit/page.tsx similarity index 91% rename from src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/[id]/edit/page.tsx rename to src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/[id]/edit/page.tsx index 070d52f0..f135f7b2 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/[id]/edit/page.tsx @@ -70,8 +70,8 @@ function EditGrafikHasilKepuasan() { }); } } catch (err) { - console.error("Error loading grafik hasil kepuasan:", err); - toast.error("Gagal memuat data grafik hasil kepuasan"); + console.error("Error loading penderita penyakit:", err); + toast.error("Gagal memuat data penderita penyakit"); } }; @@ -99,11 +99,11 @@ function EditGrafikHasilKepuasan() { setIsSubmitting(true); editState.update.form = { ...editState.update.form, ...formData }; await editState.update.submit(); - toast.success('Grafik hasil kepuasan berhasil diperbarui!'); - router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan'); + toast.success('penderita penyakit berhasil diperbarui!'); + router.push('/admin/kesehatan/data-kesehatan-warga/penderita_penyakit'); } catch (err) { - console.error('Error updating grafik hasil kepuasan:', err); - toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan'); + console.error('Error updating penderita penyakit:', err); + toast.error('Terjadi kesalahan saat memperbarui penderita penyakit'); } finally { setIsSubmitting(false); } @@ -122,7 +122,7 @@ function EditGrafikHasilKepuasan() { - Edit Grafik Hasil Kepuasan + Edit Penderita Penyakit diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/[id]/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/[id]/page.tsx similarity index 94% rename from src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/[id]/page.tsx rename to src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/[id]/page.tsx index 738e64fb..83211062 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/[id]/page.tsx @@ -26,7 +26,7 @@ function DetailGrafikHasilKepuasan() { state.delete.byId(selectedId); setModalHapus(false); setSelectedId(null); - router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan"); + router.push("/admin/kesehatan/data-kesehatan-warga/penderita_penyakit"); } }; @@ -63,7 +63,7 @@ function DetailGrafikHasilKepuasan() { > - Detail Data Grafik Hasil Kepuasan + Detail Data Penderita Penyakit @@ -118,7 +118,7 @@ function DetailGrafikHasilKepuasan() { color="green" onClick={() => router.push( - `/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${data.id}/edit` + `/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/${data.id}/edit` ) } variant="light" diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/create/page.tsx similarity index 97% rename from src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create/page.tsx rename to src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/create/page.tsx index d1ea8b7f..7cbfb5a8 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/create/page.tsx @@ -40,7 +40,7 @@ function CreateGrafikHasilKepuasanMasyarakat() { setIsSubmitting(true); await stateGrafikKepuasan.create.create(); resetForm(); - router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan"); + router.push("/admin/kesehatan/data-kesehatan-warga/penderita_penyakit"); } catch (error) { console.error("Error creating grafik kepuasan:", error); toast.error("Terjadi kesalahan saat membuat grafik kepuasan"); @@ -62,7 +62,7 @@ function CreateGrafikHasilKepuasanMasyarakat() { - Tambah Grafik Hasil Kepuasan Masyarakat + Tambah Penderita Penyakit diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/page.tsx similarity index 95% rename from src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/page.tsx rename to src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/page.tsx index e5ba5b28..884b476f 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/penderita_penyakit/page.tsx @@ -36,7 +36,7 @@ function GrafikHasilKepuasanMasyarakat() { {/* Header Search */} } value={search} @@ -115,14 +115,14 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) { {/* Judul + Tombol Tambah */} - Daftar Grafik Hasil Kepuasan Masyarakat + Daftar Penderita Penyakit - - + - {Math.round(scale * 100)}% - + } + style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil + > + Zoom Out + - + + {Math.round(scale * 100)}% + - + } + style={{ flexShrink: 0 }} + > + Zoom In + - - - + + Reset + + + + ) : ( + + ) + } + style={{ flexShrink: 0 }} + > + {isFullscreen ? 'Exit' : 'Fullscreen'} + + + + {/* 🧩 Chart Container */} @@ -325,15 +363,20 @@ function StrukturPerangkatDesaNode() { maxWidth: '100%', padding: '32px 16px', transition: 'transform 0.2s ease', - transform: `scale(${scale})`, - transformOrigin: 'center top', }} > - } - className="p-organizationchart p-organizationchart-horizontal" - /> + + } + className="p-organizationchart p-organizationchart-horizontal" + /> + @@ -345,6 +388,7 @@ function NodeCard({ node, router }: any) { const name = node?.data?.name || 'Tanpa Nama' const title = node?.data?.title || 'Tanpa Jabatan' const hasId = Boolean(node?.data?.id) + const isMobile = useMediaQuery("(max-width: 768px)"); return ( @@ -355,9 +399,10 @@ function NodeCard({ node, router }: any) { withBorder style={{ ...styles, - width: 240, - minHeight: 280, - padding: 20, + width: '100%', + maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile + minHeight: isMobile ? 240 : 280, + padding: isMobile ? 16 : 20, background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)', borderColor: 'rgba(28, 110, 164, 0.3)', borderWidth: 2, diff --git a/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/page.tsx b/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/page.tsx index 7507e6ce..1eee1a31 100644 --- a/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/page.tsx @@ -26,7 +26,7 @@ function Page() { const state = useProxy(lowonganKerjaState) const router = useRouter() const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const { data, diff --git a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx index 1aef38f4..41d388d0 100644 --- a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx @@ -14,7 +14,7 @@ function Page() { const router = useRouter() const state = useProxy(pasarDesaState.pasarDesa) const [search, setSearch] = useState(''); - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [selectedCategory, setSelectedCategory] = useState(null); const { data, diff --git a/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx b/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx index 0cadea32..f7a91480 100644 --- a/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx @@ -32,7 +32,7 @@ interface ProgramKemiskinanData { function Page() { const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const state = useProxy(programKemiskinanState) // 🔧 Get valid statistics data with proper type checking diff --git a/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx b/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx index c881fce5..8ea05d2f 100644 --- a/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx +++ b/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx @@ -11,7 +11,7 @@ import { useRouter } from 'next/navigation'; function Page() { const [search, setSearch] = useState("") - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const state = useProxy(desaDigitalState) const router = useRouter() const { diff --git a/src/app/darmasaba/(pages)/inovasi/info-teknologi-tepat-guna/page.tsx b/src/app/darmasaba/(pages)/inovasi/info-teknologi-tepat-guna/page.tsx index e08d86a2..dc69f126 100644 --- a/src/app/darmasaba/(pages)/inovasi/info-teknologi-tepat-guna/page.tsx +++ b/src/app/darmasaba/(pages)/inovasi/info-teknologi-tepat-guna/page.tsx @@ -11,7 +11,7 @@ import { IconSearch } from '@tabler/icons-react'; function Page() { const [search, setSearch] = useState("") - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const state = useProxy(infoTeknoState) const { data, diff --git a/src/app/darmasaba/(pages)/inovasi/program-kreatif-desa/page.tsx b/src/app/darmasaba/(pages)/inovasi/program-kreatif-desa/page.tsx index 7cc283cd..a731d9f4 100644 --- a/src/app/darmasaba/(pages)/inovasi/program-kreatif-desa/page.tsx +++ b/src/app/darmasaba/(pages)/inovasi/program-kreatif-desa/page.tsx @@ -45,7 +45,7 @@ import BackButton from '../../desa/layanan/_com/BackButto'; function Page() { const listState = useProxy(programKreatifState); const [search, setSearch] = useState(""); - const [debouncedSearch] = useDebouncedValue(search, 500); + const [debouncedSearch] = useDebouncedValue(search, 1000); const router = useTransitionRouter() const { data, diff --git a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx index b7fa9c11..1bc6146f 100644 --- a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx @@ -14,7 +14,7 @@ function Page() { const state = useProxy(keamananLingkunganState) const router = useRouter() const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const { data, page, diff --git a/src/app/darmasaba/(pages)/keamanan/kontak-darurat/page.tsx b/src/app/darmasaba/(pages)/keamanan/kontak-darurat/page.tsx index 13281bce..a10edc78 100644 --- a/src/app/darmasaba/(pages)/keamanan/kontak-darurat/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/kontak-darurat/page.tsx @@ -12,7 +12,7 @@ import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap'; function Page() { const kontakState = useProxy(kontakDarurat.kontakDaruratKeamananState); const [search, setSearch] = useState(""); - const [debouncedSearch] = useDebouncedValue(search, 500); + const [debouncedSearch] = useDebouncedValue(search, 1000); const { data, page, diff --git a/src/app/darmasaba/(pages)/keamanan/laporan-publik/page.tsx b/src/app/darmasaba/(pages)/keamanan/laporan-publik/page.tsx index fc7b6f82..05cce9e2 100644 --- a/src/app/darmasaba/(pages)/keamanan/laporan-publik/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/laporan-publik/page.tsx @@ -1,10 +1,26 @@ 'use client' + import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik'; import colors from '@/con/colors'; -import { Box, Button, Center, ColorSwatch, Flex, Group, Modal, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; +import { + Box, + Button, + Center, + ColorSwatch, + Flex, + Group, + Modal, + Pagination, + Paper, + SimpleGrid, + Skeleton, + Stack, + Text, + TextInput, +} from '@mantine/core'; import { DateTimePicker } from '@mantine/dates'; -import { useDebouncedValue, useDisclosure, useShallowEffect } from '@mantine/hooks'; +import { useDebouncedValue, useDisclosure, useMediaQuery, useShallowEffect } from '@mantine/hooks'; import { IconArrowRight, IconPlus, IconSearch } from '@tabler/icons-react'; import { useTransitionRouter } from 'next-view-transitions'; import { useState } from 'react'; @@ -12,9 +28,10 @@ import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; function Page() { - const [search, setSearch] = useState(""); - const router = useTransitionRouter() - const [debouncedSearch] = useDebouncedValue(search, 500); + const mobile = useMediaQuery('(max-width: 768px)'); + const [search, setSearch] = useState(''); + const router = useTransitionRouter(); + const [debouncedSearch] = useDebouncedValue(search, 1000); const [opened, { open, close }] = useDisclosure(false); const stateLaporan = useProxy(laporanPublikState); const { @@ -49,143 +66,219 @@ function Page() { const handleSubmit = async () => { await stateLaporan.create.create(); resetForm(); + close(); }; + return ( - + + {/* Header: Back + Search */} setSearch(e.target.value)} - leftSection={} - w={{ base: "100%", md: "30%" }} - /> + radius="lg" + placeholder="Cari Laporan Publik" + value={search} + onChange={(e) => setSearch(e.target.value)} + leftSection={} + w={{ base: '100%', md: '30%' }} + size={mobile ? 'sm' : 'md'} + /> + + {/* Title + Add Button */} - - + + Laporan Keamanan Lingkungan - - - - Laporan Terbaru - - - - - Terselesaikan - - - - - - Dalam Proses - - - - - - Gagal - - - - - - - + + + Laporan Terbaru + + - {data.map((v, k) => { - return ( - - - {v.judul} - - {v.tanggalWaktu - ? new Date(v.tanggalWaktu).toLocaleString('id-ID') - : '-'} - - - Penanganan: - {v.penanganan?.length ? ( - v.penanganan.map((item, index) => ( - - - - )) - ) : ( - - Belum ada penanganan - - )} - - - {v.status} - - - - - ) - })} - -
- load(newPage)} - total={totalPages} - my="md" - /> -
-
+ + + Terselesaikan + + + + Dalam Proses + + + + Gagal + + +
- + + {/* Cards Grid */} + + + {data.map((v, k) => ( + + + + {v.judul} + + + + {v.tanggalWaktu + ? new Date(v.tanggalWaktu).toLocaleString('id-ID') + : '-'} + + + + + Penanganan: + + {v.penanganan?.length ? ( + v.penanganan.map((item, index) => ( + + + + )) + ) : ( + + Belum ada penanganan + + )} + + + + {v.status} + + + + + + ))} + + + + {/* Pagination */} +
+ load(newPage)} + total={totalPages} + my="md" + size={mobile ? 'sm' : 'md'} + /> +
+ + {/* Modal Form */} + (stateLaporan.create.form.judul = e.target.value)} + onChange={(e) => + (stateLaporan.create.form.judul = e.target.value) + } label={Judul Laporan Publik} placeholder="Masukkan judul laporan publik" required + w="100%" + size={mobile ? 'sm' : 'md'} /> (stateLaporan.create.form.lokasi = e.target.value)} + onChange={(e) => + (stateLaporan.create.form.lokasi = e.target.value) + } label={Lokasi Laporan Publik} placeholder="Masukkan lokasi laporan publik" required + w="100%" + size={mobile ? 'sm' : 'md'} /> { stateLaporan.create.form.tanggalWaktu = val ? val.toString() : ''; }} + w="100%" + size={mobile ? 'sm' : 'md'} /> @@ -238,7 +341,7 @@ function Page() { + + {/* Wrapper Detail */} + + + + Detail Kontak Darurat + + + + + + Judul + {data.name || '-'} + + + + Whatsapp + {data.whatsapp || '-'} + + + + Deskripsi + + + + + Gambar + {data.image?.link ? ( + gambar + ) : ( + - + )} + + + + + + + + + + ); +} + +export default Page; diff --git a/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/page.tsx b/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/page.tsx index 0e7913f3..3967e3a1 100644 --- a/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/page.tsx +++ b/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/page.tsx @@ -7,6 +7,7 @@ import { Center, Grid, GridCol, + Group, Image, Pagination, Paper, @@ -17,17 +18,18 @@ import { TextInput, Tooltip } from '@mantine/core'; -import { useShallowEffect } from '@mantine/hooks'; -import { IconBrandWhatsapp, IconSearch } from '@tabler/icons-react'; +import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; +import { IconSearch } from '@tabler/icons-react'; +import { useTransitionRouter } from 'next-view-transitions'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; -import { useDebouncedValue } from '@mantine/hooks'; function Page() { const state = useProxy(kontakDarurat); + const router = useTransitionRouter() const [search, setSearch] = useState(''); - const [debouncedSearch] = useDebouncedValue(search, 500) + const [debouncedSearch] = useDebouncedValue(search, 1000) const { data, page, totalPages, loading, load } = state.findMany; useShallowEffect(() => { @@ -88,83 +90,79 @@ function Page() { ) : ( {data.map((v, k) => ( - - - - {v.name} (e.currentTarget.style.transform = 'scale(1.05)')} - onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')} - /> - - - - {v.name} - - - - - - - - {/* ✅ Tombol selalu di bagian bawah card */} -
- -
-
- - + + + + {v.name} (e.currentTarget.style.transform = 'scale(1.05)')} + onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')} + /> + + + + {v.name} + + + + + + + + {/* ✅ Tombol selalu di bagian bawah card */} + + + + + + ))}
)} diff --git a/src/app/darmasaba/(pages)/kesehatan/penanganan-darurat/page.tsx b/src/app/darmasaba/(pages)/kesehatan/penanganan-darurat/page.tsx index d08080c8..a910dc68 100644 --- a/src/app/darmasaba/(pages)/kesehatan/penanganan-darurat/page.tsx +++ b/src/app/darmasaba/(pages)/kesehatan/penanganan-darurat/page.tsx @@ -26,7 +26,7 @@ import BackButton from '../../desa/layanan/_com/BackButto' function Page() { const state = useProxy(penangananDarurat) const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 500) + const [debouncedSearch] = useDebouncedValue(search, 1000) const { data, page, totalPages, loading, load } = state.findMany useShallowEffect(() => { diff --git a/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx b/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx index 1048dcb2..c567f34f 100644 --- a/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx +++ b/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx @@ -12,7 +12,7 @@ import { useTransitionRouter } from "next-view-transitions"; export default function Page() { const state = useProxy(posyandustate); const [search, setSearch] = useState(""); - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const router = useTransitionRouter() const { data, page, totalPages, loading, load } = state.findMany; diff --git a/src/app/darmasaba/(pages)/kesehatan/program-kesehatan/page.tsx b/src/app/darmasaba/(pages)/kesehatan/program-kesehatan/page.tsx index ed5a360f..e95b6d30 100644 --- a/src/app/darmasaba/(pages)/kesehatan/program-kesehatan/page.tsx +++ b/src/app/darmasaba/(pages)/kesehatan/program-kesehatan/page.tsx @@ -57,7 +57,7 @@ export default function Page() { const state = useProxy(programKesehatan); const router = useRouter(); const [search, setSearch] = useState(""); - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const { data, page, totalPages, loading, load } = state.findMany; useShallowEffect(() => { diff --git a/src/app/darmasaba/(pages)/kesehatan/puskesmas/page.tsx b/src/app/darmasaba/(pages)/kesehatan/puskesmas/page.tsx index ed15017d..40f5d755 100644 --- a/src/app/darmasaba/(pages)/kesehatan/puskesmas/page.tsx +++ b/src/app/darmasaba/(pages)/kesehatan/puskesmas/page.tsx @@ -7,10 +7,12 @@ import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; +import { useDebouncedValue } from '@mantine/hooks'; function Page() { const state = useProxy(puskesmasState) const [search, setSearch] = useState('') + const [debouncedSearch] = useDebouncedValue(search, 1000); const { data, @@ -21,8 +23,8 @@ function Page() { } = state.findMany; useShallowEffect(() => { - load(page, 6, search) - }, [page, search]) + load(page, 6, debouncedSearch) + }, [page, debouncedSearch]) if (loading || !data) { return ( @@ -95,17 +97,17 @@ function Page() { - + {v.alamat} - + {v.kontak.kontakPuskesmas} - + {v.kontak.email} diff --git a/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx b/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx index 595a35a7..ea753870 100644 --- a/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx @@ -11,7 +11,7 @@ import BackButton from '../../desa/layanan/_com/BackButto'; function Page() { const state = useProxy(dataLingkunganDesaState.findMany) const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const { data, diff --git a/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx b/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx index 92fe9f3a..3e5c1d5b 100644 --- a/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx @@ -49,6 +49,7 @@ export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: Edu
- + Jenis Informasi @@ -96,7 +96,7 @@ export default function DetailInformasiPublikUser() { - + Tanggal Publikasi @@ -111,15 +111,19 @@ export default function DetailInformasiPublikUser() { - + Deskripsi - + + +
diff --git a/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/page.tsx b/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/page.tsx index 559ebe9f..9b9e12e3 100644 --- a/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/page.tsx @@ -33,7 +33,7 @@ import { useTransitionRouter } from 'next-view-transitions'; function Page() { const listData = useProxy(daftarInformasiPublik) const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const router = useTransitionRouter() const { data, diff --git a/src/app/darmasaba/(pages)/ppid/dasar-hukum/page.tsx b/src/app/darmasaba/(pages)/ppid/dasar-hukum/page.tsx index b9d64d1f..7b66459c 100644 --- a/src/app/darmasaba/(pages)/ppid/dasar-hukum/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/dasar-hukum/page.tsx @@ -31,7 +31,11 @@ function Page() { - + Dasar Hukum - + Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum @@ -71,12 +75,15 @@ function Page() { { state.create.form.name = val.currentTarget.value; @@ -607,7 +609,7 @@ const state = useProxy(indeksKepuasanState.responden); { state.create.form.tanggal = val.currentTarget.value; diff --git a/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx b/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx index 5a47ba67..6bd6ebd1 100644 --- a/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx @@ -53,23 +53,11 @@ function Page() { const permohonanInformasiPublikState = useProxy(statePermohonanInformasi); const router = useRouter(); - const submitForms = () => { + const submitForms = async () => { const { create } = permohonanInformasiPublikState.statepermohonanInformasiPublik; - - if ( - create.form.name && - create.form.nik && - create.form.notelp && - create.form.alamat && - create.form.email && - create.form.jenisInformasiDimintaId && - create.form.caraMemperolehInformasiId && - create.form.caraMemperolehSalinanInformasiId - ) { - create.create(); + const hasil = await create.create(); // tunggu hasilnya + if (hasil) { router.push('/darmasaba/permohonan/berhasil'); - } else { - console.log('Validasi gagal, form tidak lengkap'); } }; diff --git a/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx b/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx index 4ef775be..871fd6f5 100644 --- a/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx @@ -55,17 +55,13 @@ function Page() { const stateKeberatan = useProxy(permohonanKeberatanInformasi); const router = useRouter(); - const submit = () => { - if ( - stateKeberatan.create.form.name && - stateKeberatan.create.form.email && - stateKeberatan.create.form.notelp && - stateKeberatan.create.form.alasan - ) { - stateKeberatan.create.create(); + const submit = async () => { + const { create } = stateKeberatan; + + const hasil = await create.create(); // tunggu hasilnya + + if (hasil) { router.push('/darmasaba/permohonan/berhasil'); - } else { - console.log('Formulir belum lengkap'); } }; @@ -190,7 +186,7 @@ function Page() { Biografi - + + + Riwayat Karir - + + + + + diff --git a/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx b/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx index 1d921cab..c14914d8 100644 --- a/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx @@ -14,6 +14,9 @@ import { Loader, Paper, Stack, + Tabs, + TabsList, + TabsTab, Text, TextInput, Title, @@ -35,6 +38,7 @@ import { useEffect, useRef, useState } from 'react' import { useProxy } from 'valtio/utils' import BackButton from '../../desa/layanan/_com/BackButto' import './struktur.css' +import { useMediaQuery } from '@mantine/hooks' export default function Page() { return ( @@ -231,87 +235,121 @@ function StrukturOrganisasiPPID() { p="md" radius="md" style={{ - background: colors['blue-button'] + background: colors['blue-button'], + width: '100%', // ⬅️ penting + maxWidth: '100%', // ⬅️ penting + overflowX: 'auto' // ⬅️ untuk mencegah overflow }} > - - } - onChange={(e) => debouncedSearch(e.target.value)} + + + + } + onChange={(e) => debouncedSearch(e.target.value)} + styles={{ + input: { + minWidth: 250, + }, + }} + /> + + - - - - - + - {Math.round(scale * 100)}% - + } + style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil + > + Zoom Out + - + + {Math.round(scale * 100)}% + - + } + style={{ flexShrink: 0 }} + > + Zoom In + - - - + + Reset + + + + ) : ( + + ) + } + style={{ flexShrink: 0 }} + > + {isFullscreen ? 'Exit' : 'Fullscreen'} + + + + {/* 🧩 Chart Container */} @@ -325,15 +363,20 @@ function StrukturOrganisasiPPID() { maxWidth: '100%', padding: '32px 16px', transition: 'transform 0.2s ease', - transform: `scale(${scale})`, - transformOrigin: 'center top', }} > - } - className="p-organizationchart p-organizationchart-horizontal" - /> + + } + className="p-organizationchart p-organizationchart-horizontal" + /> + @@ -345,6 +388,7 @@ function NodeCard({ node, router }: any) { const name = node?.data?.name || 'Tanpa Nama' const title = node?.data?.title || 'Tanpa Jabatan' const hasId = Boolean(node?.data?.id) + const isMobile = useMediaQuery("(max-width: 768px)"); return ( @@ -355,9 +399,10 @@ function NodeCard({ node, router }: any) { withBorder style={{ ...styles, - width: 240, - minHeight: 280, - padding: 20, + width: '100%', + maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile + minHeight: isMobile ? 240 : 280, + padding: isMobile ? 16 : 20, background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)', borderColor: 'rgba(28, 110, 164, 0.3)', borderWidth: 2, @@ -411,6 +456,7 @@ function NodeCard({ node, router }: any) { c={colors['blue-button']} lineClamp={2} style={{ + // fontSize: 'clamp(12px, 4vw, 16px)', // 👈 responsif font size minHeight: 40, display: 'flex', alignItems: 'center', diff --git a/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx b/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx index 85e50080..43440556 100644 --- a/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx @@ -75,7 +75,7 @@ function Page() { lh={1.7} ta="center" dangerouslySetInnerHTML={{ __html: item.visi }} - style={{wordBreak: "break-word", whiteSpace: "normal"}} + style={{ wordBreak: "break-word", whiteSpace: "normal" }} /> @@ -86,12 +86,15 @@ function Page() { c={colors['blue-button']} mb="sm"> Misi PPID - + + + diff --git a/src/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress.tsx b/src/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress.tsx index 71825fff..8b9b3e35 100644 --- a/src/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress.tsx +++ b/src/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress.tsx @@ -55,8 +55,8 @@ function APBDesProgress({ apbdesData }: APBDesProgressProps) { return ( - {label} - + {label} + {formatRupiah(dataset.realisasi)} | {formatRupiah(dataset.anggaran)} (null); - const scrollPositionRef = useRef(0); - const animationFrameRef = useRef(0); + const scrollPosRef = useRef(0); + const animFrameRef = useRef(0); const isHoveredRef = useRef(false); - - // Refs for drag functionality const isDraggingRef = useRef(false); const startXRef = useRef(0); const scrollLeftRef = useRef(0); const velocityRef = useRef(0); - const lastScrollTimeRef = useRef(0); + const lastScrollRef = useRef(0); - // Speed configuration - const normalSpeed = 1.0; // pixels per frame - const hoverSpeed = 0.5; // slower speed on hover + const SPEED_NORMAL = 1.0; + const SPEED_HOVER = 0.5; + const VELOCITY_DECAY = 0.95; + const SCROLL_THRESHOLD = 100; useEffect(() => { state.findMany.load(); @@ -63,120 +61,114 @@ function Slider() { const data = state.findMany.data || []; const loading = state.findMany.loading; - // Duplicate slides for seamless infinite loop - const slidesData = [...data, ...data, ...data]; + // Triple data untuk infinite loop (desktop only) + const slidesData = mobile ? data : [...data, ...data, ...data]; + // Auto-scroll animation untuk desktop useEffect(() => { - if (loading || !containerRef.current || slidesData.length === 0) return; + if (loading || !containerRef.current || data.length === 0 || mobile) return; const container = containerRef.current; const slideWidth = container.scrollWidth / slidesData.length; - const originalDataLength = data.length; + const originalLength = data.length; - // Start from the middle set of slides - scrollPositionRef.current = slideWidth * originalDataLength; - container.scrollLeft = scrollPositionRef.current; + // Start dari middle set + scrollPosRef.current = slideWidth * originalLength; + container.scrollLeft = scrollPosRef.current; const animate = () => { if (!containerRef.current) return; const container = containerRef.current; const slideWidth = container.scrollWidth / slidesData.length; + const timeSinceScroll = Date.now() - lastScrollRef.current; + const isUserScrolling = timeSinceScroll < SCROLL_THRESHOLD; - // Check if user recently scrolled manually - const timeSinceLastScroll = Date.now() - lastScrollTimeRef.current; - const isUserScrolling = timeSinceLastScroll < 100; - - // Only auto-scroll if user is not actively scrolling or dragging if (!isDraggingRef.current && !isUserScrolling) { - const currentSpeed = isHoveredRef.current ? hoverSpeed : normalSpeed; - scrollPositionRef.current += currentSpeed; + const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL; + scrollPosRef.current += speed; - // Reset position for infinite loop - if (scrollPositionRef.current >= slideWidth * (originalDataLength * 2)) { - scrollPositionRef.current -= slideWidth * originalDataLength; + // Reset untuk infinite loop + if (scrollPosRef.current >= slideWidth * (originalLength * 2)) { + scrollPosRef.current -= slideWidth * originalLength; + } + if (scrollPosRef.current <= 0) { + scrollPosRef.current += slideWidth * originalLength; } - if (scrollPositionRef.current <= 0) { - scrollPositionRef.current += slideWidth * originalDataLength; - } - - container.scrollLeft = scrollPositionRef.current; + container.scrollLeft = scrollPosRef.current; } else { - // Sync scroll position when user is scrolling - scrollPositionRef.current = container.scrollLeft; - - // Apply momentum/velocity for smooth drag release + scrollPosRef.current = container.scrollLeft; + + // Momentum untuk drag release if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) { - scrollPositionRef.current += velocityRef.current; - velocityRef.current *= 0.95; // Decay velocity - container.scrollLeft = scrollPositionRef.current; + scrollPosRef.current += velocityRef.current; + velocityRef.current *= VELOCITY_DECAY; + container.scrollLeft = scrollPosRef.current; } } - animationFrameRef.current = requestAnimationFrame(animate); + animFrameRef.current = requestAnimationFrame(animate); }; - animationFrameRef.current = requestAnimationFrame(animate); + animFrameRef.current = requestAnimationFrame(animate); return () => { - if (animationFrameRef.current) { - cancelAnimationFrame(animationFrameRef.current); + if (animFrameRef.current) { + cancelAnimationFrame(animFrameRef.current); } }; - }, [loading, slidesData.length, data.length, mobile]); + }, [loading, data.length, mobile]); const handleMouseEnter = () => { - isHoveredRef.current = true; + if (!mobile) isHoveredRef.current = true; }; const handleMouseLeave = () => { - isHoveredRef.current = false; - isDraggingRef.current = false; + if (!mobile) { + isHoveredRef.current = false; + isDraggingRef.current = false; + } }; - // Mouse drag handlers const handleMouseDown = (e: React.MouseEvent) => { - if (!containerRef.current) return; - + if (!containerRef.current || mobile) return; + isDraggingRef.current = true; startXRef.current = e.pageX - containerRef.current.offsetLeft; scrollLeftRef.current = containerRef.current.scrollLeft; velocityRef.current = 0; - containerRef.current.style.cursor = 'grabbing'; }; const handleMouseMove = (e: React.MouseEvent) => { - if (!isDraggingRef.current || !containerRef.current) return; - + if (!isDraggingRef.current || !containerRef.current || mobile) return; + e.preventDefault(); const x = e.pageX - containerRef.current.offsetLeft; const walk = (x - startXRef.current) * 2; const newScrollLeft = scrollLeftRef.current - walk; - + velocityRef.current = containerRef.current.scrollLeft - newScrollLeft; - containerRef.current.scrollLeft = newScrollLeft; - scrollPositionRef.current = newScrollLeft; - lastScrollTimeRef.current = Date.now(); + scrollPosRef.current = newScrollLeft; + lastScrollRef.current = Date.now(); }; const handleMouseUp = () => { - if (!containerRef.current) return; - + if (!containerRef.current || mobile) return; + isDraggingRef.current = false; containerRef.current.style.cursor = 'grab'; }; - // Wheel scroll handler const handleWheel = (e: React.WheelEvent) => { - if (!containerRef.current) return; - + if (!containerRef.current || mobile) return; + e.preventDefault(); containerRef.current.scrollLeft += e.deltaY; - scrollPositionRef.current = containerRef.current.scrollLeft; - lastScrollTimeRef.current = Date.now(); + scrollPosRef.current = containerRef.current.scrollLeft; + lastScrollRef.current = Date.now(); }; if (loading) { @@ -211,37 +203,45 @@ function Slider() { onWheel={handleWheel} py="xl" style={{ - overflow: "hidden", - cursor: "grab", + overflowX: mobile ? "auto" : "hidden", + overflowY: "hidden", + cursor: mobile ? "default" : "grab", userSelect: "none", position: "relative", + WebkitOverflowScrolling: "touch", + scrollbarWidth: "none", + msOverflowStyle: "none", }} > - {/* Blur edges effect */} - - + {/* Blur edges - hanya untuk desktop */} + {!mobile && ( + <> + + + + )} { - e.currentTarget.style.transform = "translateY(-8px) scale(1.02)"; - e.currentTarget.style.boxShadow = "0 12px 28px rgba(0,0,0,0.25)"; + if (!mobile) { + e.currentTarget.style.transform = "translateY(-8px) scale(1.02)"; + e.currentTarget.style.boxShadow = "0 12px 28px rgba(0,0,0,0.25)"; + } }} onMouseLeave={(e) => { - e.currentTarget.style.transform = "translateY(0) scale(1)"; - e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)"; + if (!mobile) { + e.currentTarget.style.transform = "translateY(0) scale(1)"; + e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)"; + } }} > void; // ✅ TAMBAHAN + hasNewContent?: boolean; + newItemCount?: number; + onSeen?: () => void; autoShowDelay?: number; } +// === Helper === function stripHtml(html: string): string { return html - .replace(/<[^>]+>/g, '') - .replace(/ /gi, ' ') - .replace(/&/gi, '&') - .replace(/\s+/g, ' ') + .replace(/<[^>]+>/g, "") + .replace(/ /gi, " ") + .replace(/&/gi, "&") + .replace(/\s+/g, " ") .trim(); } +// === Komponen Utama === export default function ModernNewsNotification({ news = [], hasNewContent = false, - newItemCount = 0, // 👈 tambahkan ini + newItemCount = 0, onSeen, autoShowDelay = 2000, }: ModernNewsNotificationProps) { const router = useRouter(); + const pathname = usePathname(); + const [toastVisible, setToastVisible] = useState(false); const [widgetOpen, setWidgetOpen] = useState(false); const [hasNewNotifications, setHasNewNotifications] = useState(hasNewContent); const [hasShownToast, setHasShownToast] = useState(false); const [iconVisible, setIconVisible] = useState(true); - const pathname = usePathname(); - // Sinkronisasi dari luar + // Sinkronisasi prop eksternal useEffect(() => { - if (hasNewContent) { - setHasNewNotifications(true); - // Jangan otomatis tampilkan toast di sini — biarkan saat page load saja - } + setHasNewNotifications(hasNewContent); }, [hasNewContent]); - // Auto show toast hanya saat page pertama kali load + // Tampilkan toast pertama kali useEffect(() => { if (news.length > 0 && !toastVisible && !hasShownToast) { const timer = setTimeout(() => { setToastVisible(true); setHasShownToast(true); - // Jika ada new content, anggap sudah "dilihat" setelah toast muncul - if (hasNewNotifications) { - onSeen?.(); - } + if (hasNewNotifications) onSeen?.(); }, autoShowDelay); return () => clearTimeout(timer); } }, [news.length, autoShowDelay, toastVisible, hasShownToast, hasNewNotifications, onSeen]); - // Auto hide toast + // Sembunyikan toast otomatis useEffect(() => { if (toastVisible) { const timer = setTimeout(() => setToastVisible(false), 8000); @@ -76,7 +96,7 @@ export default function ModernNewsNotification({ } }, [toastVisible]); - // Scroll handler + // Kontrol visibilitas ikon saat scroll useEffect(() => { let lastScrollY = window.scrollY; const HIDE_THRESHOLD = 100; @@ -84,11 +104,11 @@ export default function ModernNewsNotification({ const handleScroll = () => { const currentScrollY = window.scrollY; - const scrollDirection = currentScrollY > lastScrollY ? 'down' : 'up'; + const scrollDirection = currentScrollY > lastScrollY ? "down" : "up"; - if (scrollDirection === 'down' && currentScrollY > HIDE_THRESHOLD) { + if (scrollDirection === "down" && currentScrollY > HIDE_THRESHOLD) { setIconVisible(false); - } else if (scrollDirection === 'up' && currentScrollY < SHOW_THRESHOLD) { + } else if (scrollDirection === "up" && currentScrollY < SHOW_THRESHOLD) { setIconVisible(true); } @@ -99,19 +119,25 @@ export default function ModernNewsNotification({ lastScrollY = currentScrollY; }; - window.addEventListener('scroll', handleScroll, { passive: true }); - return () => window.removeEventListener('scroll', handleScroll); + window.addEventListener("scroll", handleScroll, { passive: true }); + return () => window.removeEventListener("scroll", handleScroll); }, [toastVisible]); const currentNews = news[0]; + // 🔗 Arahkan ke detail dengan kategori aman const handleNotificationClick = (item: NewsItem) => { setWidgetOpen(false); - onSeen?.(); // ✅ tandai sebagai dilihat + onSeen?.(); + if (item.type === "berita") { - router.push("/darmasaba/desa/berita/semua"); + const kategori = item.kategoriBerita?.name || "umum"; + const safeKategori = encodeURIComponent(kategori); + router.push(`/darmasaba/desa/berita/${safeKategori}/${item.id}`); } else if (item.type === "pengumuman") { - router.push("/darmasaba/desa/pengumuman"); + const kategori = item.kategoriPengumuman?.name || "umum"; + const safeKategori = encodeURIComponent(kategori); + router.push(`/darmasaba/desa/pengumuman/${safeKategori}/${item.id}`); } }; @@ -119,35 +145,40 @@ export default function ModernNewsNotification({ setToastVisible(false); setWidgetOpen(true); setHasNewNotifications(false); - onSeen?.(); // ✅ + onSeen?.(); }; const handleDismissToast = (e: React.MouseEvent) => { e.stopPropagation(); setToastVisible(false); - onSeen?.(); // ✅ + onSeen?.(); }; - // Only show on landing page - if (pathname !== '/darmasaba') { - return null; - } + // Hanya tampilkan di landing page + if (pathname !== "/darmasaba") return null; return ( <> {/* Floating Bell Icon */} {(transitionStyles) => ( - + { - setWidgetOpen(!widgetOpen); + setWidgetOpen((open) => !open); setHasNewNotifications(false); - onSeen?.(); // ✅ + onSeen?.(); }} style={{ width: "60px", @@ -168,7 +199,6 @@ export default function ModernNewsNotification({ right: "6px", minWidth: "22px", height: "22px", - padding: "0 6px", display: "flex", alignItems: "center", justifyContent: "center", @@ -190,8 +220,9 @@ export default function ModernNewsNotification({ ...styles, position: "fixed", bottom: "100px", - right: "24px", - width: "380px", + left: "24px", + width: "90vw", + maxWidth: 380, maxHeight: "500px", boxShadow: "0 8px 32px rgba(0,0,0,0.12)", borderRadius: "16px", @@ -208,12 +239,14 @@ export default function ModernNewsNotification({ - Berita & Pengumuman + + Berita & Pengumuman + { setWidgetOpen(false); - onSeen?.(); // ✅ + onSeen?.(); }} variant="transparent" c="white" @@ -224,13 +257,15 @@ export default function ModernNewsNotification({ {news.length === 0 ? ( - Tidak ada berita terbaru + + Tidak ada berita terbaru + ) : ( - {news.map((item, index) => ( + {news.map((item) => ( - {item.type === "berita" ? "📰 Berita" : "📢 Pengumuman"} + {item.type === "berita" ? "Berita" : "Pengumuman"} @@ -276,15 +311,20 @@ export default function ModernNewsNotification({ {/* Toast Notification */} - + {(styles) => ( - {currentNews?.type === "berita" ? "Berita Terbaru" : "Pengumuman"} + {currentNews?.type === "berita" + ? "Berita Terbaru" + : "Pengumuman"} @@ -329,7 +370,7 @@ export default function ModernNewsNotification({ - {news.length > 1 ? `${news.length} berita tersedia` : '1 berita'} + {news.length > 1 ? `${news.length} berita tersedia` : "1 berita"} ([]); const [barChartData, setBarChartData] = useState>([]); const [opened, { open, close }] = useDisclosure(false) + const isMobile = useMediaQuery("(max-width: 768px)"); const resetForm = () => { state.create.form = { @@ -41,7 +42,7 @@ function Kepuasan() { indeksKepuasanState.jenisKelaminResponden.findMany.load() indeksKepuasanState.pilihanRatingResponden.findMany.load() indeksKepuasanState.kelompokUmurResponden.findMany.load() - },[]) + }, []) const handleSubmit = async () => { try { @@ -82,13 +83,13 @@ function Kepuasan() { // Update gender chart data setDonutDataJenisKelamin([ - { name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] }, + { name: 'Laki-laki', value: totalLaki, color: '#52ABE3FF' }, { name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' }, ]); // Update rating chart data setDonutDataRating([ - { name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] }, + { name: 'Sangat Baik', value: totalSangatBaik, color: '#52ABE3FF' }, { name: 'Baik', value: totalBaik, color: '#10A85AFF' }, { name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' }, { name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' }, @@ -96,7 +97,7 @@ function Kepuasan() { // Update age group chart data setDonutDataKelompokUmur([ - { name: 'Muda', value: totalMuda, color: colors['blue-button'] }, + { name: 'Muda', value: totalMuda, color: '#52ABE3FF' }, { name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' }, { name: 'Lansia', value: totalLansia, color: '#FFA500' }, ]); @@ -220,10 +221,13 @@ function Kepuasan() {
@@ -259,10 +263,10 @@ function Kepuasan() { withTooltip tooltipAnimationDuration={200} withLabels - labelsPosition="outside" + labelsPosition="inside" // 👈 ini yang penting! labelsType="percent" withLabelsLine - size={250} + size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile data={donutDataRating} /> @@ -302,10 +306,10 @@ function Kepuasan() { withTooltip tooltipAnimationDuration={200} withLabels - labelsPosition="outside" + labelsPosition="inside"// 👈 ini yang penting! labelsType="percent" withLabelsLine - size={250} + size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile data={donutDataKelompokUmur} /> @@ -494,6 +498,8 @@ function Kepuasan() { { state.create.form.name = val.currentTarget.value; @@ -619,7 +627,7 @@ function Kepuasan() { { state.create.form.tanggal = val.currentTarget.value; diff --git a/src/app/darmasaba/_com/main-page/landing-page/index.tsx b/src/app/darmasaba/_com/main-page/landing-page/index.tsx index 563f1dc1..8204b784 100644 --- a/src/app/darmasaba/_com/main-page/landing-page/index.tsx +++ b/src/app/darmasaba/_com/main-page/landing-page/index.tsx @@ -154,7 +154,7 @@ function LandingPage() { return ( - + diff --git a/src/app/darmasaba/_com/main-page/layanan/index.tsx b/src/app/darmasaba/_com/main-page/layanan/index.tsx index b8d01b8a..c5a79653 100644 --- a/src/app/darmasaba/_com/main-page/layanan/index.tsx +++ b/src/app/darmasaba/_com/main-page/layanan/index.tsx @@ -25,25 +25,27 @@ const textHeading = { des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!", }; +const HEIGHT = 720; + function Layanan() { return ( - - - + + + {textHeading.title} - + {textHeading.des} - +