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)/ppid/profil-ppid/page.tsx b/src/app/admin/(dashboard)/ppid/profil-ppid/page.tsx index 1e49f3da..247222cf 100644 --- a/src/app/admin/(dashboard)/ppid/profil-ppid/page.tsx +++ b/src/app/admin/(dashboard)/ppid/profil-ppid/page.tsx @@ -30,12 +30,13 @@ function Page() { return ( - + Preview Profil PPID - - + - {Math.round(scale * 100)}% - + } + style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil + > + Zoom Out + - + + {Math.round(scale * 100)}% + - + } + style={{ flexShrink: 0 }} + > + Zoom In + - - - + + Reset + + + + ) : ( + + ) + } + style={{ flexShrink: 0 }} + > + {isFullscreen ? 'Exit' : 'Fullscreen'} + + + + {/* 🧩 Chart Container */} @@ -325,15 +363,20 @@ function StrukturPerangkatDesaNode() { maxWidth: '100%', padding: '32px 16px', transition: 'transform 0.2s ease', - transform: `scale(${scale})`, - transformOrigin: 'center top', }} > - } - className="p-organizationchart p-organizationchart-horizontal" - /> + + } + className="p-organizationchart p-organizationchart-horizontal" + /> + @@ -345,6 +388,7 @@ function NodeCard({ node, router }: any) { const name = node?.data?.name || 'Tanpa Nama' const title = node?.data?.title || 'Tanpa Jabatan' const hasId = Boolean(node?.data?.id) + const isMobile = useMediaQuery("(max-width: 768px)"); return ( @@ -355,9 +399,10 @@ function NodeCard({ node, router }: any) { withBorder style={{ ...styles, - width: 240, - minHeight: 280, - padding: 20, + width: '100%', + maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile + minHeight: isMobile ? 240 : 280, + padding: isMobile ? 16 : 20, background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)', borderColor: 'rgba(28, 110, 164, 0.3)', borderWidth: 2, diff --git a/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/[id]/page.tsx b/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/[id]/page.tsx index 23c2cee6..16981c7e 100644 --- a/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/[id]/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/daftar-informasi-publik/[id]/page.tsx @@ -87,7 +87,7 @@ export default function DetailInformasiPublikUser() { - + 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/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 +607,7 @@ const state = useProxy(indeksKepuasanState.responden); { state.create.form.tanggal = val.currentTarget.value; diff --git a/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx b/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx index 5a47ba67..6bd6ebd1 100644 --- a/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/permohonan-informasi-publik/page.tsx @@ -53,23 +53,11 @@ function Page() { const permohonanInformasiPublikState = useProxy(statePermohonanInformasi); const router = useRouter(); - const submitForms = () => { + const submitForms = async () => { const { create } = permohonanInformasiPublikState.statepermohonanInformasiPublik; - - if ( - create.form.name && - create.form.nik && - create.form.notelp && - create.form.alamat && - create.form.email && - create.form.jenisInformasiDimintaId && - create.form.caraMemperolehInformasiId && - create.form.caraMemperolehSalinanInformasiId - ) { - create.create(); + const hasil = await create.create(); // tunggu hasilnya + if (hasil) { router.push('/darmasaba/permohonan/berhasil'); - } else { - console.log('Validasi gagal, form tidak lengkap'); } }; diff --git a/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx b/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx index 4ef775be..871fd6f5 100644 --- a/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/permohonan-keberatan-informasi-publik/page.tsx @@ -55,17 +55,13 @@ function Page() { const stateKeberatan = useProxy(permohonanKeberatanInformasi); const router = useRouter(); - const submit = () => { - if ( - stateKeberatan.create.form.name && - stateKeberatan.create.form.email && - stateKeberatan.create.form.notelp && - stateKeberatan.create.form.alasan - ) { - stateKeberatan.create.create(); + const submit = async () => { + const { create } = stateKeberatan; + + const hasil = await create.create(); // tunggu hasilnya + + if (hasil) { router.push('/darmasaba/permohonan/berhasil'); - } else { - console.log('Formulir belum lengkap'); } }; @@ -190,7 +186,7 @@ function Page() { Biografi - + + + Riwayat Karir - + + + + + diff --git a/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx b/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx index 1d921cab..c14914d8 100644 --- a/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx @@ -14,6 +14,9 @@ import { Loader, Paper, Stack, + Tabs, + TabsList, + TabsTab, Text, TextInput, Title, @@ -35,6 +38,7 @@ import { useEffect, useRef, useState } from 'react' import { useProxy } from 'valtio/utils' import BackButton from '../../desa/layanan/_com/BackButto' import './struktur.css' +import { useMediaQuery } from '@mantine/hooks' export default function Page() { return ( @@ -231,87 +235,121 @@ function StrukturOrganisasiPPID() { p="md" radius="md" style={{ - background: colors['blue-button'] + background: colors['blue-button'], + width: '100%', // ⬅️ penting + maxWidth: '100%', // ⬅️ penting + overflowX: 'auto' // ⬅️ untuk mencegah overflow }} > - - } - onChange={(e) => debouncedSearch(e.target.value)} + + + + } + onChange={(e) => debouncedSearch(e.target.value)} + styles={{ + input: { + minWidth: 250, + }, + }} + /> + + - - - - - + - {Math.round(scale * 100)}% - + } + style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil + > + Zoom Out + - + + {Math.round(scale * 100)}% + - + } + style={{ flexShrink: 0 }} + > + Zoom In + - - - + + Reset + + + + ) : ( + + ) + } + style={{ flexShrink: 0 }} + > + {isFullscreen ? 'Exit' : 'Fullscreen'} + + + + {/* 🧩 Chart Container */} @@ -325,15 +363,20 @@ function StrukturOrganisasiPPID() { maxWidth: '100%', padding: '32px 16px', transition: 'transform 0.2s ease', - transform: `scale(${scale})`, - transformOrigin: 'center top', }} > - } - className="p-organizationchart p-organizationchart-horizontal" - /> + + } + className="p-organizationchart p-organizationchart-horizontal" + /> + @@ -345,6 +388,7 @@ function NodeCard({ node, router }: any) { const name = node?.data?.name || 'Tanpa Nama' const title = node?.data?.title || 'Tanpa Jabatan' const hasId = Boolean(node?.data?.id) + const isMobile = useMediaQuery("(max-width: 768px)"); return ( @@ -355,9 +399,10 @@ function NodeCard({ node, router }: any) { withBorder style={{ ...styles, - width: 240, - minHeight: 280, - padding: 20, + width: '100%', + maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile + minHeight: isMobile ? 240 : 280, + padding: isMobile ? 16 : 20, background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)', borderColor: 'rgba(28, 110, 164, 0.3)', borderWidth: 2, @@ -411,6 +456,7 @@ function NodeCard({ node, router }: any) { c={colors['blue-button']} lineClamp={2} style={{ + // fontSize: 'clamp(12px, 4vw, 16px)', // 👈 responsif font size minHeight: 40, display: 'flex', alignItems: 'center', diff --git a/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx b/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx index 85e50080..43440556 100644 --- a/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/visi-misi-ppid/page.tsx @@ -75,7 +75,7 @@ function Page() { lh={1.7} ta="center" dangerouslySetInnerHTML={{ __html: item.visi }} - style={{wordBreak: "break-word", whiteSpace: "normal"}} + style={{ wordBreak: "break-word", whiteSpace: "normal" }} /> @@ -86,12 +86,15 @@ function Page() { c={colors['blue-button']} mb="sm"> Misi PPID - + + + diff --git a/src/app/darmasaba/_com/ModernNewsNotification.tsx b/src/app/darmasaba/_com/ModernNewsNotification.tsx index ccf338d9..e2663db0 100644 --- a/src/app/darmasaba/_com/ModernNewsNotification.tsx +++ b/src/app/darmasaba/_com/ModernNewsNotification.tsx @@ -220,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", @@ -290,7 +291,7 @@ export default function ModernNewsNotification({ color={item.type === "berita" ? "blue" : "orange"} variant="light" > - {item.type === "berita" ? "📰 Berita" : "📢 Pengumuman"} + {item.type === "berita" ? "Berita" : "Pengumuman"} @@ -321,8 +322,9 @@ export default function ModernNewsNotification({ ...styles, position: "fixed", bottom: "100px", - right: "24px", - width: "380px", + left: "24px", + width: "90vw", + maxWidth: 380, boxShadow: "0 8px 32px rgba(0,0,0,0.15)", borderRadius: "12px", overflow: "hidden", @@ -350,7 +352,6 @@ export default function ModernNewsNotification({ size="md" color={currentNews?.type === "berita" ? "blue" : "orange"} variant="light" - leftSection={currentNews?.type === "berita" ? "📰" : "📢"} > {currentNews?.type === "berita" ? "Berita Terbaru" diff --git a/src/app/darmasaba/_com/main-page/kepuasan/index.tsx b/src/app/darmasaba/_com/main-page/kepuasan/index.tsx index f7480598..d303760c 100644 --- a/src/app/darmasaba/_com/main-page/kepuasan/index.tsx +++ b/src/app/darmasaba/_com/main-page/kepuasan/index.tsx @@ -4,7 +4,7 @@ import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/ind import colors from "@/con/colors"; import { BarChart, PieChart } from '@mantine/charts'; import { Box, Button, Center, Container, Flex, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core"; -import { useDisclosure, useShallowEffect } from "@mantine/hooks"; +import { useDisclosure, useMediaQuery, useShallowEffect } from "@mantine/hooks"; import { useState } from "react"; import { useProxy } from "valtio/utils"; @@ -25,6 +25,7 @@ function Kepuasan() { const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState([]); 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/penghargaan/index.tsx b/src/app/darmasaba/_com/main-page/penghargaan/index.tsx index 2b4877eb..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 && ( - ) : ( - // Fallback: tampilkan poster + play button + )} + + {/* Fallback Image + Play Button */} + {(!showVideo || videoError) && ( setShowVideo(true)} + onClick={handlePlayVideo} style={{ + 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 */} (null); - const lastPengumumanId = useRef(null); + const lastBeritaTimestamp = useRef(null); + const lastPengumumanTimestamp = useRef(null); // 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; }, []); // Load data utama (untuk card) @@ -70,28 +70,49 @@ export default function Page() { if (result.success && Array.isArray(result.news)) { const news = result.news as NewsItem[]; - // Ambil ID terbaru const latestBerita = news.find((n) => n.type === "berita"); const latestPengumuman = news.find((n) => n.type === "pengumuman"); - const isNewBerita = latestBerita && lastBeritaId.current !== null && latestBerita.id !== lastBeritaId.current; - const isNewPengumuman = latestPengumuman && lastPengumumanId.current !== null && latestPengumuman.id !== lastPengumumanId.current; + const latestBeritaTs = latestBerita?.timestamp + ? new Date(latestBerita.timestamp).toISOString() + : null; + const latestPengumumanTs = latestPengumuman?.timestamp + ? new Date(latestPengumuman.timestamp).toISOString() + : null; - // Simpan ID terbaru ke ref - if (latestBerita) lastBeritaId.current = (latestBerita.id); - if (latestPengumuman) lastPengumumanId.current = (latestPengumuman.id); + // Inisialisasi flag + let isNewBerita = false; + let isNewPengumuman = false; - // Jika ini bukan inisialisasi pertama, tampilkan notifikasi - if (lastBeritaId.current !== null || lastPengumumanId.current !== null) { - if (isNewBerita || isNewPengumuman) { - const count = (isNewBerita ? 1 : 0) + (isNewPengumuman ? 1 : 0); - setNewItemCount(count); - setHasNewContent(true); + // 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; } - } else { - // Simpan ke localStorage saat pertama kali - if (latestBerita) localStorage.setItem("lastSeenBeritaId", (latestBerita.id)); - if (latestPengumuman) localStorage.setItem("lastSeenPengumumanId", (latestPengumuman.id)); + } + + // 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); @@ -113,13 +134,17 @@ export default function Page() { }, []); const handleSeen = () => { - setHasNewContent(false); - setNewItemCount(0); - const latestBerita = notificationNews.find(n => n.type === "berita"); - const latestPengumuman = notificationNews.find(n => n.type === "pengumuman"); - if (latestBerita) localStorage.setItem("lastSeenBeritaId", String(latestBerita.id)); - if (latestPengumuman) localStorage.setItem("lastSeenPengumumanId", String(latestPengumuman.id)); -}; + setHasNewContent(false); + setNewItemCount(0); + 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 (