nico/27-okt-25 #1

Merged
nicoarya20 merged 277 commits from nico/27-okt-25 into main 2025-10-27 22:18:01 +08:00
589 changed files with 33846 additions and 622 deletions
Showing only changes of commit 8f2b9665a9 - Show all commits

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
"use client";
import {
@@ -15,19 +14,19 @@ import {
Title,
} from "@mantine/core";
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useRouter, useParams } from "next/navigation";
import { useProxy } from "valtio/utils";
import { toast } from "react-toastify";
import { useProxy } from "valtio/utils";
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";
import { FileInput } from "@mantine/core";
import stateDashboardBerita from "../../../../_state/desa/berita";
import { Prisma } from "@prisma/client";
import { useShallowEffect } from "@mantine/hooks";
import { BeritaEditor } from "../../_com/BeritaEditor";
import colors from "@/con/colors";
import { Prisma } from "@prisma/client";
import stateDashboardBerita from "../../../../_state/desa/berita";
function EditBerita() {
const beritaState = useProxy(stateDashboardBerita);
@@ -36,8 +35,6 @@ function EditBerita() {
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [editorInstance, setEditorInstance] = useState<any>(null);
const [isEditorReady, setIsEditorReady] = useState(false);
const [formData, setFormData] = useState({
judul: beritaState.berita.edit.form.judul || '',
deskripsi: beritaState.berita.edit.form.deskripsi || '',
@@ -76,28 +73,7 @@ function EditBerita() {
loadBerita();
}, [params?.id]); // ✅ hapus beritaState dari dependency
// Handle editor ready
const handleEditorReady = (editor: any) => {
setEditorInstance(editor);
setIsEditorReady(true);
// Set initial content if exists
if (formData.content) {
editor.commands.setContent(formData.content);
}
};
const handleSubmit = async () => {
if (!isEditorReady || !editorInstance) {
return toast.error("Editor belum siap");
}
const htmlContent = editorInstance.getHTML();
if (!htmlContent || htmlContent === "<p></p>") {
return toast.warn("Konten tidak boleh kosong");
}
try {
// Update global state with form data
@@ -105,7 +81,7 @@ function EditBerita() {
...beritaState.berita.edit.form,
judul: formData.judul,
deskripsi: formData.deskripsi,
content: htmlContent,
content: formData.content,
kategoriBeritaId: formData.kategoriBeritaId || '',
imageId: formData.imageId // Keep existing imageId if not changed
};
@@ -189,14 +165,12 @@ function EditBerita() {
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<BeritaEditor
initialContent={formData.content}
onEditorReady={handleEditorReady}
showSubmit={false}
onUpdate={(content) => {
setFormData((prev) => ({ ...prev, content }));
beritaState.berita.edit.form.content = content;
}}
<EditEditor
value={formData.content}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, content: htmlContent }));
beritaState.berita.edit.form.content = htmlContent;
}}
/>
</Box>

View File

@@ -8,8 +8,8 @@ import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import colors from '@/con/colors';
import { ModalKonfirmasiHapus } from '../../../../_com/modalKonfirmasiHapus';
import stateDashboardBerita from '../../../../_state/desa/berita';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import stateDashboardBerita from '../../../_state/desa/berita';
function DetailBerita() {
const beritaState = useProxy(stateDashboardBerita)
@@ -45,52 +45,64 @@ function DetailBerita() {
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Berita</Text>
{beritaState.berita.findUnique.data ? (
<Paper key={beritaState.berita.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
{beritaState.berita.findUnique.data ? (
<Paper key={beritaState.berita.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Kategori</Text>
<Text fz={"lg"}>{beritaState.berita.findUnique.data?.kategoriBerita?.name}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul</Text>
<Text fz={"lg"}>{beritaState.berita.findUnique.data?.judul}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} >{beritaState.berita.findUnique.data?.deskripsi}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={beritaState.berita.findUnique.data?.image?.link} alt="gambar" />
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (beritaState.berita.findUnique.data) {
setSelectedId(beritaState.berita.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={beritaState.berita.delete.loading || !beritaState.berita.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (beritaState.berita.findUnique.data) {
router.push(`/admin/desa/berita/edit/${beritaState.berita.findUnique.data.id}`);
}
}}
disabled={!beritaState.berita.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Paper>
) : null}
<Box>
<Text fw={"bold"} fz={"lg"}>Konten</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: beritaState.berita.findUnique.data?.content }} />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (beritaState.berita.findUnique.data) {
setSelectedId(beritaState.berita.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={beritaState.berita.delete.loading || !beritaState.berita.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (beritaState.berita.findUnique.data) {
router.push(`/admin/desa/berita/${beritaState.berita.findUnique.data.id}/edit`);
}
}}
disabled={!beritaState.berita.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
@@ -10,14 +9,13 @@ import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import CreateEditor from '../../../_com/createEditor';
import stateDashboardBerita from '../../../_state/desa/berita';
import { BeritaEditor } from '../_com/BeritaEditor';
export default function CreateBerita() {
const beritaState = useProxy(stateDashboardBerita);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [editorInstance, setEditorInstance] = useState<any>(null);
const router = useRouter()
const resetForm = () => {
@@ -33,21 +31,12 @@ export default function CreateBerita() {
// Reset state lokal
setPreviewImage(null);
setFile(null);
if (editorInstance) {
editorInstance.commands.setContent(""); // Kosongkan editor
}
};
const handleSubmit = async () => {
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
}
if (!editorInstance) return toast.error("Editor belum siap");
const htmlContent = editorInstance.getHTML();
if (!htmlContent || htmlContent === "<p></p>") return toast.warn("Konten tidak boleh kosong");
beritaState.berita.create.form.content = htmlContent;
// Upload gambar dulu
const res = await ApiFetch.api.fileStorage.create.post({
@@ -124,9 +113,11 @@ export default function CreateBerita() {
)}
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<BeritaEditor
showSubmit={false}
onEditorReady={(ed) => setEditorInstance(ed)}
<CreateEditor
value={beritaState.berita.create.form.content}
onChange={(htmlContent) => {
beritaState.berita.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>

View File

@@ -163,7 +163,7 @@ function BeritaList() {
<Image w={100} src={item.image?.link} alt="gambar" />
</TableTd>
<TableTd>
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/detail/${item.id}`)}>
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>

View File

@@ -13,9 +13,6 @@ type FormCreate = Prisma.BeritaGetPayload<{
}>;
async function beritaCreate(context: Context) {
const body = context.body as FormCreate;
console.log(body)
// console.log(body)
await prisma.berita.create({
data: {

View File

@@ -40,26 +40,20 @@ export default async function handler(
}
// Ensure we're returning a proper Response object
return new Response(JSON.stringify({
return Response.json({
success: true,
message: "Success fetch berita by ID",
data,
}), {
}, {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (e) {
console.error("Find by ID error:", e);
return new Response(JSON.stringify({
return Response.json({
success: false,
message: "Gagal mengambil berita: " + (e instanceof Error ? e.message : 'Unknown error'),
}), {
}, {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
}

View File

@@ -85,7 +85,7 @@ async function beritaUpdate(context: Context) {
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error("Error updating berita:", error);
console.error("Error updating berita:", error);
return new Response(
JSON.stringify({
success: false,

View File

@@ -10,7 +10,7 @@ function Footer() {
return (
<>
<Stack bg={colors["blue-button"]}>
<Box w={mobile ? "100%" : "100%"} p={"xl"} h={{ base: 1850, md: 1100 }} >
<Box w={mobile ? "100%" : "100%"} p={"xl"} h={{ base: 2500, md: 1100 }} >
<Center>
<Paper w={"100%"}>
<Box component="footer" py="xl">

View File

@@ -44,9 +44,9 @@ function DesaAntiKorupsi() {
<Stack gap={"0"} bg={colors.Bg} p={"sm"} h={mobile ? 2000 : 1150}>
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
<Center>
<Text fz={"3.4rem"}>Desa Anti Korupsi</Text>
<Text fz={{base: "2.4rem", md: "3.4rem"}}>Desa Anti Korupsi</Text>
</Center>
<Text ta={"center"} fz={"1.4rem"}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
<Text ta={"center"} fz={{base: "1.2rem", md: "1.4rem"}}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
<Center py={20}>
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desaantikorupsi"}>Selengkapnya</Button>
</Center>
@@ -65,13 +65,13 @@ function DesaAntiKorupsi() {
<Paper p={"lg"} >
<Flex gap={"lg"} justify={"center"} align={"center"}>
<Box >
<Text fz={"lg"} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Text fz={{base: "1.2rem", md: "lg"}} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Flex justify={"center"} align={"center"}>
<Box>
{v.icon}
</Box>
<Box px={20}>
<Text fz={"sm"} ta={"justify"}>{v.deskripsi}</Text>
<Text fz={"sm"} ta={{base: "left", md: "justify"}}>{v.deskripsi}</Text>
</Box>
</Flex>
</Box>

View File

@@ -1,7 +1,8 @@
"use client";
import { Stack, Container, Center, Text, Paper, Flex, Box, SimpleGrid } from "@mantine/core";
import { BarChart, PieChart } from '@mantine/charts';
import colors from "@/con/colors";
import { BarChart, PieChart } from '@mantine/charts';
import { Box, Center, Container, Flex, Paper, SimpleGrid, Stack, Text } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
const dataBarChart = [
{
@@ -71,13 +72,14 @@ const dataPieChart3 = [
]
function Kepuasan() {
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<Stack p={"sm"}>
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
<Center>
<Text fz={"3.4rem"}>Indeks Kepuasan Masyarakat</Text>
<Text ta={"center"} fz={{base: "2.4rem", md: "3.4rem"}}>Indeks Kepuasan Masyarakat</Text>
</Center>
<Text fz={"1.4rem"} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
<Text fz={{base: "1.2rem", md: "1.4rem"}} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
</Container>
<Box px={"xl"}>
<Paper p={"lg"} bg={colors.Bg}>
@@ -118,7 +120,7 @@ function Kepuasan() {
<Text fw={"bold"}>Jenis Kelamin</Text>
<Box py={"xl"}>
<PieChart
size={250}
size={isMobile ? 100 : 220}
withLabelsLine
labelsPosition="outside"
labelsType="percent"
@@ -135,7 +137,7 @@ function Kepuasan() {
<Text fw={"bold"}>Pilihan</Text>
<Box py={"xl"}>
<PieChart
size={250}
size={isMobile ? 100 : 220}
withLabelsLine
labelsPosition="outside"
labelsType="percent"
@@ -152,7 +154,7 @@ function Kepuasan() {
<Text fw={"bold"}>Umur</Text>
<Box py={"xl"}>
<PieChart
size={250}
size={isMobile ? 100 : 220}
withLabelsLine
labelsPosition="outside"
labelsType="percent"

View File

@@ -5,9 +5,9 @@ import {
Card,
Flex,
Grid,
GridCol,
Image,
Paper,
SimpleGrid,
Stack,
Text
} from "@mantine/core";
@@ -58,10 +58,9 @@ function LandingPage() {
<Grid
>
<Grid.Col span={{
base: 2,
sm: 3,
base: 3,
lg: 2,
md: 3,
xl: 2
}}>
<Box
pos={"relative"}
@@ -88,10 +87,9 @@ function LandingPage() {
</Grid.Col>
<Grid.Col span={{
base: 2,
sm: 3,
base: 6,
lg: 2,
md: 3,
xl: 2
}}>
<Box
pos={"relative"}
@@ -118,10 +116,9 @@ function LandingPage() {
</Box>
</Grid.Col>
<Grid.Col span={{
base: 8,
sm: 12,
base: 12,
lg: 8,
md: 12,
xl: 8
}}>
<Paper
pos={"relative"}
@@ -130,15 +127,14 @@ function LandingPage() {
w={{ base: "100%", sm: "auto", md: "auto" }}
flex={{ base: "1", sm: "1", md: "1" }}
>
<SimpleGrid
cols={{
base: 2,
sm: 1,
md: 2,
}}
spacing={{ base: "xs", md: "md" }}
<Grid
>
<Box>
<GridCol span={{
base: 12,
lg: 6,
md: 6,
}}>
<Box>
<Text c={colors["white-1"]} fz={"sm"}>
Jadwal Kerja
</Text>
@@ -168,7 +164,14 @@ function LandingPage() {
</Flex>
</Paper>
</Box>
<Box>
</GridCol>
<GridCol span={{
base: 12,
lg: 6,
md: 6,
}}>
<Box>
<Text c={colors["white-1"]} fz={"sm"}>
Rabu, 10 Maret 2025
</Text>
@@ -187,7 +190,8 @@ function LandingPage() {
</Box>
</Paper>
</Box>
</SimpleGrid>
</GridCol>
</Grid>
</Paper>
</Grid.Col>

View File

@@ -51,7 +51,7 @@ function Penghargaan() {
<Text fz={"1.4rem"} c={"white"}>
Juara 2 Duta Investasi
</Text>
<Text fz={"1.4rem"} c={"white"}>
<Text fz={"1.2rem"} c={"white"}>
Juara Favorit Lomba Video Pendek
</Text>
</Stack>