Compare commits

...

3 Commits

Author SHA1 Message Date
daaed8089b Fix All Search Admin 2026-01-05 17:11:30 +08:00
f436aa2ef0 Fix QC Kak Inno Mobile Done
FIx QC Kak Ayu Mobile Admin Done
Fix Tampilan Admin Mobile Device All Menu Done
2026-01-02 16:33:15 +08:00
50bc54ceca Fix QC Kak Inno 22 Des
Fix QC Kak Ayu 22 Des
Fix Tampilan Admin Menu Inovasi
2025-12-24 14:36:51 +08:00
187 changed files with 6013 additions and 2363 deletions

View File

@@ -68,7 +68,7 @@ const category = proxy({
const res = await ApiFetch.api.desa.kategoripengumuman[
"findMany"
].get({
query: { page, limit },
query: { page, limit, search },
});
if (res.status === 200 && res.data?.success) {

View File

@@ -65,7 +65,7 @@ const potensiDesa = proxy({
const res = await ApiFetch.api.desa.potensi[
"find-many"
].get({
query: { page, limit },
query: { page, limit, search },
});
if (res.status === 200 && res.data?.success) {

View File

@@ -312,15 +312,15 @@ const kategoriProduk = proxy({
page: 1,
totalPages: 1,
loading: false,
search2: "",
load: async (page = 1, limit = 10, search2 = "") => {
search: "",
load: async (page = 1, limit = 10, search = "") => {
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriProduk.findMany.page = page;
kategoriProduk.findMany.search2 = search2;
kategoriProduk.findMany.search = search;
try {
const query: any = { page, limit };
if (search2) query.search2 = search2;
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });

View File

@@ -194,7 +194,7 @@ const posisiOrganisasi = proxy({
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi['struktur-organisasi']['posisi-organisasi']['create'].post(this.form);
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["create"].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan posisi organisasi");
posisiOrganisasi.findMany.load();

View File

@@ -60,13 +60,18 @@ const responden = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
responden.findMany.loading = true; // Use the full path to access the property
responden.findMany.page = page;
responden.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.responden["findMany"].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -65,13 +66,46 @@ const dataPendidikan = proxy({
select: { id: true; name: true; jumlah: true };
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.pendidikan.datapendidikan[
"findMany"
].get();
if (res.status === 200) {
dataPendidikan.findMany.data = res.data?.data ?? [];
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
dataPendidikan.findMany.loading = true; // Use the full path to access the property
dataPendidikan.findMany.page = page;
dataPendidikan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.pendidikan.datapendidikan[
"findMany"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
dataPendidikan.findMany.data = res.data.data || [];
dataPendidikan.findMany.total = res.data.total || 0;
dataPendidikan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load data pendidikan:",
res.data?.message
);
dataPendidikan.findMany.data = [];
dataPendidikan.findMany.total = 0;
dataPendidikan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading data pendidikan:", error);
dataPendidikan.findMany.data = [];
dataPendidikan.findMany.total = 0;
dataPendidikan.findMany.totalPages = 1;
} finally {
dataPendidikan.findMany.loading = false;
}
},
},

View File

@@ -220,11 +220,34 @@ const roleState = proxy({
isActive: true;
};
}>[],
page: 1,
totalPages: 1,
loading: false,
async load() {
const res = await ApiFetch.api.role["findMany"].get();
if (res.status === 200) {
roleState.findMany.data = res.data?.data ?? [];
search: "",
load: async (page = 1, limit = 10, search = "") => {
roleState.findMany.loading = true; // ✅ Akses langsung via nama path
roleState.findMany.page = page;
roleState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.role["findMany"].get({ query });
if (res.status === 200 && res.data?.success) {
roleState.findMany.data = res.data.data ?? [];
roleState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
roleState.findMany.data = [];
roleState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch role paginated:", err);
roleState.findMany.data = [];
roleState.findMany.totalPages = 1;
} finally {
roleState.findMany.loading = false;
}
},
},

View File

@@ -73,17 +73,17 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
>
{/* ✅ Scroll horizontal wrapper */}
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<ScrollArea type="auto" offsetScrollbars w="100%">
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
width: "max-content", // ⬅️ kunci
maxWidth: "100%",
}}
>
{tabs.map((tab, i) => (

View File

@@ -88,63 +88,65 @@ function ListVideo({ search }: { search: string }) {
{/* Desktop Table */}
<Box visibleFrom="md">
<Table highlightOnHover w="100%">
<TableThead>
<TableTr>
<TableTh>Judul Video</TableTh>
<TableTh>Tanggal</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fz="md" fw={500} lh={1.45} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed" lh={1.45}>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" lh={1.45} truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}
fz="sm"
px="xs"
>
<IconDeviceImac size={18} />
<Text ml={5}>Detail</Text>
</Button>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover striped verticalSpacing="sm">
<TableThead>
<TableTr>
<TableTh>Judul Video</TableTh>
<TableTh>Tanggal</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd style={{ maxWidth: 250 }}>
<Text fz="md" fw={500} lh={1.45} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd style={{ maxWidth: 250 }}>
<Text fz="sm" c="dimmed" lh={1.45}>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
</TableTd>
<TableTd style={{ maxWidth: 250 }}>
<Text fz="sm" lh={1.45} truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd style={{ maxWidth: 250 }}>
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}
fz="sm"
px="xs"
>
<IconDeviceImac size={18} />
<Text ml={5}>Detail</Text>
</Button>
</TableTd>
</TableTr>
))
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={24}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada video yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
))
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={24}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada video yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
)}
</TableTbody>
</Table>
)}
</TableTbody>
</Table>
</Box>
</Box>
{/* Mobile Cards */}

View File

@@ -5,8 +5,7 @@ import {
Button,
Center,
Divider,
Grid,
GridCol,
Group,
Paper,
Skeleton,
Stack,
@@ -43,32 +42,29 @@ function PelayananPendudukNonPermanent() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title
order={3}
lh={1.2}
c={colors['blue-button']}
>
Preview Pelayanan Penduduk Non Permanen
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
`/admin/desa/layanan/pelayanan_penduduk_non_permanent/${data.id}`
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title
order={3}
lh={1.2}
c={colors['blue-button']}
>
Preview Pelayanan Penduduk Non Permanen
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
`/admin/desa/layanan/pelayanan_penduduk_non_permanent/${data.id}`
)
}
>
Edit
</Button>
</Group>
{/* Content */}
<Paper p="xl" bg={'white'} withBorder radius="md" shadow="xs">

View File

@@ -6,8 +6,6 @@ import {
Button,
Center,
Divider,
Grid,
GridCol,
Group,
Paper,
Skeleton,
@@ -76,28 +74,24 @@ function PerizinanBerusaha() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']} lh={1.2}>
Preview Pelayanan Perizinan Berusaha
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
`/admin/desa/layanan/pelayanan_perizinan_berusaha/${data.id}`
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']} lh={1.2}>
Preview Pelayanan Perizinan Berusaha
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
`/admin/desa/layanan/pelayanan_perizinan_berusaha/${data.id}`
)
}
>
Edit
</Button>
</Group>
{/* Content */}
<Paper p="xl" bg={'white'} withBorder radius="md" shadow="xs">
@@ -136,7 +130,7 @@ function PerizinanBerusaha() {
umum:
</Text>
<Box p="xl" w="100%" visibleFrom='md'>
<Box p="xl" w="100%" visibleFrom='md'>
<Stepper
active={active}
onStepClick={setActive}
@@ -221,37 +215,37 @@ function PerizinanBerusaha() {
>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
<Text fz="sm" lh={1.5}>
</Text>
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
<Text fz="sm" lh={1.5}>
</Text>
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI">
<Text fz="sm" lh={1.5}>
</Text>
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
<Text fz="sm" lh={1.5}>
</Text>
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
<Text fz="sm" lh={1.5}>
</Text>
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
<Text fz="sm" lh={1.5}>
</Text>
</StepperStep>
<StepperCompleted>
<Text fz="sm" lh={1.5}>
</Text>
</StepperCompleted>
</Stepper>

View File

@@ -1,7 +1,7 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Center, Divider, Grid, GridCol, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
@@ -31,22 +31,18 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header + tombol edit */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={2} c={colors['blue-button']} lh={1.2} />
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profil/profil-perbekel/${perbekel.id}`)}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify="space-between">
<Title order={2} c={colors['blue-button']} lh={1.2}>Profil Perbekel</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profil/profil-perbekel/${perbekel.id}`)}
>
Edit
</Button>
</Group>
{/* Card Profil */}
<Paper p="xl" bg={colors['white-1']} withBorder radius="md" shadow="xs">
@@ -60,7 +56,7 @@ function Page() {
<GridCol span={12}>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
fz={{ base: 'sm', md: 'xl' }}
fw="bold"
c={colors['blue-button']}
lh={{ base: 1.45, md: 1.45 }}

View File

@@ -166,7 +166,7 @@ function ListAPBDesa({ search }: { search: string }) {
<TableTd>
<Button
variant="light"
color="green"
color="blue"
onClick={() =>
router.push(
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`
@@ -243,7 +243,7 @@ function ListAPBDesa({ search }: { search: string }) {
</Box>
<Button
variant="light"
color="green"
color="blue"
fullWidth
onClick={() =>
router.push(

View File

@@ -128,10 +128,18 @@ function ListBelanja({ search }: { search: string }) {
>
<TableThead>
<TableTr>
<TableTh style={{ width: '35%' }}>Nama</TableTh>
<TableTh style={{ width: '25%' }}>Nilai</TableTh>
<TableTh style={{ width: '20%' }}>Persentase</TableTh>
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
<TableTh style={{ width: '40%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nilai</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="sm" fw={600} lh={1.4}>Edit</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>Delete</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>

View File

@@ -120,12 +120,20 @@ function ListPembiayaan({ search }: { search: string }) {
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '35%' }}>Nama</TableTh>
<TableTh style={{ width: '25%' }}>Nilai</TableTh>
<TableTh style={{ width: '20%' }}>Persentase</TableTh>
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
<TableThead>
<TableTr>
<TableTh style={{ width: '40%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nilai</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="sm" fw={600} lh={1.4}>Edit</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>Delete</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>

View File

@@ -160,7 +160,6 @@ function ListPendapatan({ search }: { search: string }) {
px="xs"
>
<IconEdit size={16} />
<Text ml={5}>Edit</Text>
</Button>
</TableTd>
<TableTd>
@@ -176,7 +175,6 @@ function ListPendapatan({ search }: { search: string }) {
px="xs"
>
<IconTrash size={16} />
<Text ml={5}>Hapus</Text>
</Button>
</TableTd>
</TableTr>

View File

@@ -153,9 +153,14 @@ function ListPosisiOrganisasiBumDes({ search }: { search: string }) {
</Text>
</TableTd>
<TableTd>
<Text fz="sm" fw={500} lh={1.45} c="dimmed" lineClamp={2}>
{item.deskripsi || '-'}
</Text>
<Text
fz="sm"
fw={500}
lh={1.45}
c="dimmed"
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
/>
</TableTd>
<TableTd>
<Text fz="md" fw={500} lh={1.45}>{item.hierarki || '-'}</Text>
@@ -223,9 +228,14 @@ function ListPosisiOrganisasiBumDes({ search }: { search: string }) {
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.deskripsi || '-'}
</Text>
<Text
fz="sm"
fw={500}
lh={1.45}
c="dimmed"
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
/>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>

View File

@@ -21,7 +21,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -59,6 +59,8 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
const [mounted, setMounted] = useState(false);
const [modalHapus, setModalHapus] = useState(false);
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [selectedId, setSelectedId] = useState<string | null>(null);
const {
@@ -79,8 +81,8 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
useShallowEffect(() => {
setMounted(true);
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
useEffect(() => {
if (data) {

View File

@@ -28,27 +28,27 @@ import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
function KategoriProduk() {
const [search2, setSearch2] = useState('');
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Kategori Produk'
placeholder='Cari nama kategori produk...'
searchIcon={<IconSearch size={20} />}
value={search2}
onChange={(e) => setSearch2(e.currentTarget.value)}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListKategoriProduk search2={search2} />
<ListKategoriProduk search={search} />
</Box>
);
}
function ListKategoriProduk({ search2 }: { search2: string }) {
function ListKategoriProduk({ search }: { search: string }) {
const statePasar = useProxy(pasarDesaState.kategoriProduk);
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search2, 1000);
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = statePasar.findMany;

View File

@@ -194,7 +194,7 @@ function ListProgramKemiskinan({ search }: { search: string }) {
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="sm">
<Stack gap={4}>
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Judul Program

View File

@@ -40,7 +40,7 @@ function DetailAjukanIdeInofativDesa() {
const data = state.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Button
variant="subtle"
@@ -54,7 +54,7 @@ function DetailAjukanIdeInofativDesa() {
{/* Card Utama */}
<Paper
withBorder
w={{ base: "100%", md: "80%", lg: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -17,7 +17,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -44,6 +44,7 @@ function AjukanIdeInovatif() {
function ListAjukanIdeInovatif({ search }: { search: string }) {
const state = useProxy(ajukanIdeInovatifState)
const router = useRouter()
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const {
data,
@@ -54,8 +55,8 @@ function ListAjukanIdeInovatif({ search }: { search: string }) {
} = state.findMany;
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch)
}, [page, debouncedSearch])
const filteredData = data || []

View File

@@ -111,7 +111,7 @@ function EditDigitalSmartVillage() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -49,7 +49,7 @@ function DetailDesaDigital() {
const data = stateDesaDigital.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Button
variant="subtle"
@@ -63,7 +63,7 @@ function DetailDesaDigital() {
{/* Card Utama */}
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"
@@ -77,12 +77,12 @@ function DetailDesaDigital() {
{/* Sub Card Detail */}
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm">
<Box>
<Box pl={5}>
<Text fz="lg" fw="bold">Judul</Text>
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
</Box>
<Box>
<Box pl={5}>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Text
fz="md"

View File

@@ -73,7 +73,7 @@ export default function CreateDesaDigital() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header dengan tombol kembali */}
<Group mb="md" align="center">
<Button

View File

@@ -18,7 +18,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -45,28 +45,31 @@ function DesaDigitalSmartVillage() {
function ListDesaDigitalSmartVillage({ search }: { search: string }) {
const state = useProxy(desaDigitalState);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="lg">
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Box py="lg">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>List Desa Digital Smart Village</Title>
<Title order={4} lh={1.2}>
List Desa Digital Smart Village
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -78,15 +81,34 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="sm">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '30%' }}>Nama Inovasi</TableTh>
<TableTh style={{ width: '50%' }}>
Deskripsi Singkat Inovasi
<TableTh style={{ width: '30%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Nama Inovasi
</Text>
</TableTh>
<TableTh style={{ width: '50%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi Singkat Inovasi
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Aksi
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -94,21 +116,18 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={200}>
<Text fw={500} truncate="end" lineClamp={1}>
{item.name}
</Text>
</Box>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.name}
</Text>
</TableTd>
<TableTd>
<Box w={200}>
<Text
fz="sm"
c="dimmed"
lineClamp={1}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</Box>
<Text
fz="sm"
c="dimmed"
lh={1.5}
lineClamp={1}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</TableTd>
<TableTd>
<Button
@@ -121,7 +140,9 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
}
>
<IconDeviceImac size={20} />
<Text ml={5}>Detail</Text>
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</TableTd>
</TableTr>
@@ -129,8 +150,8 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data inovasi digital yang cocok
</Text>
</Center>
@@ -140,6 +161,64 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="sm">
<Stack gap="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={4}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Inovasi
</Text>
<Text fz="sm" fw={500} lh={1.5}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi Singkat Inovasi
</Text>
<Box pl={5}>
<Text
fz="sm"
fw={500}
lh={1.5}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</Box>
</Box>
<Box>
<Button
variant="light"
color="blue"
fullWidth
onClick={() =>
router.push(
`/admin/inovasi/desa-digital-smart-village/${item.id}`
)
}
>
<IconDeviceImac size={20} />
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data inovasi digital yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
<Pagination

View File

@@ -123,7 +123,7 @@ function EditInfoTeknologiTepatGuna() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol back + title */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -40,7 +40,7 @@ function DetailInfoTeknologiTepatGuna() {
const data = stateInfoTekno.findUnique.data
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Button
variant="subtle"
@@ -54,7 +54,7 @@ function DetailInfoTeknologiTepatGuna() {
{/* Card Utama */}
<Paper
withBorder
w={{ base: "100%", md: "50%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -74,7 +74,7 @@ function CreateInfoTeknologiTepatGuna() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -16,9 +16,9 @@ import {
TableThead,
TableTr,
Text,
Title
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -45,28 +45,31 @@ function InfoTeknologiTepatGuna() {
function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
const state = useProxy(infoTeknoState);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'lg' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Box py={{ base: 'sm', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Info Teknologi Tepat Guna</Title>
<Title order={4} lh={1.2}>
Daftar Info Teknologi Tepat Guna
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -79,40 +82,57 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '30%' }}>
Nama Info Teknologi
<Text fz="sm" fw={600} lh={1.4}>
Nama Info Teknologi
</Text>
</TableTh>
<TableTh style={{ width: '50%' }}>
Deskripsi Singkat
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi Singkat
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Aksi
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd style={{ width: '30%' }}>
<Text fw={500} truncate="end" lineClamp={1}>
<TableTd>
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd style={{ width: '50%' }}>
<TableTd>
<Text
fz="sm"
lh={1.5}
c="dimmed"
lineClamp={1}
truncate
fz="sm"
c="dimmed"
dangerouslySetInnerHTML={{
__html: item.deskripsi || '-',
}}
/>
</TableTd>
<TableTd style={{ width: '20%' }}>
<TableTd>
<Button
size="xs"
radius="md"
@@ -134,7 +154,7 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">
<Text fz="sm" c="dimmed" ta="center">
Tidak ada data Info Teknologi yang cocok
</Text>
</Center>
@@ -144,6 +164,63 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Stack hiddenFrom="md" gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz={"sm"} fw={600} lh={1.4}>
Nama Info Teknologi
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box>
<Text fz={"sm"} fw={600} lh={1.4}>
Deskripsi Singkat
</Text>
<Box pl={5}>
<Text
fz="sm"
fw={500}
lh={1.4}
dangerouslySetInnerHTML={{
__html: item.deskripsi || '-',
}}
/>
</Box>
</Box>
<Group justify="flex-end">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/inovasi/info-teknologi-tepat-guna/${item.id}`,
)
}
>
Detail
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text fz="sm" c="dimmed" ta="center">
Tidak ada data Info Teknologi yang cocok
</Text>
</Center>
)}
</Stack>
</Paper>
<Center>
@@ -164,4 +241,4 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
);
}
export default InfoTeknologiTepatGuna;
export default InfoTeknologiTepatGuna;

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconListDetails, IconUsers } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -57,36 +57,76 @@ function LayoutTabsKolaborasi({ children }: { children: React.ReactNode }) {
keepMounted={false}
>
{/* ✅ Scroll horizontal wrapper biar rapi */}
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel
key={i}

View File

@@ -1,7 +1,29 @@
'use client'
import React from 'react';
import LayoutTabsKolaborasi from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabsKolaborasi>
{children}

View File

@@ -118,7 +118,7 @@ function EditKolaborasiInovasi() {
};
return (
<Box px={{ base: "sm", md: "lg" }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors["blue-button"]} size={24} />

View File

@@ -41,7 +41,7 @@ function DetailKolaborasiInovasi() {
const data = kolaborasiState.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */}
<Button
variant="subtle"

View File

@@ -55,7 +55,7 @@ function CreateProgramKreatifDesa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Back Button */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -19,6 +19,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -45,69 +46,105 @@ function KolaborasiInovasi() {
function ListKolaborasiInovasi({ search }: { search: string }) {
const listState = useProxy(kolaborasiInovasiState);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, loading, page, totalPages, load } = listState.findMany;
useEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'xs', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Kolaborasi Inovasi</Title>
<Box py={{ base: 'xs', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Kolaborasi Inovasi
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/create')}
onClick={() =>
router.push('/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/create')
}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%' }}>No</TableTh>
<TableTh style={{ width: '25%' }}>Nama Kolaborasi Inovasi</TableTh>
<TableTh style={{ width: '15%' }}>Tahun</TableTh>
<TableTh style={{ width: '35%' }}>Deskripsi Singkat</TableTh>
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
<TableTh style={{ width: '5%' }}>
<Text fz="xs" fw={600} lh={1.4} ta="center">
No
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Nama Kolaborasi Inovasi
</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="xs" fw={600} lh={1.4} ta="center">
Tahun
</Text>
</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Deskripsi Singkat
</Text>
</TableTh>
<TableTh style={{ width: '10%' }}>
<Text fz="xs" fw={600} lh={1.4} ta="center">
Aksi
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>{index + 1}</TableTd>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>
{item.name}
<TableTd ta="center">
<Text fz="sm" fw={500} lh={1.45}>
{index + 1}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed">
<Text fz="sm" fw={500} lh={1.45} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd ta="center">
<Text fz="sm" fw={500} lh={1.45}>
{item.tahun}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" truncate="end" lineClamp={1} c="dimmed">
{item.slug}
</Text>
<Text dangerouslySetInnerHTML={{ __html: item.slug }} fz="sm" fw={500} lh={1.45} truncate="end" lineClamp={1} />
</TableTd>
<TableTd>
<TableTd ta="center">
<Button
size="xs"
radius="md"
@@ -115,7 +152,9 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(`/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/${item.id}`)
router.push(
`/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/${item.id}`
)
}
>
Detail
@@ -127,7 +166,9 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
<TableTr>
<TableTd colSpan={5}>
<Center py={20}>
<Text color="dimmed">Tidak ada data kolaborasi inovasi yang tersedia</Text>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kolaborasi inovasi yang tersedia
</Text>
</Center>
</TableTd>
</TableTr>
@@ -135,23 +176,93 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper key={item.id} withBorder radius="md" p="sm">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Kolaborasi Inovasi
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Tahun
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.tahun}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi Singkat
</Text>
<Text dangerouslySetInnerHTML={{ __html: item.slug }} fz="sm" fw={500} lh={1.45} truncate="end" lineClamp={1} />
</Box>
<Box>
<Button
fullWidth
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/${item.id}`
)
}
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kolaborasi inovasi yang tersedia
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
mb="md"
color="blue"
radius="md"
/>
</Center>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt={{ base: 'sm', md: 'md' }}
mb={{ base: 'sm', md: 'md' }}
color="blue"
radius="md"
/>
</Center>
)}
</Box>
);
}
export default KolaborasiInovasi;
export default KolaborasiInovasi;

View File

@@ -136,7 +136,7 @@ function EditMitraKolaborasi() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -68,7 +68,7 @@ function CreateMitraKolaborasi() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Back Button + Title */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -18,7 +18,7 @@ import {
TableThead,
TableTr,
Text,
Title
Title,
} from '@mantine/core';
import { IconEdit, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -27,6 +27,7 @@ import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import mitraKolaborasi from '../../../_state/inovasi/mitra-kolaborasi';
import { useDebouncedValue } from '@mantine/hooks';
function MitraKolaborasi() {
const [search, setSearch] = useState('');
@@ -59,27 +60,34 @@ function ListMitraKolaborasi({ search }: { search: string }) {
}
};
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, loading, page, totalPages, load } = listState.findMany;
useEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="lg">
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Box py="lg">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Mitra Kolaborasi</Title>
<Group justify="space-between" mb="lg">
<Title
order={4}
lh={{ base: 1.2, md: 1.15 }}
>
Daftar Mitra Kolaborasi
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -93,41 +101,82 @@ function ListMitraKolaborasi({ search }: { search: string }) {
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '10%' }}>No</TableTh>
<TableTh style={{ width: '30%' }}>Nama Mitra</TableTh>
<TableTh style={{ width: '25%' }}>Image</TableTh>
<TableTh style={{ width: '15%' }}>Delete</TableTh>
<TableTh style={{ width: '15%' }}>Edit</TableTh>
<TableTh style={{ width: '10%' }}>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.4}>
No
</Text>
</TableTh>
<TableTh style={{ width: '30%' }}>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.4}>
Nama Mitra
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.4}>
Image
</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.4}>
Delete
</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.4}>
Edit
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>{index + 1}</TableTd>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>
<Text fz={{ base: 'xs', md: 'sm' }} fw={500} lh={1.5}>
{index + 1}
</Text>
</TableTd>
<TableTd>
<Text
fz={{ base: 'sm', md: 'md' }}
fw={500}
lh={1.5}
truncate="end"
lineClamp={1}
>
{item.name}
</Text>
</TableTd>
<TableTd>
<Box
w={70}
h={70}
>
<Box w={70} h={70}>
{item.image?.link ? (
<Image
loading="lazy"
src={item.image.link}
alt={item.name}
fit="cover"
radius="sm"
/>
) : (
<Box bg={colors['blue-button']} w="100%" h="100%" />
<Box
bg={colors['blue-button']}
w="100%"
h="100%"
/>
)}
</Box>
</TableTd>
@@ -167,8 +216,8 @@ function ListMitraKolaborasi({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={5}>
<Center py={20}>
<Text color="dimmed">
<Center py="xl">
<Text c="dimmed" fz="sm" ta="center">
Tidak ada data mitra kolaborasi yang tersedia
</Text>
</Center>
@@ -178,7 +227,96 @@ function ListMitraKolaborasi({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Mitra
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Image
</Text>
<Box w="100%" h={60} mt="xs">
{item.image?.link ? (
<Image
loading="lazy"
src={item.image.link}
alt={item.name}
fit="cover"
radius="sm"
h="100%"
/>
) : (
<Box
bg={colors['blue-button']}
w="100%"
h="100%"
/>
)}
</Box>
</Box>
<Group justify="flex-end" mt="md" gap="xs">
<Button
size="xs"
radius="md"
variant="light"
color="red"
disabled={mitraKolaborasi.delete.loading || !item}
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
<IconX size={16} />
</Button>
<Button
size="xs"
radius="md"
variant="light"
color="green"
disabled={!item}
onClick={() =>
router.push(
`/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi/${item.id}`
)
}
>
<IconEdit size={16} />
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" ta="center">
Tidak ada data mitra kolaborasi yang tersedia
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
<Pagination
value={page}
@@ -187,14 +325,13 @@ function ListMitraKolaborasi({ search }: { search: string }) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
mb="md"
mt="lg"
mb="lg"
color="blue"
radius="md"
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
@@ -205,4 +342,4 @@ function ListMitraKolaborasi({ search }: { search: string }) {
);
}
export default MitraKolaborasi;
export default MitraKolaborasi;

View File

@@ -2,6 +2,7 @@
'use client'
import colors from '@/con/colors';
import {
Box,
ScrollArea,
Stack,
Tabs,
@@ -84,36 +85,76 @@ function LayoutTabsLayananOnlineDesa({ children }: { children: React.ReactNode }
keepMounted={false}
>
{/* ✅ Scroll horizontal biar gak overflow */}
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel
key={i}

View File

@@ -42,8 +42,8 @@ function DetailAdministrasiOnline() {
const data = stateAdminOnline.findUnique.data;
return (
<Box py={10}>
<Group justify='space-between' align='center' w={{ base: "100%", md: "50%" }} mb={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group justify='space-between' align='center' w={{ base: "100%", md: "70%" }} mb={10}>
{/* Tombol Kembali */}
<Button
variant="subtle"

View File

@@ -17,7 +17,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -44,36 +44,55 @@ function AdministrasiOnline() {
function ListAdministrasiOnline({ search }: { search: string }) {
const state = useProxy(layananonlineDesa.administrasiOnline);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Title order={4}>Daftar Administrasi Online</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Title order={4} lh={1.2} mb={{ base: 'md', md: 'lg' }}>
Daftar Administrasi Online
</Title>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '25%' }}>Nama</TableTh>
<TableTh style={{ width: '25%' }}>Alamat</TableTh>
<TableTh style={{ width: '20%' }}>Nomor Telepon</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4}>Alamat</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nomor Telepon</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="sm" fw={600} lh={1.4}>Aksi</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -81,17 +100,17 @@ function ListAdministrasiOnline({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed" lineClamp={1}>
<Text fz="sm" c="gray.7" lh={1.5} lineClamp={1}>
{item.alamat}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed" lineClamp={1}>
<Text fz="sm" c="gray.7" lh={1.5} lineClamp={1}>
{item.nomorTelepon || '-'}
</Text>
</TableTd>
@@ -116,8 +135,10 @@ function ListAdministrasiOnline({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text color="dimmed">Tidak ada data administrasi online yang cocok</Text>
<Center py={24}>
<Text c="gray.6" fz="sm" lh={1.4}>
Tidak ada data administrasi online yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -125,6 +146,55 @@ function ListAdministrasiOnline({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} p="md" withBorder>
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.name}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Alamat</Text>
<Text fz="sm" fw={500} c="gray.7" lh={1.5}>{item.alamat}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nomor Telepon</Text>
<Text fz="sm" fw={500} c="gray.7" lh={1.5}>{item.nomorTelepon || '-'}</Text>
</Box>
<Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/inovasi/layanan-online-desa/administrasi-online/${item.id}`
)
}
fullWidth
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={24}>
<Text c="gray.6" fz="sm" lh={1.4}>
Tidak ada data administrasi online yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>

View File

@@ -90,7 +90,7 @@ function EditJenisLayanan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button

View File

@@ -40,7 +40,7 @@ function DetailJenisLayanan() {
const data = state.findUnique.data
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */}
<Button
variant="subtle"
@@ -54,7 +54,7 @@ function DetailJenisLayanan() {
{/* Card utama */}
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -50,7 +50,7 @@ function CreateJenisLayanan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header dengan tombol back */}
<Group mb="md">
<Button

View File

@@ -16,9 +16,10 @@ import {
TableThead,
TableTr,
Text,
Title
Title,
useMantineTheme
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -45,28 +46,35 @@ function JenisLayanan() {
function ListJenisLayanan({ search }: { search: string }) {
const stateList = useProxy(layananonlineDesa.jenisLayanan);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = stateList.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
const theme = useMantineTheme();
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="lg">
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Stack py="lg" gap="xl">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Jenis Layanan</Title>
<Title
order={4}
lh={{ base: 1.2, md: 1.15 }}
>
Daftar Jenis Layanan
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -80,13 +88,34 @@ function ListJenisLayanan({ search }: { search: string }) {
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '30%' }}>Nama Jenis Layanan</TableTh>
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
<TableTh style={{ width: '30%' }}>
<Text fz="sm" fw={600} lh={1.4} c={theme.black}>
Nama Jenis Layanan
</Text>
</TableTh>
<TableTh style={{ width: '55%' }}>
<Text fz="sm" fw={600} lh={1.4} c={theme.black}>
Deskripsi
</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="center" c={theme.black}>
Aksi
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -94,16 +123,28 @@ function ListJenisLayanan({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd style={{ width: '30%' }}>
<Text fw={500} truncate="end" lineClamp={1}>
<Text
fz="md"
fw={500}
lh={1.5}
c={theme.black}
truncate="end"
lineClamp={1}
>
{item.nama}
</Text>
</TableTd>
<TableTd style={{ width: '40%' }}>
<Text fz="sm" c="dimmed" lineClamp={2}>
{item.deskripsi || '-'}
</Text>
<TableTd style={{ width: '55%' }}>
<Text
fz="sm"
fw={500}
lh={1.5}
c={theme.black}
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
/>
</TableTd>
<TableTd style={{ width: '15%' }}>
<TableTd style={{ width: '15%' }} ta="center">
<Button
size="xs"
radius="md"
@@ -124,8 +165,8 @@ function ListJenisLayanan({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada jenis layanan yang cocok
</Text>
</Center>
@@ -135,22 +176,80 @@ function ListJenisLayanan({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder radius="md" p="md">
<Stack gap="xs">
<Box pl={5}>
<Text fz="sm" fw={600} lh={1.4} c={theme.black}>
Nama Jenis Layanan
</Text>
<Text fz="sm" fw={500} lh={1.5} c={theme.black}>
{item.nama}
</Text>
</Box>
<Box pl={5}>
<Text fz="sm" fw={600} lh={1.4} c={theme.black}>
Deskripsi
</Text>
<Text
fz="sm"
fw={500}
lh={1.5}
c={theme.black}
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
/>
</Box>
<Group justify="flex-end" mt="xs">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/inovasi/layanan-online-desa/jenis-layanan/${item.id}`
)
}
>
Detail
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada jenis layanan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
mb="md"
color="blue"
radius="md"
/>
</Center>
</Box>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
color="blue"
radius="md"
/>
</Center>
)}
</Stack>
);
}

View File

@@ -105,7 +105,7 @@ function EditJenisPengaduan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button

View File

@@ -47,7 +47,7 @@ function CreateJenisPengaduan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -18,7 +18,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -48,12 +48,13 @@ function ListJenisPengaduan({ search }: { search: string }) {
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load, } = state.findMany;
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const handleHapus = async () => {
if (selectedId) {
@@ -63,21 +64,23 @@ function ListJenisPengaduan({ search }: { search: string }) {
}
};
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'md', md: 'lg' }}>
<Skeleton height={500} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Jenis Pengaduan</Title>
<Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Jenis Pengaduan
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -92,13 +95,33 @@ function ListJenisPengaduan({ search }: { search: string }) {
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '60%' }}>Nama Jenis Pengaduan</TableTh>
<TableTh style={{ width: '20%' }}>Edit</TableTh>
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
<TableTh style={{ width: '60%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dimmed">
Nama Jenis Pengaduan
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dimmed">
Edit
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dimmed">
Hapus
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -106,7 +129,7 @@ function ListJenisPengaduan({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.nama}
</Text>
</TableTd>
@@ -147,7 +170,7 @@ function ListJenisPengaduan({ search }: { search: string }) {
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada jenis pengaduan yang cocok
</Text>
</Center>
@@ -157,6 +180,63 @@ function ListJenisPengaduan({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Jenis Pengaduan
</Text>
<Text fz="sm" fw={500} lh={1.45}>
{item.nama}
</Text>
</Box>
<Group justify="flex-end" mt="xs">
<Button
size="xs"
radius="md"
variant="light"
color="green"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
`/admin/inovasi/layanan-online-desa/jenis-pengaduan/${item.id}`
)
}
>
Edit
</Button>
<Button
size="xs"
radius="md"
variant="light"
color="red"
leftSection={<IconTrash size={16} />}
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
Hapus
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada jenis pengaduan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
@@ -167,14 +247,13 @@ function ListJenisPengaduan({ search }: { search: string }) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
mb="md"
mt={{ base: 'sm', md: 'md' }}
mb={{ base: 'sm', md: 'md' }}
color="blue"
radius="md"
/>
</Center>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
@@ -185,4 +264,4 @@ function ListJenisPengaduan({ search }: { search: string }) {
);
}
export default JenisPengaduan;
export default JenisPengaduan;

View File

@@ -1,7 +1,28 @@
'use client'
import { Box } from "@mantine/core";
import LayoutTabsLayananOnlineDesa from "./_lib/layoutTabs";
import { usePathname } from "next/navigation";
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabsLayananOnlineDesa>
{children}

View File

@@ -41,7 +41,7 @@ function DetailPengaduanMasyarakat() {
const data = pengaduanState.pengaduanMasyarakat.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Button
variant="subtle"
@@ -55,7 +55,7 @@ function DetailPengaduanMasyarakat() {
{/* Card Detail */}
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -19,8 +19,7 @@ import {
Title
} from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -46,6 +45,7 @@ function PengaduanMasyarakat() {
function ListPengaduanMasyarakat({ search }: { search: string }) {
const listState = useProxy(layananonlineDesa.pengaduanMasyarakat);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
page,
@@ -55,56 +55,84 @@ function ListPengaduanMasyarakat({ search }: { search: string }) {
} = listState.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Pengaduan Masyarakat</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
{/* Section Header */}
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Pengaduan Masyarakat
</Title>
</Group>
<Box style={{ overflowX: "auto" }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '25%' }}>Nama</TableTh>
<TableTh style={{ width: '25%' }}>Email</TableTh>
<TableTh style={{ width: '20%' }}>Nomor Telepon</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4}>Email</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>Nomor Telepon</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="sm" fw={600} lh={1.4}>Aksi</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd style={{ width: '25%' }}>
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
<TableTd>
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd style={{ width: '25%' }}>
<Text fz="sm" c="dimmed" truncate lineClamp={1}>{item.email}</Text>
<TableTd>
<Text fz="sm" c="dimmed" lh={1.5} truncate lineClamp={1}>
{item.email}
</Text>
</TableTd>
<TableTd style={{ width: '20%' }}>
<Text fz="sm" c="dimmed" truncate lineClamp={1}>{item.nomorTelepon}</Text>
<TableTd>
<Text fz="sm" c="dimmed" lh={1.5} truncate lineClamp={1}>
{item.nomorTelepon}
</Text>
</TableTd>
<TableTd style={{ width: '15%' }}>
<TableTd>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() => router.push(`/admin/inovasi/layanan-online-desa/pengaduan-masyarakat/${item.id}`)}
onClick={() =>
router.push(`/admin/inovasi/layanan-online-desa/pengaduan-masyarakat/${item.id}`)
}
>
Detail
</Button>
@@ -114,8 +142,10 @@ function ListPengaduanMasyarakat({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text color="dimmed">Tidak ada data pengaduan yang cocok</Text>
<Center py={{ base: 'sm', md: 'lg' }}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data pengaduan yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -123,7 +153,56 @@ function ListPengaduanMasyarakat({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={'xs'}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
<Text fz="sm" fw={500} lh={1.45}>{item.name}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Email</Text>
<Text fz="sm" c="dimmed" fw={500} lh={1.45}>{item.email}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nomor Telepon</Text>
<Text fz="sm" c="dimmed" fw={500} lh={1.45}>{item.nomorTelepon}</Text>
</Box>
<Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(`/admin/inovasi/layanan-online-desa/pengaduan-masyarakat/${item.id}`)
}
fullWidth
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="lg">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data pengaduan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
{/* Pagination */}
<Center>
<Pagination
value={page}
@@ -142,4 +221,4 @@ function ListPengaduanMasyarakat({ search }: { search: string }) {
);
}
export default PengaduanMasyarakat;
export default PengaduanMasyarakat;

View File

@@ -160,7 +160,7 @@ function EditProgramKreatifDesa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button
variant="subtle"

View File

@@ -41,7 +41,7 @@ function DetailProgramKreatifDesa() {
const data = stateProgramKreatif.findUnique.data
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Button
variant="subtle"
onClick={() => router.back()}
@@ -53,7 +53,7 @@ function DetailProgramKreatifDesa() {
<Paper
withBorder
w={{ base: "100%", md: "50%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -52,7 +52,7 @@ function CreateProgramKreatifDesa() {
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -18,7 +18,7 @@ import {
TableThead,
TableTr,
Text,
Title
Title,
} from '@mantine/core';
import {
IconCash,
@@ -50,9 +50,10 @@ import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import programKreatifState from '../../_state/inovasi/program-kreatif';
import { useDebouncedValue } from '@mantine/hooks';
function ProgramKreatifDesa() {
const [search, setSearch] = useState("");
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
@@ -71,12 +72,13 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
const listState = useProxy(programKreatifState);
const { data, loading, page, totalPages, load } = listState.findMany;
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
useEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
const iconMap: Record<string, React.FC<any>> = {
ekowisata: IconLeaf,
@@ -103,7 +105,7 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'xl' }}>
<Skeleton height={650} radius="md" />
</Stack>
);
@@ -111,35 +113,44 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
if (data.length === 0) {
return (
<Box py={10}>
<Paper p="md" radius="md" shadow="sm" withBorder>
<Stack>
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Program Kreatif Desa</Title>
<Box py={{ base: 'sm', md: 'xl' }}>
<Paper p="lg" radius="md" shadow="sm" withBorder>
<Stack gap="xl">
<Group justify="space-between" wrap="nowrap">
<Title order={4} lh={1.15}>
Daftar Program Kreatif Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() =>
router.push(
'/admin/inovasi/program-kreatif-desa/create'
)
router.push('/admin/inovasi/program-kreatif-desa/create')
}
>
Tambah Baru
</Button>
</Group>
<Table highlightOnHover striped>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama Program Kreatif Desa</TableTh>
<TableTh>Deskripsi Singkat</TableTh>
<TableTh>Ikon</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
striped
style={{ tableLayout: 'fixed', width: '100%' }}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh style={{ width: '20%' }}>Nama Program Kreatif Desa</TableTh>
<TableTh style={{ width: '35%' }}>Deskripsi Singkat</TableTh>
<TableTh style={{ width: '10%', textAlign: 'center' }}>Ikon</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
<Text ta="center" c="dimmed" py="lg">
Tidak ada data program kreatif desa yang tersedia
</Text>
@@ -150,7 +161,7 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
}
return (
<Box py={10}>
<Box py={{ base: 'sm', md: 'xl' }}>
<Paper
withBorder
bg={colors['white-1']}
@@ -159,58 +170,83 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
radius="md"
h={{ base: 'auto', md: 650 }}
>
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Program Kreatif Desa</Title>
<Group justify="space-between" mb="xl" wrap="nowrap">
<Title order={4} lh={1.15}>
Daftar Program Kreatif Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() =>
router.push(
'/admin/inovasi/program-kreatif-desa/create'
)
router.push('/admin/inovasi/program-kreatif-desa/create')
}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowY: 'auto' }}>
<Table highlightOnHover striped >
{/* Desktop Table */}
<Box visibleFrom="md" style={{ height: '100%', overflowY: 'auto' }}>
<Table
highlightOnHover
miw={0}
striped
style={{ tableLayout: 'fixed', width: '100%' }}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh style={{ width: '20%' }}>
<Text lineClamp={1} fw={"bold"} fz="sm">Nama Program Kreatif Desa</Text>
<TableTh style={{ width: '5%', textAlign: 'center' }}>
<Text fz="xs" fw={600} lh={1.2}>
No
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="xs" fw={600} lh={1.2}>
Nama Program Kreatif Desa
</Text>
</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="xs" fw={600} lh={1.2}>
Deskripsi Singkat
</Text>
</TableTh>
<TableTh style={{ width: '10%', textAlign: 'center' }}>
<Text fz="xs" fw={600} lh={1.2}>
Ikon
</Text>
</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>
<Text fz="xs" fw={600} lh={1.2}>
Detail
</Text>
</TableTh>
<TableTh style={{ width: '35%' }}>Deskripsi Singkat</TableTh>
<TableTh style={{ width: '10%', textAlign: 'center' }}>Ikon</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>
<Box w={200}>
<Text fw={500} lineClamp={1}>{item.name}</Text>
</Box>
<TableTd ta="center" fz="sm" fw={500} lh={1.45}>
{index + 1}
</TableTd>
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>
<Box w={150}>
<Text fz="sm" c="dimmed" lineClamp={1}>
{item.slug}
</Text>
</Box>
<TableTd>
<Text fz="sm" fw={500} lh={1.45} lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd style={{ width: '10%', textAlign: 'center' }}>
<TableTd>
<Text fz="sm" c="dimmed" lh={1.45} lineClamp={1}>
{item.slug}
</Text>
</TableTd>
<TableTd ta="center">
{iconMap[item.icon] && (
<Box title={item.icon}>
{React.createElement(iconMap[item.icon], { size: 24 })}
</Box>
)}
</TableTd>
<TableTd style={{ width: '15%', textAlign: 'center' }}>
<TableTd ta="center">
<Button
size="xs"
radius="md"
@@ -221,7 +257,9 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
}
>
<IconDeviceImac size={18} />
<Text ml={6}>Detail</Text>
<Text ml={6} fz="sm" fw={500}>
Detail
</Text>
</Button>
</TableTd>
</TableTr>
@@ -229,7 +267,72 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="md">
{filteredData.map((item, index) => (
<Paper key={item.id} p="md" withBorder radius="md">
<Stack gap={4}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Program Kreatif Desa
</Text>
<Text fz="sm" fw={500} lh={1.4} lineClamp={2}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi Singkat
</Text>
<Text fz="sm" fw={500} c="dimmed" lh={1.4} lineClamp={2}>
{item.slug}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Ikon
</Text>
<Box mt={4} ta="center">
{iconMap[item.icon] && (
<Box title={item.icon}>
{React.createElement(iconMap[item.icon], { size: 24 })}
</Box>
)}
</Box>
</Box>
<Box ta="center" mt="sm">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
onClick={() =>
router.push(`/admin/inovasi/program-kreatif-desa/${item.id}`)
}
>
<IconDeviceImac size={18} />
<Text ml={6} fz="sm" fw={500}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))}
</Stack>
</Box>
</Paper>
<Center>
<Pagination
value={page}
@@ -238,8 +341,8 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
mb="md"
mt="xl"
mb="xl"
color="blue"
radius="md"
/>
@@ -248,4 +351,4 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
);
}
export default ProgramKreatifDesa;
export default ProgramKreatifDesa;

View File

@@ -38,7 +38,7 @@ function ListTarifLayanan({ search }: { search: string }) {
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [debouncedSearch] = useDebouncedValue(search, 10000);
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,

View File

@@ -96,15 +96,15 @@ function ListKontakDarurat({ search }: { search: string }) {
{filteredData.length > 0 ? (
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<TableTd w={335}>
<Text fw={500} fz="md" lh={1.45} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd>
<TableTd w={335}>
<Text fz="sm" lh={1.45} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<TableTd w={335}>
<Button
variant="light"
color="blue"

View File

@@ -110,10 +110,10 @@ function ListProgramKesehatan({ search }: { search: string }) {
{item.name}
</Text>
</TableTd>
<TableTd w={200}>
<TableTd>
<Text fz="sm" lh={1.5} truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
</TableTd>
<TableTd w={200}>
<TableTd>
<Text fz="sm" lh={1.5} truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>

View File

@@ -18,7 +18,7 @@ import {
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -50,6 +50,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
const router = useRouter();
const { data, page, totalPages, loading, load } = stateKategori.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const handleHapus = () => {
if (selectedId) {
@@ -60,8 +61,8 @@ function ListKategoriKegiatan({ search }: { search: string }) {
};
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
@@ -211,7 +212,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
<Box hiddenFrom="md">{renderMobileCards()}</Box>
</Paper>
{totalPages > 1 && (
<Center mt="xl">
<Pagination
value={page}
@@ -224,7 +225,6 @@ function ListKategoriKegiatan({ search }: { search: string }) {
radius="md"
/>
</Center>
)}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,7 +1,7 @@
'use client'
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -29,10 +29,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
const router = useRouter();
const listState = useProxy(korupsiState.desaAntikorupsi);
const { data, page, totalPages, loading, load } = listState.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];

View File

@@ -2,7 +2,7 @@
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import {
Box,
Button,
@@ -47,16 +47,14 @@ interface ListRespondenProps {
function ListResponden({ search }: ListRespondenProps) {
const state = useProxy(indeksKepuasanState.responden);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10);
}, [page]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = (data || []).filter((item) => {
const keyword = search.toLowerCase();
return item.name.toLowerCase().includes(keyword);
});
const filteredData = data || [];
if (loading || !data) {
return (

View File

@@ -196,6 +196,7 @@ function ListMediaSosial({ search }: { search: string }) {
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama Media Sosial / Kontak</Text>
<Text fw={500} fz="sm" lh={1.45}>
@@ -221,37 +222,38 @@ function ListMediaSosial({ search }: { search: string }) {
return <Box bg={colors['blue-button']} w="100%" h="100%" />;
})()}
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Link / No. Telepon</Text>
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: 'underline' }}
>
<Text
fz="sm"
c="blue"
truncate
<Box>
<Text fz="sm" fw={600} lh={1.4}>Link / No. Telepon</Text>
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: 'underline' }}
>
{item.iconUrl || item.noTelp || '-'}
</Text>
</a>
</Box>
<Group mt="sm" justify="flex-end">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(`/admin/landing-page/profil/media-sosial/${item.id}`)
}
>
Detail
</Button>
</Group>
<Text
fz="sm"
c="blue"
truncate
>
{item.iconUrl || item.noTelp || '-'}
</Text>
</a>
</Box>
<Group mt="sm" justify="flex-end">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(`/admin/landing-page/profil/media-sosial/${item.id}`)
}
>
Detail
</Button>
</Group>
</Stack>
</Paper>
))
) : (

View File

@@ -1,7 +1,7 @@
'use client'
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
import colors from '@/con/colors';
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Center, Divider, Grid, GridCol, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
@@ -29,22 +29,18 @@ function Page() {
return (
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
style={{fontSize: 15, fontWeight: "bold"}}
c="green"
variant="light"
radius="md"
onClick={() => router.push(`/admin/landing-page/profil/pejabat-desa/${allList.findUnique.data?.id}`)}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify="space-between">
<Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title>
<Button
style={{ fontSize: 15, fontWeight: "bold" }}
c="green"
variant="light"
radius="md"
onClick={() => router.push(`/admin/landing-page/profil/pejabat-desa/${allList.findUnique.data?.id}`)}
>
Edit
</Button>
</Group>
{dataArray.map((item) => (
<Paper key={item.id} p="xl" bg={'white'} withBorder radius="md" shadow="xs">
<Box px={{ base: "sm", md: 100 }}>

View File

@@ -84,7 +84,9 @@ function DetailProgramInovasi() {
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.description || '-' }}></Text>
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.description || '-' }}></Text>
</Box>
</Box>
<Box>

View File

@@ -34,8 +34,8 @@ function ListProgramInovasi({ search }: { search: string }) {
const { data, page, totalPages, loading, load } = stateProgramInovasi.findMany;
useShallowEffect(() => {
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
@@ -144,9 +144,7 @@ function ListProgramInovasi({ search }: { search: string }) {
{/* Description */}
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
<Text fz="sm" c="gray.7" lineClamp={2}>
{item.description || '-'}
</Text>
<Text dangerouslySetInnerHTML={{ __html: item.description || '-' }} fz="sm" c="gray.7" lineClamp={2} />
</Box>
{/* Link */}

View File

@@ -132,7 +132,7 @@ export default function EditDataLingkunganDesa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -79,7 +79,7 @@ function DetailDataLingkunganDesa() {
const data = stateDataLingkungan.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Back Button */}
<Button
variant="subtle"
@@ -93,7 +93,7 @@ function DetailDataLingkunganDesa() {
{/* Main Card */}
<Paper
withBorder
w={{ base: '100%', md: '60%' }}
w={{ base: '100%', md: '70%' }}
bg={colors['white-1']}
p="lg"
radius="md"
@@ -131,7 +131,9 @@ function DetailDataLingkunganDesa() {
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
</Box>
</Box>
{/* Action Buttons */}

View File

@@ -49,7 +49,7 @@ function CreateDataLingkunganDesa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button

View File

@@ -1,30 +1,65 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
'use client';
import colors from '@/con/colors';
import {
Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack,
Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text,
Title
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core';
import {
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled,
IconDeviceImacCog, IconDroplet, IconHome, IconHomeEco, IconLeaf,
IconAlertTriangle,
IconAmbulance,
IconBuilding,
IconCash,
IconChartLine,
IconChristmasTreeFilled,
IconClipboardTextFilled,
IconDeviceImacCog,
IconDroplet,
IconFiretruck,
IconHome,
IconHomeEco,
IconHospital,
IconLeaf,
IconPlus,
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent,
IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled
IconRecycle,
IconScale,
IconSchool,
IconSearch,
IconShieldFilled,
IconShoppingCart,
IconTent,
IconTrashFilled,
IconTree,
IconTrendingUp,
IconTrophy,
IconTruckFilled,
} from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import dataLingkunganDesaState from '../../_state/lingkungan/data-lingkungan-desa';
import { useDebouncedValue } from '@mantine/hooks';
function DataLingkunganDesa() {
const [search, setSearch] = useState("");
const [search, setSearch] = useState('');
return (
<Box>
<Box py={{ base: 'md', md: 'lg' }} px={{ base: 'sm', md: 0 }}>
<HeaderSearch
title='Data Lingkungan Desa'
placeholder='Cari data lingkungan...'
@@ -38,15 +73,16 @@ function DataLingkunganDesa() {
}
function ListDataLingkunganDesa({ search }: { search: string }) {
const listState = useProxy(dataLingkunganDesaState)
const { data, loading, page, totalPages, load } = listState.findMany
const listState = useProxy(dataLingkunganDesaState);
const { data, loading, page, totalPages, load } = listState.findMany;
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
useEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
const iconMap: Record<string, React.FC<any>> = {
ekowisata: IconLeaf,
@@ -64,12 +100,22 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
mencegahBencana: IconShieldFilled,
rumah: IconHome,
pohon: IconTree,
air: IconDroplet
air: IconDroplet,
bantuan: IconCash,
pelatihan: IconSchool,
subsidi: IconShoppingCart,
layananKesehatan: IconHospital,
polisi: IconShieldFilled,
ambulans: IconAmbulance,
pemadam: IconFiretruck,
rumahSakit: IconHospital,
bangunan: IconBuilding,
darurat: IconAlertTriangle
};
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="md">
<Skeleton height={600} radius="md" />
</Stack>
);
@@ -77,46 +123,66 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
if (data.length === 0) {
return (
<Box py={10}>
<Paper withBorder shadow="md" radius="md" p="lg" bg={colors['white-1']}>
<Box py="md">
<Paper withBorder shadow="md" radius="md" p={{ base: 'md', md: 'lg' }} bg={colors['white-1']}>
<Stack>
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Data Lingkungan Desa</Title>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}>
<Title order={4} lh={1.2}>
Daftar Data Lingkungan Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}
>
Tambah Baru
</Button>
</Group>
<Table highlightOnHover>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama Data Lingkungan Desa</TableTh>
<TableTh>Jumlah</TableTh>
<TableTh>Ikon</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
<Center py={20}>
<Text c="dimmed">Tidak ada data lingkungan desa yang tersedia</Text>
<Box visibleFrom="md">
<Table highlightOnHover miw={0} style={{ tableLayout: 'fixed', width: '100%' }}>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh style={{ width: '25%' }}>Nama Data Lingkungan Desa</TableTh>
<TableTh style={{ width: '35%' }}>Jumlah</TableTh>
<TableTh style={{ width: '15%' }}>Ikon</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
<Center py="xl">
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.4}>
Tidak ada data lingkungan desa yang tersedia
</Text>
</Center>
</Stack>
</Paper>
</Box >
</Box>
);
}
return (
<Box py={10}>
<Paper withBorder shadow="md" radius="md" bg={colors['white-1']} p="lg">
<Box py="md">
<Paper withBorder shadow="md" radius="md" p={{ base: 'md', md: 'lg' }} bg={colors['white-1']}>
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Data Lingkungan Desa</Title>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}>
<Title order={4} lh={1.2}>
Daftar Data Lingkungan Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table highlightOnHover miw={0} style={{ tableLayout: 'fixed', width: '100%' }}>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
@@ -129,21 +195,21 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd ta="center">{index + 1}</TableTd>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed">± {item.jumlah}</Text>
<Text fz="sm" c="dimmed" lh={1.5}>
± {item.jumlah}
</Text>
</TableTd>
<TableTd>
{iconMap[item.icon] && (
<Box title={item.icon}>
{React.createElement(iconMap[item.icon], { size: 22 })}
</Box>
)}
{iconMap[item.icon] && <Box title={item.icon}>{React.createElement(iconMap[item.icon], { size: 22 })}</Box>}
</TableTd>
<TableTd style={{ textAlign: 'center' }}>
<TableTd ta="center">
<Button
size="xs"
radius="md"
@@ -160,7 +226,62 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.map((item, index) => (
<Paper key={item.id} withBorder radius="md" p="sm" bg={colors['white-1']}>
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Data
</Text>
<Text fz="sm" fw={500} lh={1.4} truncate="end">
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Jumlah
</Text>
<Text fz="sm" fw={500} lh={1.4}>
± {item.jumlah}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Ikon
</Text>
{iconMap[item.icon] && <Box title={item.icon}>{React.createElement(iconMap[item.icon], { size: 22 })}</Box>}
</Box>
<Center>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${item.id}`)}
>
Detail
</Button>
</Center>
</Stack>
</Paper>
))}
</Stack>
</Box>
</Paper>
<Center>
<Pagination
value={page}
@@ -178,4 +299,5 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
</Box>
);
}
export default DataLingkunganDesa;
export default DataLingkunganDesa;

View File

@@ -59,43 +59,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
whiteSpace: "nowrap",
transition: "all 0.2s ease",
}}
>
<span style={{
display: "inline-block",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
}}>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</span>
</TabsTab>
))}
</TabsList>
</ScrollArea>
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel key={i} value={tab.value}>

