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
}
color="blue"
variant="light"
onClick={() =>
router.push(
- '/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
+ '/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/create'
)
}
>
@@ -176,7 +176,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
color="blue"
onClick={() =>
router.push(
- `/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`
+ `/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/${item.id}`
)
}
>
@@ -221,7 +221,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
{/* Chart */}
- Grafik Hasil Kepuasan Masyarakat
+ Penderita Penyakit
{mounted && diseaseChartData.length > 0 ? (
-
+
Preview Profil PPID
}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/berita/del.ts b/src/app/api/[[...slugs]]/_lib/desa/berita/del.ts
index bb5df949..0e31e421 100644
--- a/src/app/api/[[...slugs]]/_lib/desa/berita/del.ts
+++ b/src/app/api/[[...slugs]]/_lib/desa/berita/del.ts
@@ -6,33 +6,24 @@ import path from "path";
const beritaDelete = async (context: Context) => {
const id = context.params?.id as string;
- if (!id) {
- return {
- status: 400,
- body: "ID tidak diberikan",
- };
- }
+ if (!id) return { status: 400, body: "ID tidak diberikan" };
const berita = await prisma.berita.findUnique({
where: { id },
- include: {
- image: true,
- kategoriBerita: true, // pastikan relasi image sudah ada di prisma schema
- },
+ include: { image: true, kategoriBerita: true },
});
- if (!berita) {
- return {
- status: 404,
- body: "Berita tidak ditemukan",
- };
- }
+ if (!berita) return { status: 404, body: "Berita tidak ditemukan" };
- // Hapus file gambar dari filesystem jika ada
+ // 1. HAPUS BERITA DULU
+ await prisma.berita.delete({ where: { id } });
+
+ // 2. BARU HAPUS FILE
if (berita.image) {
try {
const filePath = path.join(berita.image.path, berita.image.name);
await fs.unlink(filePath);
+
await prisma.fileStorage.delete({
where: { id: berita.image.id },
});
@@ -41,15 +32,11 @@ const beritaDelete = async (context: Context) => {
}
}
- // Hapus berita dari DB
- await prisma.berita.delete({
- where: { id },
- });
-
return {
success: true,
message: "Berita dan file terkait berhasil dihapus",
};
};
+
export default beritaDelete;
diff --git a/src/app/api/[[...slugs]]/_lib/ppid/index.ts b/src/app/api/[[...slugs]]/_lib/ppid/index.ts
index b590bd86..94ae5516 100644
--- a/src/app/api/[[...slugs]]/_lib/ppid/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/ppid/index.ts
@@ -1,6 +1,5 @@
import Elysia from "elysia";
import DaftarInformasiPublik from "./daftar_informasi_publik";
-import GrafikHasilKepuasanMasyarakat from "./ikm/grafik_hasil_kepuasan_masyarakat";
import GrafikBerdasarkanJenisKelamin from "./ikm/grafik_berdasarkan_jenis_kelamin";
import GrafikBerdasarkanResponden from "./ikm/grafik_responden";
import GrafikBerdasarkanUmur from "./ikm/grafik_berdasarkan_umur";
@@ -10,6 +9,7 @@ import ProfilePPID from "./profile_ppid";
import VisiMisiPPID from "./visi_misi_ppid/visi_misi_ppid";
import DasarHukumPPID from "./dasar_hukum";
import StrukturPPID from "./struktur_ppid";
+import GrafikHasilKepuasanMasyarakat from "./ikm/grafik_hasil_kepuasan_masyarakat";
diff --git a/src/app/api/[[...slugs]]/_lib/ppid/permohonan_informasi_publik/create.ts b/src/app/api/[[...slugs]]/_lib/ppid/permohonan_informasi_publik/create.ts
index 4cc99f88..dcee8f4a 100644
--- a/src/app/api/[[...slugs]]/_lib/ppid/permohonan_informasi_publik/create.ts
+++ b/src/app/api/[[...slugs]]/_lib/ppid/permohonan_informasi_publik/create.ts
@@ -3,39 +3,55 @@ import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.PermohonanInformasiPublikGetPayload<{
- select: {
- name: true;
- nik: true;
- email: true;
- notelp: true;
- alamat: true;
- jenisInformasiDimintaId: true;
- caraMemperolehInformasiId: true;
- caraMemperolehSalinanInformasiId: true;
- }
-}>
-export default async function permohonanInformasiPublikCreate(context: Context) {
- const body = context.body as FormCreate;
-
- await prisma.permohonanInformasiPublik.create({
- data: {
- name: body.name,
- nik: body.nik,
- email: body.email,
- notelp: body.notelp,
- alamat: body.alamat,
- jenisInformasiDimintaId: body.jenisInformasiDimintaId,
- caraMemperolehInformasiId: body.caraMemperolehInformasiId,
- caraMemperolehSalinanInformasiId: body.caraMemperolehSalinanInformasiId,
- }
- })
+ select: {
+ name: true;
+ nik: true;
+ email: true;
+ notelp: true;
+ alamat: true;
+ jenisInformasiDimintaId: true;
+ caraMemperolehInformasiId: true;
+ caraMemperolehSalinanInformasiId: true;
+ };
+}>;
+export default async function permohonanInformasiPublikCreate(context: Context) {
+ const body = context.body as FormCreate;
+
+ // ========== VALIDASI NIK ==========
+ if (body.nik && body.nik.length > 16) {
return {
- success: true,
- message: "Permohonan Informasi Publik Berhasil Dibuat",
- data: {
- ...body,
- }
- }
+ success: false,
+ status: 400,
+ message: "Maksimal NIK adalah 16 angka",
+ };
+ }
+
+ // ========== VALIDASI NOMOR TELEPON ==========
+ if (body.notelp && body.notelp.length > 15) {
+ return {
+ success: false,
+ status: 400,
+ message: "Maksimal nomor telepon adalah 15 angka",
+ };
+ }
+
+ await prisma.permohonanInformasiPublik.create({
+ data: {
+ name: body.name,
+ nik: body.nik,
+ email: body.email,
+ notelp: body.notelp,
+ alamat: body.alamat,
+ jenisInformasiDimintaId: body.jenisInformasiDimintaId,
+ caraMemperolehInformasiId: body.caraMemperolehInformasiId,
+ caraMemperolehSalinanInformasiId: body.caraMemperolehSalinanInformasiId,
+ },
+ });
+
+ return {
+ success: true,
+ message: "Permohonan Informasi Publik Berhasil Dibuat",
+ data: { ...body },
+ };
}
-
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/ppid/permohonan_keberatan_informasi_publik/create.ts b/src/app/api/[[...slugs]]/_lib/ppid/permohonan_keberatan_informasi_publik/create.ts
index a6f70308..e63a562c 100644
--- a/src/app/api/[[...slugs]]/_lib/ppid/permohonan_keberatan_informasi_publik/create.ts
+++ b/src/app/api/[[...slugs]]/_lib/ppid/permohonan_keberatan_informasi_publik/create.ts
@@ -3,31 +3,42 @@ import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.FormulirPermohonanKeberatanGetPayload<{
- select: {
- name: true;
- email: true;
- notelp: true;
- alasan: true;
- }
-}>
+ select: {
+ name: true;
+ email: true;
+ notelp: true;
+ alasan: true;
+ };
+}>;
-export default async function permohonanKeberatanInformasiPublikCreate(context: Context) {
- const body = context.body as FormCreate;
-
- await prisma.formulirPermohonanKeberatan.create({
- data: {
- name: body.name,
- email: body.email,
- notelp: body.notelp,
- alasan: body.alasan,
- }
- })
+export default async function permohonanKeberatanInformasiPublikCreate(
+ context: Context
+) {
+ const body = context.body as FormCreate;
+ // ========== VALIDASI NOMOR TELEPON ==========
+ if (body.notelp && body.notelp.length > 15) {
return {
- success: true,
- message: "Permohonan Keberatan Informasi Publik Berhasil Dibuat",
- data: {
- ...body,
- }
- }
-}
\ No newline at end of file
+ success: false,
+ status: 400,
+ message: "Maksimal nomor telepon adalah 15 angka",
+ };
+ }
+
+ await prisma.formulirPermohonanKeberatan.create({
+ data: {
+ name: body.name,
+ email: body.email,
+ notelp: body.notelp,
+ alasan: body.alasan,
+ },
+ });
+
+ return {
+ success: true,
+ message: "Permohonan Keberatan Informasi Publik Berhasil Dibuat",
+ data: {
+ ...body,
+ },
+ };
+}
diff --git a/src/app/api/check-update/route.ts b/src/app/api/check-update/route.ts
deleted file mode 100644
index 167f90a8..00000000
--- a/src/app/api/check-update/route.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// app/api/check-update/route.ts
-import prisma from "@/lib/prisma";
-
-export async function GET() {
- try {
- // Ambil berita terbaru
- const latestBerita = await prisma.berita.findFirst({
- orderBy: { createdAt: "desc" },
- select: { id: true, createdAt: true },
- });
-
- // Ambil pengumuman terbaru
- const latestPengumuman = await prisma.pengumuman.findFirst({
- orderBy: { createdAt: "desc" },
- select: { id: true, createdAt: true },
- });
-
- return Response.json({
- success: true,
- data: {
- berita: latestBerita
- ? { id: latestBerita.id, createdAt: latestBerita.createdAt.toISOString() }
- : null,
- pengumuman: latestPengumuman
- ? { id: latestPengumuman.id, createdAt: latestPengumuman.createdAt.toISOString() }
- : null,
- },
- });
- } catch (error) {
- console.error("Error in /api/check-update:", error);
- return Response.json(
- { success: false, message: "Gagal cek update" },
- { status: 500 }
- );
- }
-}
\ No newline at end of file
diff --git a/src/app/api/news/latest/route.ts b/src/app/api/news/latest/route.ts
new file mode 100644
index 00000000..a38a1fe6
--- /dev/null
+++ b/src/app/api/news/latest/route.ts
@@ -0,0 +1,43 @@
+// app/api/news/latest/route.ts
+import { NextResponse } from "next/server";
+import prisma from "@/lib/prisma";
+
+export async function GET() {
+ try {
+ const berita = await prisma.berita.findMany({
+ take: 3,
+ orderBy: { createdAt: "desc" },
+ include: { kategoriBerita: true },
+ });
+
+ const pengumuman = await prisma.pengumuman.findMany({
+ take: 3,
+ orderBy: { createdAt: "desc" },
+ include: { CategoryPengumuman: true },
+ });
+
+ const news = [
+ ...berita.map((b) => ({
+ id: b.id,
+ type: "berita" as const,
+ title: b.judul,
+ content: b.content,
+ timestamp: b.createdAt,
+ kategoriBerita: b.kategoriBerita || undefined,
+ })),
+ ...pengumuman.map((p) => ({
+ id: p.id,
+ type: "pengumuman" as const,
+ title: p.judul,
+ content: p.content,
+ timestamp: p.createdAt,
+ kategoriPengumuman: p.CategoryPengumuman || undefined,
+ })),
+ ];
+
+ return NextResponse.json({ success: true, news }); // ✅ ganti 'data' jadi 'news'
+ } catch (error) {
+ console.error("API Error:", error);
+ return NextResponse.json({ success: false, error: "Gagal memuat data" }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx b/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx
index a2c4e9bc..0a9dcd44 100644
--- a/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx
@@ -100,7 +100,7 @@ function Page() {
{data.name}
-
+
- {/* Header */}
-
-
-
-
-
-
- Pengumuman Adat & Budaya
-
-
- Informasi dan pengumuman resmi terkait pengumuman adat & budaya di Desa Darmasaba.
-
-
-
-
- {dataPengumuman.map((v, k) => {
- return (
-
- {v.judul}
-
-
-
- {v.tanggal}
-
-
-
- {v.jam}
-
-
-
- {v.lokasi}
-
-
-
- {v.deskripsi}
-
-
- )
- })}
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/digitalisasi-desa/page.tsx b/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/digitalisasi-desa/page.tsx
deleted file mode 100644
index e6837804..00000000
--- a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/digitalisasi-desa/page.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import colors from '@/con/colors';
-import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
-import React from 'react';
-import BackButton from '../../../layanan/_com/BackButto';
-import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
-
-const dataPengumuman = [
- {
- id: 1,
- judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
- tanggal: 'Selasa, 30 April 2025',
- jam: '09:00 WITA',
- lokasi: 'Perpustakaan Desa',
- deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
- },
- {
- id: 2,
- judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
- tanggal: 'Selasa, 30 April 2025',
- jam: '09:00 WITA',
- lokasi: 'Perpustakaan Desa',
- deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
- },
- {
- id: 3,
- judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
- tanggal: 'Selasa, 30 April 2025',
- jam: '09:00 WITA',
- lokasi: 'Perpustakaan Desa',
- deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
- },
- {
- id: 4,
- judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
- tanggal: 'Selasa, 30 April 2025',
- jam: '09:00 WITA',
- lokasi: 'Perpustakaan Desa',
- deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
- },
- {
- id: 5,
- judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
- tanggal: 'Selasa, 30 April 2025',
- jam: '09:00 WITA',
- lokasi: 'Perpustakaan Desa',
- deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
- },
-]
-function Page() {
- return (
-
- {/* Header */}
-
-
-
-
-
-
- Pengumuman Digitalisasi Desa
-
-
- Informasi dan pengumuman resmi terkait pengumuman digitalisasi desa
-
-
-
-
- {dataPengumuman.map((v, k) => {
- return (
-
- {v.judul}
-
-
-
- {v.tanggal}
-
-
-
- {v.jam}
-
-
-
- {v.lokasi}
-
-
-
- {v.deskripsi}
-
-
- )
- })}
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/ekonomi-&-umkm/page.tsx b/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/ekonomi-&-umkm/page.tsx
deleted file mode 100644
index ffb38abc..00000000
--- a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/ekonomi-&-umkm/page.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import colors from '@/con/colors';
-import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
-import React from 'react';
-import BackButton from '../../../layanan/_com/BackButto';
-import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
-
-const dataPengumuman = [
- {
- id: 1,
- judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
- tanggal: 'Rabu, 23 April 2025',
- jam: '13:00 WITA',
- lokasi: 'Aula Kantor Desa',
- deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
- },
- {
- id: 2,
- judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
- tanggal: 'Rabu, 23 April 2025',
- jam: '13:00 WITA',
- lokasi: 'Aula Kantor Desa',
- deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
- },
- {
- id: 3,
- judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
- tanggal: 'Rabu, 23 April 2025',
- jam: '13:00 WITA',
- lokasi: 'Aula Kantor Desa',
- deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
- },
- {
- id: 4,
- judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
- tanggal: 'Rabu, 23 April 2025',
- jam: '13:00 WITA',
- lokasi: 'Aula Kantor Desa',
- deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
- },
- {
- id: 5,
- judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
- tanggal: 'Rabu, 23 April 2025',
- jam: '13:00 WITA',
- lokasi: 'Aula Kantor Desa',
- deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
- },
-]
-function Page() {
- return (
-
- {/* Header */}
-
-
-
-
-
-
- Pengumuman Ekonomi & UMKM
-
-
- Informasi dan pengumuman resmi terkait pengumuman ekonomi & umkm
-
-
-
-
- {dataPengumuman.map((v, k) => {
- return (
-
- {v.judul}
-
-
-
- {v.tanggal}
-
-
-
- {v.jam}
-
-
-
- {v.lokasi}
-
-
-
- {v.deskripsi}
-
-
- )
- })}
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/lingkungan-&-bencana/page.tsx b/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/lingkungan-&-bencana/page.tsx
deleted file mode 100644
index 50320eeb..00000000
--- a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/lingkungan-&-bencana/page.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import colors from '@/con/colors';
-import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
-import React from 'react';
-import BackButton from '../../../layanan/_com/BackButto';
-import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
-
-const dataPengumuman = [
- {
- id: 1,
- judul: 'Gotong Royong Bersih Sungai dan Drainase',
- tanggal: 'Minggu, 21 April 2025',
- jam: '06:30 WITA',
- lokasi: 'Titik Kumpul: Poskamling RW 02',
- deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
- },
- {
- id: 2,
- judul: 'Gotong Royong Bersih Sungai dan Drainase',
- tanggal: 'Minggu, 21 April 2025',
- jam: '06:30 WITA',
- lokasi: 'Titik Kumpul: Poskamling RW 02',
- deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
- },
- {
- id: 3,
- judul: 'Gotong Royong Bersih Sungai dan Drainase',
- tanggal: 'Minggu, 21 April 2025',
- jam: '06:30 WITA',
- lokasi: 'Titik Kumpul: Poskamling RW 02',
- deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
- },
- {
- id: 4,
- judul: 'Gotong Royong Bersih Sungai dan Drainase',
- tanggal: 'Minggu, 21 April 2025',
- jam: '06:30 WITA',
- lokasi: 'Titik Kumpul: Poskamling RW 02',
- deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
- },
- {
- id: 5,
- judul: 'Gotong Royong Bersih Sungai dan Drainase',
- tanggal: 'Minggu, 21 April 2025',
- jam: '06:30 WITA',
- lokasi: 'Titik Kumpul: Poskamling RW 02',
- deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
- },
-]
-function Page() {
- return (
-
- {/* Header */}
-
-
-
-
-
-
- Pengumuman Lingkungan & Bencana
-
-
- Informasi dan pengumuman resmi terkait pengumuman lingkungan & bencana
-
-
-
-
- {dataPengumuman.map((v, k) => {
- return (
-
- {v.judul}
-
-
-
- {v.tanggal}
-
-
-
- {v.jam}
-
-
-
- {v.lokasi}
-
-
-
- {v.deskripsi}
-
-
- )
- })}
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/pendidikan-&-kepemudaan/page.tsx b/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/pendidikan-&-kepemudaan/page.tsx
deleted file mode 100644
index 003785fc..00000000
--- a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/pendidikan-&-kepemudaan/page.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import colors from '@/con/colors';
-import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
-import React from 'react';
-import BackButton from '../../../layanan/_com/BackButto';
-import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
-
-const dataPengumuman = [
- {
- id: 1,
- judul: 'Pendaftaran Bimbingan Belajar Gratis untuk Pelajar',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
- {
- id: 2,
- judul: 'Lomba Video Pendek Hari Lingkungan',
- tanggal: 'Deadline: 28 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Online Submission',
- deskripsi: 'Karang Taruna Desa mengadakan lomba video pendek bertema "Lingkunganku, Tanggung Jawabku". Pemenang akan diumumkan saat acara Hari Desa Hijau. Total hadiah Rp1.000.000.'
- },
- {
- id: 3,
- judul: 'Pendaftaran Bimbingan Belajar Gratis untuk Pelajar',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
- {
- id: 4,
- judul: 'Lomba Video Pendek Hari Lingkungan',
- tanggal: 'Deadline: 28 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Online Submission',
- deskripsi: 'Karang Taruna Desa mengadakan lomba video pendek bertema "Lingkunganku, Tanggung Jawabku". Pemenang akan diumumkan saat acara Hari Desa Hijau. Total hadiah Rp1.000.000.'
- },
- {
- id: 5,
- judul: 'Pendaftaran Bimbingan Belajar Gratis untuk Pelajar',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
-]
-function Page() {
- return (
-
- {/* Header */}
-
-
-
-
-
-
- Pengumuman Pendidikan & Kepemudaan
-
-
- Informasi dan pengumuman resmi terkait pengumuman pendidikan & kepemudaan
-
-
-
-
- {dataPengumuman.map((v, k) => {
- return (
-
- {v.judul}
-
-
-
- {v.tanggal}
-
-
-
- {v.jam}
-
-
-
- {v.lokasi}
-
-
-
- {v.deskripsi}
-
-
- )
- })}
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/sosial-&-kesehatan/page.tsx b/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/sosial-&-kesehatan/page.tsx
deleted file mode 100644
index f6924545..00000000
--- a/src/app/darmasaba/(pages)/desa/pengumuman/(kategori)/sosial-&-kesehatan/page.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import colors from '@/con/colors';
-import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
-import React from 'react';
-import BackButton from '../../../layanan/_com/BackButto';
-import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
-
-const dataPengumuman = [
- {
- id: 1,
- judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
- {
- id: 2,
- judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
- {
- id: 3,
- judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
- {
- id: 4,
- judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
- {
- id: 5,
- judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
- tanggal: 'Sabtu, 20 April 2025',
- jam: '08:00 WITA',
- lokasi: 'Balai Banjar Desa Darmasaba',
- deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
- },
-]
-function Page() {
- return (
-
- {/* Header */}
-
-
-
-
-
-
- Pengumuman Sosial & Kesehatan
-
-
- Informasi dan pengumuman resmi terkait pengumuman sosial & kesehatan
-
-
-
-
- {dataPengumuman.map((v, k) => {
- return (
-
- {v.judul}
-
-
-
- {v.tanggal}
-
-
-
- {v.jam}
-
-
-
- {v.lokasi}
-
-
-
- {v.deskripsi}
-
-
- )
- })}
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/darmasaba/(pages)/desa/pengumuman/[name]/[id]/page.tsx b/src/app/darmasaba/(pages)/desa/pengumuman/[name]/[id]/page.tsx
index 82a8ccd5..f103d6f8 100644
--- a/src/app/darmasaba/(pages)/desa/pengumuman/[name]/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/desa/pengumuman/[name]/[id]/page.tsx
@@ -46,8 +46,8 @@ function Page() {
-
-
+
+
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
weekday: 'long',
day: 'numeric',
diff --git a/src/app/darmasaba/(pages)/desa/profile/struktur-perangkat-desa/page.tsx b/src/app/darmasaba/(pages)/desa/profile/struktur-perangkat-desa/page.tsx
index 5ac50922..bc4b3300 100644
--- a/src/app/darmasaba/(pages)/desa/profile/struktur-perangkat-desa/page.tsx
+++ b/src/app/darmasaba/(pages)/desa/profile/struktur-perangkat-desa/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 './struktur.css'
import BackButton from '../_com/BackButto'
+import { useMediaQuery } from '@mantine/hooks'
export default function StrukturPerangkatDesa() {
return (
@@ -231,87 +235,121 @@ function StrukturPerangkatDesaNode() {
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,
+ },
+ }}
+ />
+
+
-
-
- }
- c={colors['blue-button']}
- >
- Zoom Out
-
-
-
+
- {Math.round(scale * 100)}%
-
+ }
+ style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
+ >
+ Zoom Out
+
- }
- >
- Zoom In
-
+
+ {Math.round(scale * 100)}%
+
-
- Reset
-
+ }
+ style={{ flexShrink: 0 }}
+ >
+ Zoom In
+
-
- ) : (
-
- )
- }
- >
- Fullscreen
-
-
-
+
+ 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
}
>
- Tambah Laporan
+ {mobile ? 'Tambah' : 'Tambah Laporan'}
-
-
-
- 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}
-
- }
- onClick={() => router.push(`/darmasaba/keamanan/laporan-publik/${v.id}`)}
- >Lihat Detail Kronologi
-
-
-
- )
- })}
-
-
- 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}
+
+
+
+ }
+ onClick={() => router.push(`/darmasaba/keamanan/laporan-publik/${v.id}`)}
+ size={mobile ? 'sm' : 'md'}
+ fullWidth
+ >
+ {mobile ? 'Detail' : 'Lihat Detail Kronologi'}
+
+
+
+ ))}
+
+
+
+ {/* 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() {
-
@@ -78,25 +78,33 @@ function Page() {
Pendahuluan
-
+
+
+
{state.findUnique.data.symptom?.title}
-
+
+
+
{state.findUnique.data.prevention?.title}
-
+
+
+
{state.findUnique.data.firstaid?.title}
-
+
+
+
@@ -114,10 +122,14 @@ function Page() {
{state.findUnique.data?.mythvsfact ? (
-
+
+
+
-
+
+
+
) : (
@@ -133,17 +145,15 @@ function Page() {
Kapan Harus ke Dokter?
-
+
Segera bawa penderita ke fasilitas kesehatan jika mengalami:
-
-
+
+
+
- Kasus DBD di Wilayah Abiansemal
-
-
diff --git a/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/[id]/page.tsx b/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/[id]/page.tsx
index d8d02e6e..086c9a18 100644
--- a/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/[id]/page.tsx
@@ -175,7 +175,9 @@ function Page() {
Layanan Unggulan
{layananUnggulan ? (
-
+
+
+
) : (
@@ -251,7 +253,9 @@ function Page() {
Fasilitas Pendukung
{fasilitasPendukungHtml ? (
-
+
+
+
) : (
@@ -313,7 +317,7 @@ function Page() {
Prosedur Pendaftaran
{prosedur ? (
-
+
) : (
Belum ada prosedur pendaftaran
)}
diff --git a/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/grafik-penyakit/page.tsx b/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/grafik-penyakit/page.tsx
index a2820dc5..9a4e53d1 100644
--- a/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/grafik-penyakit/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/grafik-penyakit/page.tsx
@@ -95,7 +95,7 @@ function GrafikPenyakit() {
- Grafik Hasil Kepuasan Masyarakat
+ Penderita Penyakit
Belum ada data untuk ditampilkan dalam grafik
@@ -103,7 +103,7 @@ function GrafikPenyakit() {
) : (
- Grafik Hasil Kepuasan Masyarakat
+ Penderita Penyakit
{mounted && diseaseChartData.length > 0 && (
diff --git a/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/[id]/page.tsx b/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/[id]/page.tsx
index f5af70e8..c7a29b9b 100644
--- a/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/[id]/page.tsx
@@ -69,25 +69,33 @@ function Page() {
Deskripsi Kegiatan
-
+
+
+
Layanan yang Tersedia
-
+
+
+
Syarat & Ketentuan
-
+
+
+
Dokumen yang Perlu Dibawa
-
+
+
+
diff --git a/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/[id]/page.tsx b/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/[id]/page.tsx
index cb19a507..9fe5ac53 100644
--- a/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/[id]/page.tsx
@@ -71,11 +71,13 @@ function DetailInfoWabahPenyakitUser() {
{/* Deskripsi */}
Deskripsi
-
+
+
+
diff --git a/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/page.tsx b/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/page.tsx
index e65feec2..a110fdf4 100644
--- a/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/info-wabah-penyakit/page.tsx
@@ -30,7 +30,7 @@ function Page() {
const state = useProxy(infoWabahPenyakit);
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(() => {
diff --git a/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/[id]/page.tsx b/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/[id]/page.tsx
new file mode 100644
index 00000000..7e84f01b
--- /dev/null
+++ b/src/app/darmasaba/(pages)/kesehatan/kontak-darurat/[id]/page.tsx
@@ -0,0 +1,111 @@
+'use client'
+import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
+import colors from '@/con/colors';
+import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconArrowBack, IconBrandWhatsapp } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import { useProxy } from 'valtio/utils';
+
+function Page() {
+ const state = useProxy(kontakDarurat);
+ const router = useRouter();
+ const params = useParams();
+
+ useShallowEffect(() => {
+ state.findUnique.load(params?.id as string);
+ }, []);
+
+ if (!state.findUnique.data) {
+ return (
+
+
+
+ );
+ }
+
+ const data = state.findUnique.data;
+
+ return (
+
+ {/* Tombol Back */}
+ router.back()}
+ leftSection={ }
+ mb={15}
+ >
+ Kembali
+
+
+ {/* Wrapper Detail */}
+
+
+
+ Detail Kontak Darurat
+
+
+
+
+
+ Judul
+ {data.name || '-'}
+
+
+
+ Whatsapp
+ {data.whatsapp || '-'}
+
+
+
+ Deskripsi
+
+
+
+
+ Gambar
+ {data.image?.link ? (
+
+ ) : (
+ -
+ )}
+
+
+ }
+ component="a"
+ href={`https://wa.me/${data.whatsapp.replace(/\D/g, '')}`}
+ target="_blank"
+ aria-label="Hubungi WhatsApp"
+ >
+ WhatsApp
+
+
+
+
+
+
+
+ );
+}
+
+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) => (
-
-
-
- (e.currentTarget.style.transform = 'scale(1.05)')}
- onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
- />
-
-
-
- {v.name}
-
-
-
-
-
-
-
- {/* ✅ Tombol selalu di bagian bawah card */}
-
- }
- component="a"
- href={`https://wa.me/${v.whatsapp.replace(/\D/g, '')}`}
- target="_blank"
- aria-label="Hubungi WhatsApp"
- >
- WhatsApp
-
-
-
-
-
+
+
+
+ (e.currentTarget.style.transform = 'scale(1.05)')}
+ onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
+ />
+
+
+
+ {v.name}
+
+
+
+
+
+
+
+ {/* ✅ Tombol selalu di bagian bawah card */}
+
+ router.push(`/darmasaba/kesehatan/kontak-darurat/${v.id}`)}
+ >
+ Detail
+
+
+
+
+
))}
)}
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,
+ },
+ }}
+ />
+
+
-
-
- }
- c={colors['blue-button']}
- >
- Zoom Out
-
-
-
+
- {Math.round(scale * 100)}%
-
+ }
+ style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
+ >
+ Zoom Out
+
- }
- >
- Zoom In
-
+
+ {Math.round(scale * 100)}%
+
-
- Reset
-
+ }
+ style={{ flexShrink: 0 }}
+ >
+ Zoom In
+
-
- ) : (
-
- )
- }
- >
- Fullscreen
-
-
-
+
+ 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}
-
+
(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.3; // slower speed on hover
+ const SPEED_NORMAL = 1.0;
+ const SPEED_HOVER = 0.3;
+ const VELOCITY_DECAY = 0.95;
+ const SCROLL_THRESHOLD = 100;
useEffect(() => {
const loadData = async () => {
@@ -100,126 +98,118 @@ function Slider() {
const data = state.suratKeterangan.findMany.data || [];
- // Duplicate slides for seamless infinite loop
- // We need at least 3x the data for smooth infinite scrolling
- 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;
+ scrollPosRef.current = container.scrollLeft;
- // Apply momentum/velocity for smooth drag release
+ // 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; // Multiply for faster scroll
+ const walk = (x - startXRef.current) * 2;
const newScrollLeft = scrollLeftRef.current - walk;
- // Calculate velocity for momentum
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) {
- return ;
+ return ;
}
if (data.length === 0) {
@@ -242,9 +232,13 @@ function Slider() {
onMouseUp={handleMouseUp}
onWheel={handleWheel}
style={{
- overflow: "hidden",
- cursor: "grab",
+ overflowX: mobile ? "auto" : "hidden",
+ overflowY: "hidden",
+ cursor: mobile ? "default" : "grab",
userSelect: "none",
+ WebkitOverflowScrolling: "touch",
+ scrollbarWidth: "none",
+ msOverflowStyle: "none",
}}
>
-
+
diff --git a/src/app/darmasaba/_com/main-page/penghargaan/index.tsx b/src/app/darmasaba/_com/main-page/penghargaan/index.tsx
index 1272be84..0c487411 100644
--- a/src/app/darmasaba/_com/main-page/penghargaan/index.tsx
+++ b/src/app/darmasaba/_com/main-page/penghargaan/index.tsx
@@ -4,7 +4,7 @@ import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import { Stack, Box, Container, Button, Text, Loader, Paper, Center, ActionIcon } from "@mantine/core";
import { IconAward, IconArrowRight, IconPlayerPlay } from "@tabler/icons-react";
import { useTransitionRouter } from 'next-view-transitions';
-import { useEffect, useState } from "react";
+import { useEffect, useState, useRef } from "react";
import { useProxy } from "valtio/utils";
import { useMediaQuery } from "@mantine/hooks";
@@ -15,16 +15,35 @@ function Penghargaan() {
const isMobile = useMediaQuery('(max-width: 768px)');
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [showVideo, setShowVideo] = useState(true);
+ const [videoError, setVideoError] = useState(false);
+ const videoRef = useRef(null);
- // Opsional: deteksi iOS
- const isIOS = typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
+ // Deteksi iOS dengan lebih akurat
+ const isIOS = typeof window !== 'undefined' && (
+ /iPad|iPhone|iPod/.test(navigator.userAgent) ||
+ (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) // iPad dengan iPadOS 13+
+ );
useEffect(() => {
- if (isIOS) {
- // Di iOS, jangan andalkan autoplay — tampilkan kontrol
- setShowVideo(false);
+ // Di iOS, coba autoplay dulu, kalau gagal tampilkan fallback
+ if (isIOS && videoRef.current) {
+ const playPromise = videoRef.current.play();
+
+ if (playPromise !== undefined) {
+ playPromise
+ .then(() => {
+ // Autoplay berhasil
+ setShowVideo(true);
+ setIsVideoLoaded(true);
+ })
+ .catch(() => {
+ // Autoplay gagal, tampilkan fallback
+ setShowVideo(false);
+ setVideoError(true);
+ });
+ }
}
- }, []);
+ }, [isIOS]);
useEffect(() => {
const loadData = async () => {
@@ -38,42 +57,99 @@ function Penghargaan() {
loadData();
}, []);
+ const handlePlayVideo = () => {
+ setShowVideo(true);
+ setVideoError(false);
+
+ // Paksa play video setelah user interaction
+ setTimeout(() => {
+ if (videoRef.current) {
+ videoRef.current.play().catch(err => {
+ console.error("Video play error:", err);
+ setVideoError(true);
+ });
+ }
+ }, 100);
+ };
+
// kalau mobile ambil 1 data aja, kalau desktop ambil 3
const data = state.findMany.data?.slice(0, isMobile ? 1 : 3);
return (
-
- {showVideo ? (
+
+ {/* Video Layer */}
+ {showVideo && !videoError && (
setIsVideoLoaded(true)}
- style={{ opacity: isVideoLoaded ? 1 : 0, transition: 'opacity 0.5s' }}
+ onError={() => {
+ console.error("Video load error");
+ setVideoError(true);
+ setShowVideo(false);
+ }}
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ objectFit: 'cover',
+ opacity: isVideoLoaded ? 1 : 0,
+ transition: 'opacity 0.5s ease',
+ zIndex: 0,
+ }}
>
- ) : (
- // Fallback: tampilkan poster + play button
+ )}
+
+ {/* Fallback Image + Play Button */}
+ {(!showVideo || videoError) && (
setShowVideo(true)}
+ onClick={handlePlayVideo}
style={{
- backgroundImage: "url('/assets/images/award-poster.jpg')",
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ backgroundImage: "url('/mangupuraaward.jpeg')",
backgroundSize: 'cover',
backgroundPosition: 'center',
+ backgroundRepeat: 'no-repeat',
cursor: 'pointer',
+ zIndex: 0,
}}
>
-
-
-
+
+
+
)}
+ {/* Overlay Gradient + Content */}
([]);
const [hasNewContent, setHasNewContent] = useState(false);
const [newItemCount, setNewItemCount] = useState(0);
- const lastBeritaId = useRef(null);
- const lastPengumumanId = useRef(null);
+ const lastBeritaTimestamp = useRef(null);
+ const lastPengumumanTimestamp = useRef(null);
- // 🔁 Inisialisasi dari localStorage saat mount
+ // Inisialisasi dari localStorage
useEffect(() => {
- const savedBerita = localStorage.getItem("lastSeenBeritaId");
- const savedPengumuman = localStorage.getItem("lastSeenPengumumanId");
- if (savedBerita) lastBeritaId.current = savedBerita;
- if (savedPengumuman) lastPengumumanId.current = savedPengumuman;
+ const savedBeritaTs = localStorage.getItem("lastSeenBeritaTs");
+ const savedPengumumanTs = localStorage.getItem("lastSeenPengumumanTs");
+ if (savedBeritaTs) lastBeritaTimestamp.current = savedBeritaTs;
+ if (savedPengumumanTs) lastPengumumanTimestamp.current = savedPengumumanTs;
}, []);
- // Simpan ID saat data dimuat (termasuk dari API)
- useEffect(() => {
- if (featured.data?.id) lastBeritaId.current = featured.data.id;
- if (pengumuman.data?.id) lastPengumumanId.current = pengumuman.data.id;
- }, [featured.data?.id, pengumuman.data?.id]);
-
- // Load data awal
+ // Load data utama (untuk card)
useEffect(() => {
if (!featured.data && !loadingFeatured) {
stateDashboardBerita.berita.findFirst.load();
@@ -64,90 +62,88 @@ export default function Page() {
}
}, []);
- // 🔁 Polling untuk cek update setiap 30 detik
- useEffect(() => {
- const checkForUpdates = async () => {
- try {
- const res = await fetch("/api/check-update");
- const result = await res.json();
+ // 🔁 Fetch berita & pengumuman lengkap untuk notifikasi
+ const fetchNotificationData = async () => {
+ try {
+ const res = await fetch("/api/news/latest");
+ const result = await res.json();
+ if (result.success && Array.isArray(result.news)) {
+ const news = result.news as NewsItem[];
- if (!result.success) return;
+ const latestBerita = news.find((n) => n.type === "berita");
+ const latestPengumuman = news.find((n) => n.type === "pengumuman");
- const { berita, pengumuman } = result.data;
+ const latestBeritaTs = latestBerita?.timestamp
+ ? new Date(latestBerita.timestamp).toISOString()
+ : null;
+ const latestPengumumanTs = latestPengumuman?.timestamp
+ ? new Date(latestPengumuman.timestamp).toISOString()
+ : null;
- // Deteksi hanya jika sudah pernah ada data sebelumnya
- const isNewBerita = berita && lastBeritaId.current !== null && berita.id !== lastBeritaId.current;
- const isNewPengumuman = pengumuman && lastPengumumanId.current !== null && pengumuman.id !== lastPengumumanId.current;
+ // Inisialisasi flag
+ let isNewBerita = false;
+ let isNewPengumuman = false;
- if (isNewBerita || isNewPengumuman) {
- // Hitung berapa yang benar-benar baru
- const count = (isNewBerita ? 1 : 0) + (isNewPengumuman ? 1 : 0);
- setNewItemCount(count);
- setHasNewContent(true);
-
- // Reload hanya yang berubah
- if (isNewBerita) stateDashboardBerita.berita.findFirst.load();
- if (isNewPengumuman) stateDesaPengumuman.pengumuman.findFirst.load();
- } else {
- // Jika ini adalah pertama kali (masih null), simpan ID tanpa notifikasi
- if (lastBeritaId.current === null && berita) {
- lastBeritaId.current = berita.id;
- localStorage.setItem("lastSeenBeritaId", berita.id);
- }
- if (lastPengumumanId.current === null && pengumuman) {
- lastPengumumanId.current = pengumuman.id;
- localStorage.setItem("lastSeenPengumumanId", pengumuman.id);
+ // Deteksi berita baru
+ if (latestBeritaTs) {
+ if (lastBeritaTimestamp.current === null) {
+ // Pertama kali: simpan tanpa notifikasi
+ lastBeritaTimestamp.current = latestBeritaTs;
+ localStorage.setItem("lastSeenBeritaTs", latestBeritaTs);
+ } else if (latestBeritaTs > lastBeritaTimestamp.current) {
+ isNewBerita = true;
+ lastBeritaTimestamp.current = latestBeritaTs;
}
}
- } catch (err) {
- console.error("Gagal cek update berita/pengumuman:", err);
- }
- };
- const interval = setInterval(checkForUpdates, 30_000);
- return () => clearInterval(interval);
+ // Deteksi pengumuman baru
+ if (latestPengumumanTs) {
+ if (lastPengumumanTimestamp.current === null) {
+ // Pertama kali: simpan tanpa notifikasi
+ lastPengumumanTimestamp.current = latestPengumumanTs;
+ localStorage.setItem("lastSeenPengumumanTs", latestPengumumanTs);
+ } else if (latestPengumumanTs > lastPengumumanTimestamp.current) {
+ isNewPengumuman = true;
+ lastPengumumanTimestamp.current = latestPengumumanTs;
+ }
+ }
+
+ // 🔔 Trigger notifikasi hanya jika ada yang benar-benar BARU
+ if (isNewBerita || isNewPengumuman) {
+ const count = (isNewBerita ? 1 : 0) + (isNewPengumuman ? 1 : 0);
+ setNewItemCount(count);
+ setHasNewContent(true); // ✅ INI YANG KAMU LUPA!
+ }
+
+ setNotificationNews(news);
+ }
+ } catch (err) {
+ console.error("Gagal fetch data notifikasi:", err);
+ }
+ };
+
+ // Load data notifikasi pertama kali
+ useEffect(() => {
+ fetchNotificationData();
}, []);
- const newsData = useMemo(() => {
- const items = [];
-
- if (featured.data) {
- items.push({
- id: String(featured.data.id || "berita-1"),
- type: "berita" as const,
- title: String(featured.data.judul || "Berita Terbaru"),
- content: String(featured.data.content || ""),
- timestamp: featured.data.createdAt
- ? (typeof featured.data.createdAt === 'string'
- ? featured.data.createdAt
- : new Date(featured.data.createdAt).toISOString())
- : new Date().toISOString(),
- });
- }
-
- if (pengumuman.data) {
- items.push({
- id: String(pengumuman.data.id || "pengumuman-1"),
- type: "pengumuman" as const,
- title: String(pengumuman.data.judul || "Pengumuman Penting"),
- content: String(pengumuman.data.content || ""),
- timestamp: pengumuman.data.createdAt
- ? (typeof pengumuman.data.createdAt === 'string'
- ? pengumuman.data.createdAt
- : new Date(pengumuman.data.createdAt).toISOString())
- : new Date().toISOString(),
- });
- }
-
- return items;
- }, [featured.data, pengumuman.data]);
+ // Polling setiap 30 detik
+ useEffect(() => {
+ const interval = setInterval(fetchNotificationData, 30_000);
+ return () => clearInterval(interval);
+ }, []);
const handleSeen = () => {
setHasNewContent(false);
setNewItemCount(0);
- // Simpan ke localStorage saat dilihat
- if (featured.data?.id) localStorage.setItem("lastSeenBeritaId", featured.data.id);
- if (pengumuman.data?.id) localStorage.setItem("lastSeenPengumumanId", pengumuman.data.id);
+ const latestBerita = notificationNews.find(n => n.type === "berita");
+ const latestPengumuman = notificationNews.find(n => n.type === "pengumuman");
+ if (latestBerita) {
+ localStorage.setItem("lastSeenBeritaTs", new Date(latestBerita.timestamp!).toISOString());
+ }
+ if (latestPengumuman) {
+ localStorage.setItem("lastSeenPengumumanTs", new Date(latestPengumuman.timestamp!).toISOString());
+ }
};
return (
@@ -168,7 +164,7 @@ export default function Page() {