Merge pull request 'Fix Seeder Image, Menu Landing Page - Desa' (#56) from nico/30-jan-26 into staggingweb
Reviewed-on: http://wibugit.wibudev.com/wibu/desa-darmasaba/pulls/56
This commit is contained in:
@@ -552,7 +552,7 @@ const maskotDesa = proxy({
|
||||
deskripsi: profileData.deskripsi || "",
|
||||
images: (profileData.images || []).map((img) => ({
|
||||
label: img.label,
|
||||
imageId: img.image.id,
|
||||
imageId: img?.image?.id || "",
|
||||
})),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -462,7 +462,7 @@ function Page() {
|
||||
<Card withBorder key={idx} p="xs" w={{ base: '100%', md: 180 }}>
|
||||
<Center>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
src={ img?.image?.link || '/no-image.jpg'}
|
||||
alt={img.label}
|
||||
w={150}
|
||||
h={150}
|
||||
@@ -562,7 +562,7 @@ function Page() {
|
||||
<Card withBorder key={idx} p="xs" w={{ base: '100%', md: 180 }}>
|
||||
<Center>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
src={img?.image?.link || '/no-image.jpg'}
|
||||
alt={img.label}
|
||||
w={150}
|
||||
h={150}
|
||||
|
||||
@@ -2,77 +2,91 @@ import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function maskotDesaUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
images: { label: string; imageId: string }[];
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}), { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await prisma.maskotDesa.findUnique({
|
||||
where: { id },
|
||||
include: { images: { include: { image: true } } }
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}), { status: 404 });
|
||||
}
|
||||
|
||||
// Hapus semua gambar lama (dan file-nya jika perlu)
|
||||
for (const old of existing.images) {
|
||||
try {
|
||||
await prisma.fileStorage.delete({ where: { id: old.imageId } });
|
||||
// opsional: hapus file dari disk juga kalau kamu simpan file fisik
|
||||
// await fs.unlink(path.join(old.image.path, old.image.name));
|
||||
} catch (error) {
|
||||
console.warn("Gagal hapus gambar lama:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update profile & re-create images
|
||||
const updated = await prisma.maskotDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
images: {
|
||||
deleteMany: {},
|
||||
create: body.images.map((img) => ({
|
||||
label: img.label,
|
||||
imageId: img.imageId
|
||||
}))
|
||||
}
|
||||
},
|
||||
include: {
|
||||
images: {
|
||||
include: {
|
||||
image: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: "Data berhasil diperbarui",
|
||||
data: updated,
|
||||
}), { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Gagal update MaskotDesa:", error);
|
||||
return new Response(JSON.stringify({
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = (await context.body) as {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
images: { label: string; imageId: string }[];
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat update",
|
||||
}), { status: 500 });
|
||||
message: "ID tidak boleh kosong",
|
||||
}),
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await prisma.maskotDesa.findUnique({
|
||||
where: { id },
|
||||
include: { images: { include: { image: true } } },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}),
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Hapus semua gambar lama (dan file-nya jika perlu)
|
||||
for (const old of existing.images) {
|
||||
try {
|
||||
if (old.imageId) {
|
||||
await prisma.fileStorage.delete({ where: { id: old.imageId } });
|
||||
}
|
||||
// opsional: hapus file dari disk juga kalau kamu simpan file fisik
|
||||
// await fs.unlink(path.join(old.image.path, old.image.name));
|
||||
} catch (error) {
|
||||
console.warn("Gagal hapus gambar lama:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update profile & re-create images
|
||||
const updated = await prisma.maskotDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
images: {
|
||||
deleteMany: {},
|
||||
create: body.images.map((img) => ({
|
||||
label: img.label,
|
||||
imageId: img.imageId,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
images: {
|
||||
include: {
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Data berhasil diperbarui",
|
||||
data: updated,
|
||||
}),
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Gagal update MaskotDesa:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat update",
|
||||
}),
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ function Semua() {
|
||||
<Grid gutter={0}>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Image
|
||||
src={featuredData.image?.link || '/images/placeholder.jpg'}
|
||||
src={featuredData.image?.link || '/images/placeholderx.jpg'}
|
||||
alt={featuredData.judul || 'Berita Utama'}
|
||||
height={400}
|
||||
fit="cover"
|
||||
|
||||
@@ -83,7 +83,7 @@ function MaskotDesa() {
|
||||
className="hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
src={img.image?.link || '/no-image.jpg'}
|
||||
alt={img.label}
|
||||
w="100%"
|
||||
h={200}
|
||||
|
||||
@@ -19,20 +19,58 @@ interface APBDesProgressProps {
|
||||
function APBDesProgress({ apbdesData }: APBDesProgressProps) {
|
||||
// Return null if apbdesData is not available yet
|
||||
if (!apbdesData) {
|
||||
return null;
|
||||
return (
|
||||
<Paper
|
||||
mx={{ base: 'md', md: 100 }}
|
||||
p="xl"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
withBorder
|
||||
bg={colors['white-1']}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Title order={4} c={colors['blue-button']} ta="center">
|
||||
Grafik Pelaksanaan APBDes
|
||||
</Title>
|
||||
<Text ta="center">Tidak ada data APBDes tersedia.</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const items = Array.isArray(apbdesData.items) ? apbdesData.items : [];
|
||||
|
||||
// Show message if no items exist
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<Paper
|
||||
mx={{ base: 'md', md: 100 }}
|
||||
p="xl"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
withBorder
|
||||
bg={colors['white-1']}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Title order={4} c={colors['blue-button']} ta="center">
|
||||
Grafik Pelaksanaan APBDes Tahun {apbdesData.tahun || 'N/A'}
|
||||
</Title>
|
||||
<Text ta="center">Tidak ada rincian anggaran tersedia untuk tahun ini.</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const items = apbdesData.items || [];
|
||||
const sortedItems = [...items].sort((a, b) => a.kode.localeCompare(b.kode));
|
||||
|
||||
// Kelompokkan berdasarkan tipe
|
||||
const pendapatanItems = sortedItems.filter(item => item.tipe === 'pendapatan');
|
||||
const belanjaItems = sortedItems.filter(item => item.tipe === 'belanja');
|
||||
const pembiayaanItems = sortedItems.filter(item => item.tipe === 'pembiayaan');
|
||||
|
||||
|
||||
// Items without a type (should be filtered out from calculations)
|
||||
const untypedItems = sortedItems.filter(item => !item.tipe);
|
||||
|
||||
|
||||
if (untypedItems.length > 0) {
|
||||
console.warn(`Found ${untypedItems.length} items without a type. These will be excluded from calculations.`);
|
||||
}
|
||||
@@ -99,7 +137,7 @@ function APBDesProgress({ apbdesData }: APBDesProgressProps) {
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Title order={4} c={colors['blue-button']} ta="center">
|
||||
Grafik Pelaksanaan APBDes Tahun {apbdesData.tahun}
|
||||
Grafik Pelaksanaan APBDes Tahun {apbdesData.tahun || 'N/A'}
|
||||
</Title>
|
||||
|
||||
<Text ta="center" fw="bold" fz="sm" c="dimmed">
|
||||
|
||||
@@ -34,7 +34,36 @@ interface APBDesTableProps {
|
||||
}
|
||||
|
||||
function APBDesTable({ apbdesData }: APBDesTableProps) {
|
||||
// Handle case where apbdesData is null or undefined
|
||||
if (!apbdesData) {
|
||||
return (
|
||||
<Box pt={"xs"} pb="md" px={{ base: 'md', md: 100 }}>
|
||||
<Title order={4} c={colors['blue-button']} mb="sm">
|
||||
Rincian APBDes
|
||||
</Title>
|
||||
<Paper withBorder radius="md" shadow="xs" p="md">
|
||||
<Text>Tidak ada data APBDes tersedia.</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const items = Array.isArray(apbdesData.items) ? apbdesData.items : [];
|
||||
|
||||
// Show message if no items exist
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<Box pt={"xs"} pb="md" px={{ base: 'md', md: 100 }}>
|
||||
<Title order={4} c={colors['blue-button']} mb="sm">
|
||||
Rincian APBDes Tahun {apbdesData.tahun || 'N/A'}
|
||||
</Title>
|
||||
<Paper withBorder radius="md" shadow="xs" p="md">
|
||||
<Text>Tidak ada rincian anggaran tersedia untuk tahun ini.</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const sortedItems = [...items].sort((a, b) => a.kode.localeCompare(b.kode));
|
||||
|
||||
// Calculate totals
|
||||
@@ -46,7 +75,7 @@ function APBDesTable({ apbdesData }: APBDesTableProps) {
|
||||
return (
|
||||
<Box pt={"xs"} pb="md" px={{ base: 'md', md: 100 }}>
|
||||
<Title order={4} c={colors['blue-button']} mb="sm">
|
||||
Rincian APBDes Tahun {apbdesData.tahun}
|
||||
Rincian APBDes Tahun {apbdesData.tahun || 'N/A'}
|
||||
</Title>
|
||||
|
||||
<Paper withBorder radius="md" shadow="xs" p="md">
|
||||
|
||||
@@ -32,11 +32,38 @@ export interface APBDesData {
|
||||
}
|
||||
|
||||
export function transformAPBDesData(data: any): APBDesData {
|
||||
if (!data) {
|
||||
return {
|
||||
id: '',
|
||||
tahun: null,
|
||||
items: [],
|
||||
image: null,
|
||||
file: null
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
items: data.items.map((item: any) => ({
|
||||
...item,
|
||||
tipe: isAPBDesTipe(item.tipe) ? item.tipe : null
|
||||
}))
|
||||
id: data.id || '',
|
||||
tahun: data.tahun || null,
|
||||
items: Array.isArray(data.items)
|
||||
? data.items.map((item: any) => ({
|
||||
id: item.id || '',
|
||||
kode: item.kode || '',
|
||||
uraian: item.uraian || '',
|
||||
anggaran: typeof item.anggaran === 'number' ? item.anggaran : 0,
|
||||
realisasi: typeof item.realisasi === 'number' ? item.realisasi : 0,
|
||||
selisih: typeof item.selisih === 'number' ? item.selisih : 0,
|
||||
persentase: typeof item.persentase === 'number' ? item.persentase : 0,
|
||||
level: typeof item.level === 'number' ? item.level : 1,
|
||||
tipe: isAPBDesTipe(item.tipe) ? item.tipe : null,
|
||||
createdAt: item.createdAt || undefined,
|
||||
updatedAt: item.updatedAt || undefined,
|
||||
deletedAt: item.deletedAt || null,
|
||||
isActive: item.isActive !== undefined ? item.isActive : true,
|
||||
apbdesId: item.apbdesId || undefined
|
||||
}))
|
||||
: [],
|
||||
image: data.image ? { id: data.image.id || '', url: data.image.url || '' } : null,
|
||||
file: data.file ? { id: data.file.id || '', url: data.file.url || '' } : null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { IconDownload } from '@tabler/icons-react'
|
||||
import { Link } from 'next-view-transitions'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import { toast } from 'react-toastify'
|
||||
import BackButton from '../../(pages)/desa/layanan/_com/BackButto'
|
||||
import APBDesTable from './lib/apbDesaTable'
|
||||
import APBDesProgress from './lib/apbDesaProgress'
|
||||
@@ -19,6 +20,7 @@ function Page() {
|
||||
const paDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedYear, setSelectedYear] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
@@ -26,7 +28,8 @@ function Page() {
|
||||
await state.findMany.load()
|
||||
await paDesaState.findMany.load()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error('Error loading APBDes data:', error)
|
||||
toast.error('Gagal memuat data APBDes')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -37,7 +40,13 @@ function Page() {
|
||||
const dataAPBDes = state.findMany.data || []
|
||||
|
||||
// Buat daftar tahun unik dari data
|
||||
const years = Array.from(new Set(dataAPBDes.map((item: any) => item.tahun)))
|
||||
const years = Array.from(
|
||||
new Set(
|
||||
dataAPBDes
|
||||
.filter((item: any) => item?.tahun != null) // Filter out items with null/undefined tahun
|
||||
.map((item: any) => item.tahun)
|
||||
)
|
||||
)
|
||||
.sort((a, b) => b - a) // urutkan descending
|
||||
.map(year => ({ value: year.toString(), label: `Tahun ${year}` }))
|
||||
|
||||
@@ -49,9 +58,16 @@ function Page() {
|
||||
}, [years, selectedYear])
|
||||
|
||||
// Transform and filter data based on selected year
|
||||
const currentApbdes = dataAPBDes.length > 0
|
||||
? transformAPBDesData(dataAPBDes.find(item => item?.tahun?.toString() === selectedYear) || dataAPBDes[0])
|
||||
: null
|
||||
let currentApbdes = null;
|
||||
if (dataAPBDes.length > 0 && selectedYear) {
|
||||
const selectedData = dataAPBDes.find((item: any) => item?.tahun?.toString() === selectedYear);
|
||||
if (selectedData) {
|
||||
currentApbdes = transformAPBDesData(selectedData);
|
||||
}
|
||||
} else if (dataAPBDes.length > 0 && !selectedYear && years.length > 0) {
|
||||
// If no year is selected but data exists, use the first item
|
||||
currentApbdes = transformAPBDesData(dataAPBDes[0]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
|
||||
|
||||
@@ -143,29 +143,29 @@ function Apbdes() {
|
||||
)}
|
||||
|
||||
{/* GRID */}
|
||||
<SimpleGrid
|
||||
mx={{ base: 'md', md: 100 }}
|
||||
cols={{ base: 1, sm: 3 }}
|
||||
spacing="lg"
|
||||
pb="xl"
|
||||
>
|
||||
{loading ? (
|
||||
<Center mih={200}>
|
||||
<Loader size="lg" color="blue" />
|
||||
</Center>
|
||||
) : data.length === 0 ? (
|
||||
<Center mih={200}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz="lg" c="dimmed" lh={1.4}>
|
||||
Belum ada data APBDes yang tersedia
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||
Data akan ditampilkan di sini setelah diunggah
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
data.map((v, k) => (
|
||||
{loading ? (
|
||||
<Center mx={{ base: 'md', md: 100 }} mih={200} pb="xl">
|
||||
<Loader size="lg" color="blue" />
|
||||
</Center>
|
||||
) : data.length === 0 ? (
|
||||
<Center mx={{ base: 'md', md: 100 }} mih={200} pb="xl">
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz="lg" c="dimmed" lh={1.4}>
|
||||
Belum ada data APBDes yang tersedia
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||
Data akan ditampilkan di sini setelah diunggah
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
<SimpleGrid
|
||||
mx={{ base: 'md', md: 100 }}
|
||||
cols={{ base: 1, sm: 3 }}
|
||||
spacing="lg"
|
||||
pb="xl"
|
||||
>
|
||||
{data.map((v, k) => (
|
||||
<BackgroundImage
|
||||
key={k}
|
||||
src={v.image?.link || ''}
|
||||
@@ -213,9 +213,9 @@ function Apbdes() {
|
||||
</Center>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
))
|
||||
)}
|
||||
</SimpleGrid>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user