View File

@@ -96,7 +96,7 @@ export default function EditContohKegiatanDesaDarmasaba() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,5 +1,5 @@
'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -25,13 +25,10 @@ function Page() {
return (
<Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Group justify="space-between" align="center" mb={{ base: 'md', md: 'lg' }}>
<Title order={3} fw={600}>
Preview Contoh Kegiatan Di Desa Darmasaba
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button
size="sm"
variant="light"
@@ -46,8 +43,7 @@ function Page() {
>
Edit
</Button>
</GridCol>
</Grid>
</Group>
<Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,7 +1,28 @@
'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layouTabs";
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -81,7 +81,7 @@ export default function EditMateriEdukasiYangDiberikan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -1,5 +1,5 @@
'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -25,11 +25,8 @@ function Page() {
return (
<Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Group align="center" justify='space-between' mb={{ base: 'md', md: 'lg' }}>
<Title order={3} fw={600}>Preview Materi Edukasi Yang Diberikan</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button
size="sm"
variant="light"
@@ -42,8 +39,7 @@ function Page() {
>
Edit
</Button>
</GridCol>
</Grid>
</Group>
<Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -84,7 +84,7 @@ export default function EditTujuanEdukasiLingkungan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button
variant="subtle"

View File

@@ -1,5 +1,5 @@
'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -25,25 +25,21 @@ function Page() {
return (
<Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}>Preview Tujuan Edukasi Lingkungan</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan/edit')
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group align="center" justify='space-between' mb={{ base: 'md', md: 'lg' }}>
<Title order={3} fw={600}>Preview Tujuan Edukasi Lingkungan</Title>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan/edit')
}
>
Edit
</Button>
</Group>
<Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconClipboardList, IconTags } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -59,35 +59,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false}
>
{/* ✅ Scroll horizontal */}
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{/* ✅ Panel dengan gaya kartu */}
{tabs.map((tab, i) => (

View File

@@ -83,7 +83,7 @@ function EditKategoriKegiatan() {
if (loading) return <Text>Loading...</Text>;
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md" align="center">
<Button variant="subtle" p="xs" radius="md" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -39,7 +39,7 @@ function CreateKategoriKegiatan() {
}
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md" align="center">
<Button variant="subtle" p="xs" radius="md" onClick={() => router.back()}>

View File

@@ -18,7 +18,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -28,7 +28,7 @@ import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
function KategoriKegiatan() {
const [search, setSearch] = useState("")
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
@@ -44,10 +44,11 @@ function KategoriKegiatan() {
}
function ListKategoriKegiatan({ search }: { search: string }) {
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter()
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan);
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -55,52 +56,76 @@ function ListKategoriKegiatan({ search }: { search: string }) {
totalPages,
loading,
load,
} = stateKategori.findMany
} = stateKategori.findMany;
const handleHapus = () => {
if (selectedId) {
stateKategori.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
stateKategori.delete.byId(selectedId);
setModalHapus(false);
setSelectedId(null);
}
}
};
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'md', md: 'lg' }}>
<Skeleton height={500} radius="md" />
</Stack>
)
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Kategori Kegiatan</Title>
<Title order={4} lh={1.2}>
Daftar Kategori Kegiatan
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan/create')}
onClick={() =>
router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan/create')
}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '60%' }}>Nama Kategori</TableTh>
<TableTh style={{ width: '20%' }}>Edit</TableTh>
<TableTh style={{ width: '20%' }}>Delete</TableTh>
<TableTh style={{ width: '60%' }}>
<Text fz="sm" fw={600} lh={1.2} ta="left">
Nama Kategori
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.2} ta="center">
Edit
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.2} ta="center">
Delete
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -108,21 +133,27 @@ function ListKategoriKegiatan({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.nama}
</Text>
</TableTd>
<TableTd>
<TableTd ta="center">
<Button
size="xs"
radius="md"
variant="light"
color="green"
leftSection={<IconEdit size={16} />}
onClick={() => router.push(`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`)}
onClick={() =>
router.push(
`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`
)
}
>
Edit
</Button>
</TableTd>
<TableTd>
<TableTd ta="center">
<Button
size="xs"
radius="md"
@@ -130,8 +161,8 @@ function ListKategoriKegiatan({ search }: { search: string }) {
color="red"
leftSection={<IconTrash size={16} />}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
setSelectedId(item.id);
setModalHapus(true);
}}
>
Hapus
@@ -142,8 +173,10 @@ function ListKategoriKegiatan({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text c="dimmed">Tidak ada kategori kegiatan yang cocok</Text>
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada kategori kegiatan yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -151,6 +184,63 @@ function ListKategoriKegiatan({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Kategori
</Text>
<Text fz="sm" fw={500} lh={1.45}>
{item.nama}
</Text>
</Box>
<Group justify="flex-end" mt="xs">
<Button
size="xs"
radius="md"
variant="light"
color="green"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`
)
}
>
Edit
</Button>
<Button
size="xs"
radius="md"
variant="light"
color="red"
leftSection={<IconTrash size={16} />}
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
Hapus
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada kategori kegiatan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
@@ -179,4 +269,4 @@ function ListKategoriKegiatan({ search }: { search: string }) {
);
}
export default KategoriKegiatan;
export default KategoriKegiatan;

View File

@@ -162,7 +162,7 @@ export default function EditKegiatanDesa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -41,7 +41,7 @@ function DetailKegiatanDesa() {
const data = kegiatanDesaState.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Button
variant="subtle"
@@ -55,7 +55,7 @@ function DetailKegiatanDesa() {
{/* Container Detail */}
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"
@@ -85,13 +85,17 @@ function DetailKegiatanDesa() {
{/* Deskripsi Singkat */}
<Box>
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat || '-' }} />
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat || '-' }} />
</Box>
</Box>
{/* Deskripsi Lengkap */}
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} />
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} />
</Box>
</Box>
{/* Kategori */}

View File

@@ -86,7 +86,7 @@ function CreateKegiatanDesa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button

View File

@@ -18,7 +18,7 @@ import {
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -45,6 +45,7 @@ function KegiatanDesa() {
function ListKegiatanDesa({ search }: { search: string }) {
const listState = useProxy(gotongRoyongState.kegiatanDesa);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -55,24 +56,26 @@ function ListKegiatanDesa({ search }: { search: string }) {
} = listState.findMany;
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Kegiatan Desa</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Kegiatan Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -84,14 +87,39 @@ function ListKegiatanDesa({ search }: { search: string }) {
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop: Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '30%' }}>Judul</TableTh>
<TableTh style={{ width: '25%' }}>Kategori</TableTh>
<TableTh style={{ width: '25%' }}>Lokasi</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
<TableTh style={{ width: '30%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Judul
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Kategori
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Lokasi
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Aksi
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -99,17 +127,36 @@ function ListKegiatanDesa({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>
<Text
fz="md"
fw={500}
lh={1.5}
truncate="end"
lineClamp={1}
c="dark"
>
{item.judul}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed">
<Text
fz="sm"
fw={500}
lh={1.5}
c="dark"
>
{item.kategoriKegiatan?.nama || '-'}
</Text>
</TableTd>
<TableTd>
<Text fz="sm">{item.lokasi}</Text>
<Text
fz="sm"
fw={500}
lh={1.5}
c="dark"
>
{item.lokasi}
</Text>
</TableTd>
<TableTd>
<Button
@@ -133,7 +180,7 @@ function ListKegiatanDesa({ search }: { search: string }) {
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text c="dimmed">
<Text fz="sm" c="dimmed" lh={1.4}>
Tidak ada kegiatan desa yang cocok dengan pencarian
</Text>
</Center>
@@ -143,6 +190,64 @@ function ListKegiatanDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile: Card List */}
<Stack hiddenFrom="md" gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder radius="md" p="sm">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Judul
</Text>
<Text fz="sm" fw={500} lh={1.4} c="dark" truncate="end">
{item.judul}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Kategori
</Text>
<Text fz="sm" fw={500} lh={1.4} c="dark">
{item.kategoriKegiatan?.nama || '-'}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Lokasi
</Text>
<Text fz="sm" fw={500} lh={1.4} c="dark">
{item.lokasi}
</Text>
</Box>
<Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(
`/admin/lingkungan/gotong-royong/kegiatan-desa/${item.id}`
)
}
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text fz="sm" c="dimmed" lh={1.4}>
Tidak ada kegiatan desa yang cocok dengan pencarian
</Text>
</Center>
)}
</Stack>
</Paper>
<Center>
<Pagination
@@ -162,4 +267,4 @@ function ListKegiatanDesa({ search }: { search: string }) {
);
}
export default KegiatanDesa;
export default KegiatanDesa;

View File

@@ -1,6 +1,28 @@
'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layoutTabs";
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -59,43 +59,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
whiteSpace: "nowrap",
transition: "all 0.2s ease",
}}
>
<span style={{
display: "inline-block",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
}}>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</span>
</TabsTab>
))}
</TabsList>
</ScrollArea>
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel key={i} value={tab.value}>

View File

@@ -77,7 +77,7 @@ function EditBentukKonservasiBerdasarkanAdat() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,5 +1,5 @@
'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -28,29 +28,25 @@ function Page() {
<Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
{/* Header */}
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600} c="dark">
Preview Bentuk Konservasi Berdasarkan Adat
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
'/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center" mb={{ base: 'md', md: 'lg' }}>
<Title order={3} fw={600} c="dark">
Preview Bentuk Konservasi Berdasarkan Adat
</Title>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
'/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten */}
<Stack gap="md">

View File

@@ -82,7 +82,7 @@ function EditFilosofiTriHitaKarana() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,5 +1,5 @@
'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -25,29 +25,25 @@ function Page() {
return (
<Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}>
Preview Filosofi Tri Hita Karana
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
'/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify="space-between" align="center" mb={{ base: 'md', md: 'lg' }}>
<Title order={3} fw={600}>
Preview Filosofi Tri Hita Karana
</Title>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
'/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana/edit'
)
}
>
Edit
</Button>
</Group>
<Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,8 +1,29 @@
'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layoutTabs"
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -76,7 +76,7 @@ function EditNilaiKonservasiAdat() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,5 +1,5 @@
'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -25,27 +25,23 @@ function Page() {
return (
<Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}>
Preview Nilai Konservasi Adat
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit')
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group align="center" justify='space-between' mb={{ base: 'md', md: 'lg' }}>
<Title order={3} fw={600}>
Preview Nilai Konservasi Adat
</Title>
<Button
size="sm"
variant="light"
color="green"
radius="md"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit')
}
>
Edit
</Button>
</Group>
<Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconRecycle, IconTrash } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -58,35 +58,76 @@ function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.R
keepMounted={false}
>
{/* ✅ Scroll horizontal wrapper */}
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -120,7 +120,7 @@ function EditKeteranganBankSampahTerdekat() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -43,7 +43,7 @@ function DetailKeteranganBankSampahTerdekat() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Box mb="md">
<Button
variant="light"
@@ -57,7 +57,7 @@ function DetailKeteranganBankSampahTerdekat() {
</Box>
<Paper
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
p="xl"
radius="lg"
withBorder

View File

@@ -56,7 +56,7 @@ function CreateKeteranganBankSampahTerdekat() {
}
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -1,7 +1,7 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -28,6 +28,7 @@ function KeteranganBankSampahTerdekat() {
function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -38,42 +39,52 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
} = keteranganState.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Bank Sampah Terdekat</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Bank Sampah Terdekat
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/create')}
>
Tambah Baru
</Button>
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh>Nama Bank Sampah</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Nama Tempat di Maps</TableTh>
<TableTh>Aksi</TableTh>
<TableTh style={{ width: '30%' }}>Nama Bank Sampah</TableTh>
<TableTh style={{ width: '35%' }}>Alamat</TableTh>
<TableTh style={{ width: '25%' }}>Nama Tempat di Maps</TableTh>
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -81,17 +92,17 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500}>{item.name}</Text>
<Text fz="md" fw={500} lh={1.5}>
{item.name}
</Text>
</TableTd>
<TableTd>
<Box w={200}>
<Text lineClamp={2} truncate="end" fz="sm">
{item.alamat || '-'}
</Text>
</Box>
<Text fz="sm" lh={1.5} lineClamp={2}>
{item.alamat || '-'}
</Text>
</TableTd>
<TableTd>
<Text fz="sm">
<Text fz="sm" lh={1.5}>
{item.namaTempatMaps || '-'}
</Text>
</TableTd>
@@ -100,16 +111,19 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
variant="light"
color="blue"
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)}
p="xs"
>
<IconDeviceImac size={20} />
</Button>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))
) : (
<TableTr>
<TableTd colSpan={4} align="center" py="xl">
<Text c="dimmed">Tidak ada data bank sampah terdekat</Text>
<TableTd colSpan={4} ta="center" py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data bank sampah terdekat
</Text>
</TableTd>
</TableTr>
)}
@@ -117,8 +131,60 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
</Table>
</Box>
{totalPages > 1 && (
<Center mt="xl">
{/* Mobile Card */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} p="sm" radius="sm" withBorder>
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Bank Sampah
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Alamat
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.alamat || '-'}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Tempat di Maps
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.namaTempatMaps || '-'}
</Text>
</Box>
<Group justify="flex-end" mt="xs">
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)}
p="xs"
>
<IconDeviceImac size={20} />
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data bank sampah terdekat
</Text>
</Center>
)}
</Stack>
</Box>
<Center mt={{ base: 'lg', md: 'xl' }}>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
@@ -128,10 +194,9 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
withEdges
/>
</Center>
)}
</Paper>
</Box>
);
}
export default KeteranganBankSampahTerdekat;
export default KeteranganBankSampahTerdekat;

Some files were not shown because too many files have changed in this diff Show More