Fix QC Kak Inno 22 Des
Fix QC Kak Ayu 22 Des Fix Tampilan Admin Mobile Device Menu Ekonomi Fix Search -> useDebounced Menu Ekonomi
This commit is contained in:
36
src/app/admin/(dashboard)/_com/modalNonaktif.tsx
Normal file
36
src/app/admin/(dashboard)/_com/modalNonaktif.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// components/modal/ModalKonfirmasiHapus.tsx
|
||||||
|
import colors from "@/con/colors"
|
||||||
|
import { Modal, Text, Button, Flex } from "@mantine/core"
|
||||||
|
|
||||||
|
interface ModalKonfirmasiNonAktifProps {
|
||||||
|
opened: boolean
|
||||||
|
loading?: boolean
|
||||||
|
onClose: () => void
|
||||||
|
onConfirm: () => void
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModalKonfirmasiNonAktif({
|
||||||
|
opened,
|
||||||
|
loading = false,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
text,
|
||||||
|
}: ModalKonfirmasiNonAktifProps) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={<Text fw={"bold"} fz={"xl"}>Konfirmasi Non Aktif</Text>}
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Text mb="md">{text}</Text>
|
||||||
|
<Flex justify="flex-end" gap="sm">
|
||||||
|
<Button style={{color: "white"}} bg={colors['blue-button']} variant="default" onClick={onClose}>Batal</Button>
|
||||||
|
<Button color="red" onClick={onConfirm} loading={loading}>
|
||||||
|
Yakin Non Aktif
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Stack,
|
Stack,
|
||||||
Tabs,
|
Tabs,
|
||||||
@@ -85,6 +86,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
{/* ✅ Scroll horizontal wrapper */}
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
|
<Box visibleFrom='md' pb={10}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
<TabsList
|
<TabsList
|
||||||
p="sm"
|
p="sm"
|
||||||
@@ -95,7 +97,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "nowrap",
|
flexWrap: "nowrap",
|
||||||
gap: "0.5rem",
|
gap: "0.5rem",
|
||||||
paddingInline: "0.5rem",
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
@@ -107,7 +109,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: "0.9rem",
|
fontSize: "0.9rem",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
flexShrink: 0,
|
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -115,6 +117,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</ScrollArea>
|
</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) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ function EditAPBDesa() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function DetailAPBDesa() {
|
|||||||
const data = apbState.findUnique.data;
|
const data = apbState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function CreateAPBDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import {
|
|||||||
TableTh,
|
TableTh,
|
||||||
TableThead,
|
TableThead,
|
||||||
TableTr,
|
TableTr,
|
||||||
Text
|
Text,
|
||||||
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -44,6 +45,7 @@ function APBDesa() {
|
|||||||
function ListAPBDesa({ search }: { search: string }) {
|
function ListAPBDesa({ search }: { search: string }) {
|
||||||
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa);
|
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -61,26 +63,26 @@ function ListAPBDesa({ search }: { search: string }) {
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
<Paper withBorder bg={colors["white-1"]} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors["white-1"]} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Text fw={600} fz="lg">
|
<Title order={4} lh={1.2}>
|
||||||
List APB Desa
|
List APB Desa
|
||||||
</Text>
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -94,45 +96,72 @@ function ListAPBDesa({ search }: { search: string }) {
|
|||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table highlightOnHover>
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: "15%" }}>Tahun</TableTh>
|
<TableTh style={{ width: "15%" }}>
|
||||||
<TableTh style={{ width: "25%" }}>Pembiayaan</TableTh>
|
<Text fz="sm" fw={600} lh={1.4} ta="left">Tahun</Text>
|
||||||
<TableTh style={{ width: "25%" }}>Belanja</TableTh>
|
</TableTh>
|
||||||
<TableTh style={{ width: "25%" }}>Pendapatan</TableTh>
|
<TableTh style={{ width: "25%" }}>
|
||||||
<TableTh style={{ width: "10%" }}>Aksi</TableTh>
|
<Text fz="sm" fw={600} lh={1.4} ta="left">Pembiayaan</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: "25%" }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="left">Belanja</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: "25%" }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="left">Pendapatan</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: "10%" }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="left">Aksi</Text>
|
||||||
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.tahun}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5} ta="left">{item.tahun}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5} ta="left">
|
||||||
{formatRupiah(
|
{formatRupiah(
|
||||||
item.pembiayaan.reduce(
|
item.pembiayaan.reduce(
|
||||||
(sum, val) => sum + Number(val.value),
|
(sum, val) => sum + Number(val.value),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5} ta="left">
|
||||||
{formatRupiah(
|
{formatRupiah(
|
||||||
item.belanja.reduce(
|
item.belanja.reduce(
|
||||||
(sum, val) => sum + Number(val.value),
|
(sum, val) => sum + Number(val.value),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5} ta="left">
|
||||||
{formatRupiah(
|
{formatRupiah(
|
||||||
item.pendapatan.reduce(
|
item.pendapatan.reduce(
|
||||||
(sum, val) => sum + Number(val.value),
|
(sum, val) => sum + Number(val.value),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
@@ -143,9 +172,12 @@ function ListAPBDesa({ search }: { search: string }) {
|
|||||||
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
size="compact-sm"
|
||||||
>
|
>
|
||||||
<IconDeviceImacCog size={20} />
|
<IconDeviceImacCog size={16} />
|
||||||
<Text ml={5}>Detail</Text>
|
<Text ml={5} fz="sm" fw={500} lh={1.4}>
|
||||||
|
Detail
|
||||||
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -154,7 +186,7 @@ function ListAPBDesa({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={5}>
|
<TableTd colSpan={5}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
Tidak ada data APB Desa yang cocok
|
Tidak ada data APB Desa yang cocok
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -164,7 +196,81 @@ function ListAPBDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
<Stack gap="sm">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="md" radius="sm">
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="xs" fw={600} lh={1.4}>Tahun</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>{item.tahun}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="xs" fw={600} lh={1.4}>Pembiayaan</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{formatRupiah(
|
||||||
|
item.pembiayaan.reduce(
|
||||||
|
(sum, val) => sum + Number(val.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="xs" fw={600} lh={1.4}>Belanja</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{formatRupiah(
|
||||||
|
item.belanja.reduce(
|
||||||
|
(sum, val) => sum + Number(val.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="xs" fw={600} lh={1.4}>Pendapatan</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{formatRupiah(
|
||||||
|
item.pendapatan.reduce(
|
||||||
|
(sum, val) => sum + Number(val.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
fullWidth
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={16} />
|
||||||
|
<Text ml={5} fz="sm" fw={500} lh={1.4}>
|
||||||
|
Detail
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data APB Desa yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ function EditBelanja() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ function CreateBelanja() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header dengan back button */}
|
{/* Header dengan back button */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -31,7 +31,7 @@ import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
|||||||
function Belanja() {
|
function Belanja() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack gap="xl">
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title="Belanja"
|
title="Belanja"
|
||||||
placeholder="Cari belanja berdasarkan nama atau nilai..."
|
placeholder="Cari belanja berdasarkan nama atau nilai..."
|
||||||
@@ -40,7 +40,7 @@ function Belanja() {
|
|||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListBelanja search={search} />
|
<ListBelanja search={search} />
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -72,29 +73,35 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
belanjaState.delete.byId(selectedId);
|
belanjaState.delete.byId(selectedId);
|
||||||
setModalHapus(false);
|
setModalHapus(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="md">
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack gap="xl">
|
||||||
|
{/* Desktop Table */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Belanja</Title>
|
<Title
|
||||||
|
order={4}
|
||||||
|
lh={{ base: 1.2, md: 1.1 }}
|
||||||
|
>
|
||||||
|
Daftar Belanja
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -107,14 +114,24 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box visibleFrom="md">
|
||||||
<Table highlightOnHover striped withTableBorder withRowBorders>
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
striped
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh style={{ width: '35%' }}>Nama</TableTh>
|
||||||
<TableTh>Nilai</TableTh>
|
<TableTh style={{ width: '25%' }}>Nilai</TableTh>
|
||||||
<TableTh>Persentase</TableTh>
|
<TableTh style={{ width: '20%' }}>Persentase</TableTh>
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -123,15 +140,19 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fz="md" fw={500} lh={1.45} truncate="end" lineClamp={1}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Text fz="md" lh={1.45}>{formatRupiah(item.value)}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" lh={1.45}>
|
||||||
{totalBelanja > 0
|
{totalBelanja > 0
|
||||||
? ((item.value / totalBelanja) * 100).toFixed(0) + '%'
|
? ((item.value / totalBelanja) * 100).toFixed(0) + '%'
|
||||||
: '0%'}
|
: '0%'}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
@@ -165,18 +186,20 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
))}
|
))}
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={2}>
|
<TableTd colSpan={2}>
|
||||||
<Text fw="bold">Total</Text>
|
<Text fz="md" fw={600} lh={1.45}>Total</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd colSpan={2}>
|
<TableTd colSpan={2}>
|
||||||
<Text fw="bold">{formatRupiah(totalBelanja)}</Text>
|
<Text fz="md" fw={600} lh={1.45}>{formatRupiah(totalBelanja)}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py="xl">
|
||||||
<Text c="dimmed">Tidak ada data belanja yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data belanja yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -186,7 +209,93 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Stack visibleFrom="xs" hiddenFrom="md" 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
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4} truncate="end">
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Nilai
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{formatRupiah(item.value)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Persentase
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{totalBelanja > 0
|
||||||
|
? ((item.value / totalBelanja) * 100).toFixed(0) + '%'
|
||||||
|
: '0%'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" mt="xs">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
disabled={belanjaState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Paper withBorder p="xl" radius="md">
|
||||||
|
<Center>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data belanja yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Paper withBorder p="md" radius="md">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Total
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{formatRupiah(totalBelanja)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
|
{(totalPages > 1 || page > 1) && (
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -196,11 +305,11 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
|
||||||
color="blue"
|
color="blue"
|
||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
@@ -209,7 +318,7 @@ function ListBelanja({ search }: { search: string }) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text="Apakah anda yakin ingin menghapus belanja ini?"
|
text="Apakah anda yakin ingin menghapus belanja ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,30 @@
|
|||||||
|
'use client'
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import LayoutTabs from './_lib/layoutTabs';
|
import LayoutTabs from './_lib/layoutTabs';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { Box } from '@mantine/core';
|
||||||
|
|
||||||
function Layout({ children }: { children: React.ReactNode }) {
|
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 (
|
return (
|
||||||
<LayoutTabs>
|
<LayoutTabs>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ function EditPembiayaan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function CreatePembiayaan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import {
|
|||||||
TableThead,
|
TableThead,
|
||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -48,6 +48,7 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -71,17 +72,17 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
pembiayaanState.delete.byId(selectedId);
|
pembiayaanState.delete.byId(selectedId);
|
||||||
setModalHapus(false);
|
setModalHapus(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="lg">
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -90,10 +91,10 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack gap="xl" py="lg">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Pembiayaan</Title>
|
<Title order={4} lh={1.2}>Daftar Pembiayaan</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -106,13 +107,24 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover striped withTableBorder withRowBorders>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
striped
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh style={{ width: '35%' }}>Nama</TableTh>
|
||||||
<TableTh>Nilai</TableTh>
|
<TableTh style={{ width: '25%' }}>Nilai</TableTh>
|
||||||
<TableTh>Persentase</TableTh>
|
<TableTh style={{ width: '20%' }}>Persentase</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -122,15 +134,19 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>{formatRupiah(item.value)}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
{totalPembiayaan > 0
|
{totalPembiayaan > 0
|
||||||
? ((item.value / totalPembiayaan) * 100).toFixed(0) + '%'
|
? ((item.value / totalPembiayaan) * 100).toFixed(0) + '%'
|
||||||
: '0%'}
|
: '0%'}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
@@ -163,16 +179,20 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
{/* Total Row */}
|
{/* Total Row */}
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={2}>
|
<TableTd colSpan={2}>
|
||||||
<Text fw="bold">Total</Text>
|
<Text fz="md" fw={600} lh={1.5}>Total</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd colSpan={2}>
|
||||||
|
<Text fz="md" fw={600} lh={1.5}>{formatRupiah(totalPembiayaan)}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd colSpan={2}>{formatRupiah(totalPembiayaan)}</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text c="dimmed">Tidak ada data pembiayaan yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data pembiayaan yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -180,6 +200,79 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card View */}
|
||||||
|
<Stack gap="sm" hiddenFrom="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</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4} truncate="end">
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nilai</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{formatRupiah(item.value)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Persentase</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{totalPembiayaan > 0
|
||||||
|
? ((item.value / totalPembiayaan) * 100).toFixed(0) + '%'
|
||||||
|
: '0%'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" mt="xs">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
variant="light"
|
||||||
|
size="xs"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
size="xs"
|
||||||
|
disabled={pembiayaanState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data pembiayaan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Paper withBorder p="md" radius="md">
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Total</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>{formatRupiah(totalPembiayaan)}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
@@ -205,7 +298,7 @@ function ListPembiayaan({ search }: { search: string }) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text="Apakah anda yakin ingin menghapus pembiayaan ini?"
|
text="Apakah anda yakin ingin menghapus pembiayaan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ function EditPendapatan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header with Back Button */}
|
{/* Header with Back Button */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function CreatePendapatan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header dengan tombol back + judul */}
|
{/* Header dengan tombol back + judul */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -49,6 +49,7 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -70,19 +71,19 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
pendapatanState.delete.byId(selectedId);
|
pendapatanState.delete.byId(selectedId);
|
||||||
setModalHapus(false);
|
setModalHapus(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -91,10 +92,12 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
const totalValue = filteredData.reduce((total, item) => total + item.value, 0);
|
const totalValue = filteredData.reduce((total, item) => total + item.value, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Pendapatan</Title>
|
<Title order={2} lh={1.2}>
|
||||||
|
Daftar Pendapatan
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -107,14 +110,30 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover striped withTableBorder withRowBorders>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '40%' }}>Nama</TableTh>
|
<TableTh style={{ width: '40%' }}>
|
||||||
<TableTh style={{ width: '25%' }}>Nilai</TableTh>
|
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
|
||||||
<TableTh style={{ width: '15%' }}>Edit</TableTh>
|
</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Delete</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>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -123,11 +142,13 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fz="md" fw={500} lh={1.5} truncate="end">
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>{formatRupiah(item.value)}</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -135,8 +156,10 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)
|
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)
|
||||||
}
|
}
|
||||||
|
fz="sm"
|
||||||
|
px="xs"
|
||||||
>
|
>
|
||||||
<IconEdit size={18} />
|
<IconEdit size={16} />
|
||||||
<Text ml={5}>Edit</Text>
|
<Text ml={5}>Edit</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -149,8 +172,10 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
setSelectedId(item.id);
|
setSelectedId(item.id);
|
||||||
setModalHapus(true);
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
|
fz="sm"
|
||||||
|
px="xs"
|
||||||
>
|
>
|
||||||
<IconTrash size={18} />
|
<IconTrash size={16} />
|
||||||
<Text ml={5}>Hapus</Text>
|
<Text ml={5}>Hapus</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -159,19 +184,21 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
|
|
||||||
{/* Row total */}
|
{/* Row total */}
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={1}>
|
<TableTd>
|
||||||
<Text fw={'bold'}>Total</Text>
|
<Text fz="md" fw={700} lh={1.5}>Total</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd colSpan={3}>
|
<TableTd colSpan={3}>
|
||||||
<Text fw={'bold'}>{formatRupiah(totalValue)}</Text>
|
<Text fz="md" fw={700} lh={1.5}>{formatRupiah(totalValue)}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py={24}>
|
||||||
<Text color="dimmed">Tidak ada data pendapatan yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data pendapatan yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -179,10 +206,72 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Stack gap="xs" hiddenFrom="md">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder radius="md" p="md">
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nilai</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>{formatRupiah(item.value)}</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" mt="xs">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="xs"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={14} />
|
||||||
|
<Text ml={4}>Edit</Text>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
disabled={pendapatanState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={14} />
|
||||||
|
<Text ml={4}>Hapus</Text>
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={24}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data pendapatan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Paper withBorder radius="md" p="md">
|
||||||
|
<Box>
|
||||||
|
<Text fz="xs" fw={600} lh={1.4}>Total</Text>
|
||||||
|
<Text fz="sm" fw={700} lh={1.4}>{formatRupiah(totalValue)}</Text>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
<Center>
|
{totalPages > 1 && (
|
||||||
|
<Center mt={{ base: 'sm', md: 'md' }} mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
@@ -190,12 +279,12 @@ function ListPendapatan({ search }: { search: string }) {
|
|||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
color="blue"
|
color="blue"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
ScrollArea,
|
||||||
|
Stack,
|
||||||
|
Tabs,
|
||||||
|
TabsList,
|
||||||
|
TabsPanel,
|
||||||
|
TabsTab,
|
||||||
|
Title
|
||||||
|
} from '@mantine/core';
|
||||||
|
import {
|
||||||
|
IconBuildingCommunity,
|
||||||
|
IconHierarchy,
|
||||||
|
IconUsers
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "Pegawai",
|
||||||
|
value: "pegawai",
|
||||||
|
href: "/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai",
|
||||||
|
icon: <IconUsers size={18} stroke={1.8} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Posisi Organisasi",
|
||||||
|
value: "posisiorganisasi",
|
||||||
|
href: "/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/posisi-organisasi",
|
||||||
|
icon: <IconHierarchy size={18} stroke={1.8} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Struktur Organisasi",
|
||||||
|
value: "strukturorganisasi",
|
||||||
|
href: "/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/struktur-organisasi",
|
||||||
|
icon: <IconBuildingCommunity size={18} stroke={1.8} />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentTab = tabs.find((tab) => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(
|
||||||
|
currentTab?.value || tabs[0].value
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabChange = (value: string | null) => {
|
||||||
|
const tab = tabs.find((t) => t.value === value);
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.href);
|
||||||
|
}
|
||||||
|
setActiveTab(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const match = tabs.find((tab) => tab.href === pathname);
|
||||||
|
if (match) {
|
||||||
|
setActiveTab(match.value);
|
||||||
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
|
Struktur Organisasi & SK Pengurus BUMDes
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
color={colors["blue-button"]}
|
||||||
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
{/* ✅ Scroll horizontal biar rapi kalau label panjang */}
|
||||||
|
<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}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Stack >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LayoutTabs;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
'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}
|
||||||
|
</LayoutTabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -143,7 +143,7 @@ export default function EditPegawaiBumDes() {
|
|||||||
if (id && !stateOrganisasi.edit.id) stateOrganisasi.edit.id = id;
|
if (id && !stateOrganisasi.edit.id) stateOrganisasi.edit.id = id;
|
||||||
|
|
||||||
const success = await stateOrganisasi.edit.submit();
|
const success = await stateOrganisasi.edit.submit();
|
||||||
if (success) router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai');
|
if (success) router.push('/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating pegawai:', error);
|
console.error('Error updating pegawai:', error);
|
||||||
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui data pegawai');
|
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui data pegawai');
|
||||||
@@ -153,12 +153,12 @@ export default function EditPegawaiBumDes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
<Title order={4} ml="sm" c="dark">Edit Data Pegawai PPID</Title>
|
<Title order={4} ml="sm" c="dark">Edit Data Pegawai BumDes</Title>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import { ModalKonfirmasiNonAktif } from '@/app/admin/(dashboard)/_com/modalNonaktif';
|
||||||
import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
@@ -28,7 +29,7 @@ function DetailPegawai() {
|
|||||||
statePegawai.delete.byId(selectedId);
|
statePegawai.delete.byId(selectedId);
|
||||||
setModalHapus(false);
|
setModalHapus(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai");
|
router.push("/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ function DetailPegawai() {
|
|||||||
statePegawai.nonActive.byId(selectedId);
|
statePegawai.nonActive.byId(selectedId);
|
||||||
setModalNonActive(false);
|
setModalNonActive(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai");
|
router.push("/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ function DetailPegawai() {
|
|||||||
const data = statePegawai.findUnique.data;
|
const data = statePegawai.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
@@ -60,7 +61,7 @@ function DetailPegawai() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w={{ base: "100%", md: "70%" }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -68,7 +69,7 @@ function DetailPegawai() {
|
|||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
Detail Pegawai PPID
|
Detail Pegawai BumDes
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
@@ -165,7 +166,7 @@ function DetailPegawai() {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
onClick={() => router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/${data.id}/edit`)}
|
onClick={() => router.push(`/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai/${data.id}/edit`)}
|
||||||
variant="light"
|
variant="light"
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
@@ -187,7 +188,7 @@ function DetailPegawai() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal NonActive */}
|
{/* Modal NonActive */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiNonAktif
|
||||||
opened={modalNonActive}
|
opened={modalNonActive}
|
||||||
onClose={() => setModalNonActive(false)}
|
onClose={() => setModalNonActive(false)}
|
||||||
onConfirm={handleNonActive}
|
onConfirm={handleNonActive}
|
||||||
@@ -72,7 +72,7 @@ function CreatePegawaiBumDes() {
|
|||||||
// Reset form dan redirect
|
// Reset form dan redirect
|
||||||
resetForm();
|
resetForm();
|
||||||
toast.success("Data pegawai berhasil ditambahkan");
|
toast.success("Data pegawai berhasil ditambahkan");
|
||||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai");
|
router.push("/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating pegawai:", error);
|
console.error("Error creating pegawai:", error);
|
||||||
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
||||||
@@ -82,13 +82,13 @@ function CreatePegawaiBumDes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
<Title order={4} ml="sm" c="dark">
|
<Title order={4} ml="sm" c="dark">
|
||||||
Tambah Pegawai BUMDesa
|
Tambah Pegawai BUMDes
|
||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title } from '@mantine/core';
|
||||||
|
import { IconCheck, IconDeviceImacCog, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import stateStrukturBumDes from '../../../_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
|
||||||
|
function PegawaiBumDes() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Pegawai BUMDes'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListPegawaiBumdes search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListPegawaiBumdes({ search }: { search: string }) {
|
||||||
|
const stateOrganisasi = useProxy(stateStrukturBumDes.pegawai);
|
||||||
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateOrganisasi.findMany;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load(page, 10, debouncedSearch);
|
||||||
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
|
// Handle loading state
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="md">
|
||||||
|
<Skeleton height={300} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box py="md">
|
||||||
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
|
<Group justify="space-between" mb="md">
|
||||||
|
<Title order={2} visibleFrom="md">Daftar Pegawai BUMDes</Title>
|
||||||
|
<Title order={3} hiddenFrom="md">Daftar Pegawai BUMDes</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
<Center py="xl">
|
||||||
|
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} ta="center">
|
||||||
|
Tidak ada data pegawai yang ditemukan
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedData = [...filteredData].sort((a, b) => {
|
||||||
|
if (a.isActive === b.isActive) {
|
||||||
|
return a.namaLengkap.localeCompare(b.namaLengkap);
|
||||||
|
}
|
||||||
|
return Number(b.isActive) - Number(a.isActive);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="md">
|
||||||
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
|
<Group justify="space-between" mb="md">
|
||||||
|
<Title order={2} visibleFrom="md">Daftar Pegawai BUMDes</Title>
|
||||||
|
<Title order={3} hiddenFrom="md">Daftar Pegawai BUMDes</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Desktop: Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '35%' }}>Nama Lengkap</TableTh>
|
||||||
|
<TableTh style={{ width: '30%' }}>Posisi</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>Status</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{sortedData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text fw={500} fz="md" lh={1.45} truncate="end">
|
||||||
|
{item.namaLengkap}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Badge variant="light" color="blue" fz="sm" lh={1.4}>
|
||||||
|
{item.posisi?.nama || 'Belum diatur'}
|
||||||
|
</Badge>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Badge color={item.isActive ? "green" : "red"} fz="sm" lh={1.4}>
|
||||||
|
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||||
|
</Badge>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai/${item.id}`)}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile: Card List */}
|
||||||
|
<Stack gap="sm" hiddenFrom="md">
|
||||||
|
{sortedData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="md" radius="md">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama Lengkap</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.namaLengkap}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Posisi</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.posisi?.nama || 'Belum diatur'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Status</Text>
|
||||||
|
<Group gap="xs">
|
||||||
|
{item.isActive ? (
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon color="green" variant="light" size="sm">
|
||||||
|
<IconCheck size={14} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text fz="sm" fw={500} c="green">Aktif</Text>
|
||||||
|
</Group>
|
||||||
|
) : (
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon color="red" variant="light" size="sm">
|
||||||
|
<IconX size={14} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text fz="sm" fw={500} c="red">Tidak Aktif</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai/${item.id}`)}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
withEdges
|
||||||
|
withControls
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PegawaiBumDes;
|
||||||
@@ -95,7 +95,7 @@ function EditPosisiOrganisasiBumDes() {
|
|||||||
const success = await stateOrganisasi.edit.update();
|
const success = await stateOrganisasi.edit.update();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi');
|
router.push('/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/posisi-organisasi');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating posisi organisasi:', err);
|
console.error('Error updating posisi organisasi:', err);
|
||||||
@@ -106,7 +106,7 @@ function EditPosisiOrganisasiBumDes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
@@ -36,7 +36,7 @@ function CreatePosisiOrganisasiBumDes() {
|
|||||||
|
|
||||||
await stateOrganisasi.create.submit();
|
await stateOrganisasi.create.submit();
|
||||||
toast.success('Posisi organisasi berhasil ditambahkan');
|
toast.success('Posisi organisasi berhasil ditambahkan');
|
||||||
router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi');
|
router.push('/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/posisi-organisasi');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Gagal menambahkan posisi organisasi');
|
toast.error('Gagal menambahkan posisi organisasi');
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
@@ -46,7 +46,7 @@ function CreatePosisiOrganisasiBumDes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
'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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import stateStrukturBumDes from '../../../_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
||||||
|
|
||||||
|
function PosisiOrganisasiBumDes() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Posisi Organisasi BUMDes'
|
||||||
|
placeholder='Cari posisi organisasi...'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListPosisiOrganisasiBumDes search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListPosisiOrganisasiBumDes({ search }: { search: string }) {
|
||||||
|
const stateOrganisasi = useProxy(stateStrukturBumDes.posisiOrganisasi);
|
||||||
|
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,
|
||||||
|
} = stateOrganisasi.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, debouncedSearch);
|
||||||
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
|
const handleHapus = async () => {
|
||||||
|
if (selectedId) {
|
||||||
|
await stateOrganisasi.delete.byId(selectedId);
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||||
|
<Skeleton height={600} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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={{ base: 1.2, md: 1.1 }}
|
||||||
|
>
|
||||||
|
Daftar Posisi Organisasi BumDes
|
||||||
|
</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/posisi-organisasi/create'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '25%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="left">
|
||||||
|
Nama Posisi
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '25%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="left">
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="left">
|
||||||
|
Hierarki
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="center">
|
||||||
|
Edit
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="center">
|
||||||
|
Hapus
|
||||||
|
</Text>
|
||||||
|
</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.nama}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" fw={500} lh={1.45} c="dimmed" lineClamp={2}>
|
||||||
|
{item.deskripsi || '-'}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.45}>{item.hierarki || '-'}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd ta="center">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="xs"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/posisi-organisasi/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd ta="center">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={{ base: 'sm', md: 'md' }}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data posisi organisasi yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</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 Posisi
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.deskripsi || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Hierarki
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.hierarki || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" gap="xs" mt="xs">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="xs"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/posisi-organisasi/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py="sm">
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data posisi organisasi yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus posisi organisasi BumDes ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PosisiOrganisasiBumDes;
|
||||||
@@ -118,7 +118,7 @@ export default function EditDemografiPekerjaan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function CreateDemografiPekerjaan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -106,38 +106,52 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>List Demografi Pekerjaan</Title>
|
<Title
|
||||||
|
order={4}
|
||||||
|
lh={{ base: 1.2, md: 1.15 }}
|
||||||
|
>
|
||||||
|
List Demografi Pekerjaan
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/ekonomi/demografi-pekerjaan/create')}
|
onClick={() => router.push('/admin/ekonomi/demografi-pekerjaan/create')}
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
>
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ minWidth: 200 }}>Pekerjaan</TableTh>
|
<TableTh style={{ width: '40%' }}>Pekerjaan</TableTh>
|
||||||
<TableTh style={{ minWidth: 200 }}>Laki - Laki</TableTh>
|
<TableTh style={{ width: '20%' }}>Laki - Laki</TableTh>
|
||||||
<TableTh style={{ minWidth: 200 }}>Perempuan</TableTh>
|
<TableTh style={{ width: '20%' }}>Perempuan</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh style={{ width: '10%' }}>Edit</TableTh>
|
||||||
<TableTh>Hapus</TableTh>
|
<TableTh style={{ width: '10%' }}>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ minWidth: 200 }}>{item.pekerjaan}</TableTd>
|
<TableTd>{item.pekerjaan}</TableTd>
|
||||||
<TableTd style={{ minWidth: 200 }}>{item.lakiLaki}</TableTd>
|
<TableTd>{item.lakiLaki}</TableTd>
|
||||||
<TableTd style={{ minWidth: 200 }}>{item.perempuan}</TableTd>
|
<TableTd>{item.perempuan}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -145,8 +159,11 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)
|
router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)
|
||||||
}
|
}
|
||||||
|
fz="sm"
|
||||||
|
px="xs"
|
||||||
|
py="xs"
|
||||||
>
|
>
|
||||||
<IconEdit size={18} />
|
<IconEdit size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
@@ -158,17 +175,22 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
setSelectedId(item.id);
|
setSelectedId(item.id);
|
||||||
setModalHapus(true);
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
|
fz="sm"
|
||||||
|
px="xs"
|
||||||
|
py="xs"
|
||||||
>
|
>
|
||||||
<IconTrash size={18} />
|
<IconTrash size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={5}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">Tidak ada data demografi pekerjaan yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data demografi pekerjaan yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -176,6 +198,78 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
<Stack gap="xs">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Pekerjaan
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.pekerjaan}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Laki - Laki
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.lakiLaki}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Perempuan
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.perempuan}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" gap="xs">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)
|
||||||
|
}
|
||||||
|
fz="xs"
|
||||||
|
px="xs"
|
||||||
|
py="xs"
|
||||||
|
>
|
||||||
|
<IconEdit size={14} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
disabled={stateDemografi.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
fz="xs"
|
||||||
|
px="xs"
|
||||||
|
py="xs"
|
||||||
|
>
|
||||||
|
<IconTrash size={14} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data demografi pekerjaan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
@@ -195,10 +289,13 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
<Box mt={30} style={{ width: '100%', minHeight: 400 }}>
|
<Box mt={{ base: 'lg', md: 'xl' }}>
|
||||||
<Paper bg={colors['white-1']} p="md" radius="md" withBorder>
|
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="md" withBorder>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="xs">
|
||||||
<Title pb={10} order={4}>
|
<Title
|
||||||
|
order={4}
|
||||||
|
lh={{ base: 1.2, md: 1.15 }}
|
||||||
|
>
|
||||||
Grafik Demografi Pekerjaan
|
Grafik Demografi Pekerjaan
|
||||||
</Title>
|
</Title>
|
||||||
{mounted && chartData.length > 0 ? (
|
{mounted && chartData.length > 0 ? (
|
||||||
@@ -213,17 +310,23 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.4}>
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Box py={10}>
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
<Group justify='center'>
|
<Group justify="center" gap='md'>
|
||||||
<Flex align="center" gap={10}>
|
<Flex align="center" gap={8}>
|
||||||
<Box bg="#5082EE" w={20} h={20} />
|
<Box bg="#5082EE" w={16} h={16} />
|
||||||
<Text>Laki - Laki</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||||
|
Laki - Laki
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex align="center" gap={10}>
|
<Flex align="center" gap={8}>
|
||||||
<Box bg="#6EDF9C" w={20} h={20} />
|
<Box bg="#6EDF9C" w={16} h={16} />
|
||||||
<Text>Perempuan</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||||
|
Perempuan
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ function EditJumlahPendudukMiskin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function CreateJumlahPendudukMiskin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -16,9 +16,9 @@ import {
|
|||||||
TableThead,
|
TableThead,
|
||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -26,12 +26,10 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||||
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||||
|
|
||||||
// ✅ BarChart Mantine
|
|
||||||
import { BarChart } from '@mantine/charts';
|
import { BarChart } from '@mantine/charts';
|
||||||
|
|
||||||
function JumlahPendudukMiskin() {
|
function JumlahPendudukMiskin() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
@@ -54,16 +52,15 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const { data, page, loading, load, totalPages } = stateJPM.findMany;
|
const { data, page, loading, load, totalPages } = stateJPM.findMany;
|
||||||
|
|
||||||
// Load data awal
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
// Update chart data
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stateJPM.findMany.data) {
|
if (stateJPM.findMany.data) {
|
||||||
setChartData(
|
setChartData(
|
||||||
@@ -88,18 +85,20 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={{ base: 'sm', md: 'md' }} gap='lg'>
|
||||||
{/* Tabel */}
|
{/* Main Table/Card Section */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>Daftar Jumlah Penduduk Miskin</Title>
|
<Title order={4} lh={1.2}>
|
||||||
|
Daftar Jumlah Penduduk Miskin
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -112,22 +111,54 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Tahun</TableTh>
|
<TableTh style={{ width: '25%' }}>
|
||||||
<TableTh style={{ width: '35%' }}>Jumlah Penduduk Miskin</TableTh>
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
Tahun
|
||||||
<TableTh style={{ width: '20%' }}>Delete</TableTh>
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '35%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Jumlah Penduduk Miskin
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>
|
||||||
|
<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>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.year}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.year}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.totalPoorPopulation}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -158,7 +189,9 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">Tidak ada data yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -166,6 +199,64 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card View */}
|
||||||
|
<Stack hiddenFrom="md" gap="xs">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Tahun
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.year}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Jumlah Penduduk Miskin
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.totalPoorPopulation}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" mt="xs">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="xs"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
disabled={stateJPM.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
@@ -185,9 +276,9 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Bar Chart */}
|
{/* Bar Chart */}
|
||||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
<Paper bg={colors['white-1']} p={{ base: 'sm', md: 'md' }} mt="lg" withBorder radius="md">
|
||||||
<Stack>
|
<Stack gap="xs">
|
||||||
<Title order={4} mb="sm">
|
<Title order={4} lh={1.2} mb="sm">
|
||||||
Grafik Jumlah Penduduk Miskin
|
Grafik Jumlah Penduduk Miskin
|
||||||
</Title>
|
</Title>
|
||||||
{mounted && chartData.length > 0 ? (
|
{mounted && chartData.length > 0 ? (
|
||||||
@@ -198,14 +289,14 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
value: item.totalPoorPopulation,
|
value: item.totalPoorPopulation,
|
||||||
}))}
|
}))}
|
||||||
dataKey="name"
|
dataKey="name"
|
||||||
series={[
|
series={[{ name: 'value', color: colors['blue-button'] }]}
|
||||||
{ name: 'value', color: colors['blue-button'] },
|
|
||||||
]}
|
|
||||||
withTooltip
|
withTooltip
|
||||||
valueFormatter={(v) => `${v.toLocaleString()} jiwa`}
|
valueFormatter={(v) => `${v.toLocaleString()} jiwa`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -217,7 +308,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text="Apakah anda yakin ingin menghapus data ini?"
|
text="Apakah anda yakin ingin menghapus data ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Stack,
|
Stack,
|
||||||
Tabs,
|
Tabs,
|
||||||
@@ -61,6 +62,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
|
<Box visibleFrom='md' pb={10}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
<TabsList
|
<TabsList
|
||||||
p="sm"
|
p="sm"
|
||||||
@@ -71,7 +73,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "nowrap",
|
flexWrap: "nowrap",
|
||||||
gap: "0.5rem",
|
gap: "0.5rem",
|
||||||
paddingInline: "0.5rem",
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
@@ -83,7 +85,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: "0.9rem",
|
fontSize: "0.9rem",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
flexShrink: 0,
|
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -91,6 +93,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</ScrollArea>
|
</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) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -1,6 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import LayoutTabs from "./_lib/layoutTabs";
|
import LayoutTabs from "./_lib/layoutTabs";
|
||||||
|
import { Box } from "@mantine/core";
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
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 (
|
return (
|
||||||
<LayoutTabs>
|
<LayoutTabs>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function EditGrafikBerdasarkanPendidikan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function CreateGrafikBerdasarkanPendidikan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Flex,
|
Flex,
|
||||||
|
Group,
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -51,6 +52,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -64,8 +66,8 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stategrafik.findMany.data) {
|
if (stategrafik.findMany.data) {
|
||||||
@@ -103,18 +105,20 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'sm', md: 'lg' }}>
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={{ base: 'sm', md: 'lg' }} gap='md'>
|
||||||
{/* Table Data */}
|
{/* Section: List Table */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Flex justify="space-between" align="center" mb="md">
|
<Flex visibleFrom='md' justify="space-between" align="center" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>List Pengangguran Berdasarkan Pendidikan</Title>
|
<Title order={4} lh={1.2}>
|
||||||
|
List Pengangguran Berdasarkan Pendidikan
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -129,17 +133,43 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Group hiddenFrom='md' align="center" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Table highlightOnHover>
|
<Title order={4} lh={1.2}>
|
||||||
|
List Pengangguran Berdasarkan Pendidikan
|
||||||
|
</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>SD</TableTh>
|
<TableTh style={{ width: '16%' }}>SD</TableTh>
|
||||||
<TableTh>SMP</TableTh>
|
<TableTh style={{ width: '16%' }}>SMP</TableTh>
|
||||||
<TableTh>SMA</TableTh>
|
<TableTh style={{ width: '16%' }}>SMA</TableTh>
|
||||||
<TableTh>D3</TableTh>
|
<TableTh style={{ width: '16%' }}>D3</TableTh>
|
||||||
<TableTh>S1</TableTh>
|
<TableTh style={{ width: '16%' }}>S1</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh style={{ width: '10%' }}>Edit</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh style={{ width: '10%' }}>Delete</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -147,7 +177,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={7}>
|
<TableTd colSpan={7}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
Belum ada data grafik responden
|
Belum ada data grafik responden
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -156,11 +186,31 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
) : (
|
) : (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.SD}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.SMP}</TableTd>
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
<TableTd>{item.SMA}</TableTd>
|
{item.SD}
|
||||||
<TableTd>{item.D3}</TableTd>
|
</Text>
|
||||||
<TableTd>{item.S1}</TableTd>
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.SMP}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.SMA}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.D3}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.S1}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
@@ -193,6 +243,92 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Center py="sm">
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Belum ada data grafik responden
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<Stack gap="sm">
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="sm" radius="md">
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
SD
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.SD}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
SMP
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.SMP}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
SMA
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.SMA}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
D3
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.D3}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
S1
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.S1}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Flex gap="xs" mt="xs">
|
||||||
|
<Button
|
||||||
|
size="compact-sm"
|
||||||
|
color="green"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="compact-sm"
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
disabled={stategrafik.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
@@ -211,10 +347,10 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Donut Chart */}
|
{/* Section: Donut Chart */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md" mt="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Stack>
|
<Stack gap="md">
|
||||||
<Title order={3} pb={10}>
|
<Title order={4} lh={1.2}>
|
||||||
Grafik Pengangguran Berdasarkan Pendidikan
|
Grafik Pengangguran Berdasarkan Pendidikan
|
||||||
</Title>
|
</Title>
|
||||||
<Center>
|
<Center>
|
||||||
@@ -228,7 +364,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
thickness={40}
|
thickness={40}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text color="dimmed">
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||||
Belum ada data untuk ditampilkan dalam grafik
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -243,7 +379,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text="Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan pendidikan ini?"
|
text="Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan pendidikan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function CreateGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Flex,
|
Flex,
|
||||||
|
Group,
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -51,6 +52,7 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -64,8 +66,8 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stategrafik.findMany.data) {
|
if (stategrafik.findMany.data) {
|
||||||
@@ -87,19 +89,21 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
{/* Table */}
|
{/* Table - Desktop */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md" mb="lg">
|
||||||
<Stack>
|
<Stack gap="md">
|
||||||
<Flex justify="space-between" align="center" mb="md">
|
<Flex justify="space-between" align="center" visibleFrom='md'>
|
||||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
<Title order={4} lh={1.2}>
|
||||||
|
List Pengangguran Berdasarkan Usia Kerja
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -112,26 +116,58 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Group align="center" hiddenFrom='md'>
|
||||||
<Table highlightOnHover>
|
<Title order={4} lh={1.2}>
|
||||||
|
List Pengangguran Berdasarkan Usia Kerja
|
||||||
|
</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Usia 18-25</TableTh>
|
<TableTh style={{ width: '20%' }}>Usia 18-25</TableTh>
|
||||||
<TableTh>Usia 26-35</TableTh>
|
<TableTh style={{ width: '20%' }}>Usia 26-35</TableTh>
|
||||||
<TableTh>Usia 36-45</TableTh>
|
<TableTh style={{ width: '20%' }}>Usia 36-45</TableTh>
|
||||||
<TableTh>Usia 46+</TableTh>
|
<TableTh style={{ width: '20%' }}>Usia 46+</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh style={{ width: '10%' }}>Edit</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh style={{ width: '10%' }}>Delete</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.usia18_25}</TableTd>
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
<TableTd>{item.usia26_35}</TableTd>
|
{item.usia18_25}
|
||||||
<TableTd>{item.usia36_45}</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.usia46_keatas}</TableTd>
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.usia26_35}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.usia36_45}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.usia46_keatas}
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
@@ -160,7 +196,9 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={6}>
|
<TableTd colSpan={6}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">Belum ada data grafik responden</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Belum ada data grafik responden
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -168,6 +206,80 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card View */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
<Stack gap="xs">
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Usia 18-25
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.usia18_25}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Usia 26-35
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.usia26_35}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Usia 36-45
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.usia36_45}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Usia 46+
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.usia46_keatas}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Flex gap="xs" mt="xs">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
color="red"
|
||||||
|
disabled={stategrafik.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Belum ada data grafik responden
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -189,8 +301,8 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
|
|
||||||
{/* Donut Chart */}
|
{/* Donut Chart */}
|
||||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||||
<Stack>
|
<Stack gap="md">
|
||||||
<Title order={3} pb={10}>
|
<Title order={4} lh={1.2}>
|
||||||
Grafik Pengangguran Berdasarkan Usia Kerja
|
Grafik Pengangguran Berdasarkan Usia Kerja
|
||||||
</Title>
|
</Title>
|
||||||
{donutData.length > 0 ? (
|
{donutData.length > 0 ? (
|
||||||
@@ -205,7 +317,9 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ function EditDetailDataPengangguran() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function DetailJumlahPengangguran() {
|
|||||||
const data = stateDetail.findUnique.data;
|
const data = stateDetail.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Tombol Kembali */}
|
{/* Tombol Kembali */}
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@@ -54,7 +54,7 @@ function DetailJumlahPengangguran() {
|
|||||||
{/* Paper Detail */}
|
{/* Paper Detail */}
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w={{ base: "100%", md: "70%" }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function CreateJumlahPengangguran() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
Table, TableTbody, TableTd, TableTh, TableThead, TableTr,
|
Table, TableTbody, TableTd, TableTh, TableThead, TableTr,
|
||||||
Text, Title
|
Text, Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -20,7 +20,7 @@ function DetailDataPengangguran() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Detail Data Pengangguran'
|
title='Detail Data Pengangguran'
|
||||||
placeholder='Cari bulan atau tahun...'
|
placeholder='Cari bulan atau tahun...'
|
||||||
@@ -29,7 +29,7 @@ function DetailDataPengangguran() {
|
|||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListDetailDataPengangguran search={search} />
|
<ListDetailDataPengangguran search={search} />
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -49,8 +50,8 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -68,23 +69,25 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py="md">
|
<Stack py={{ base: 'md', md: 'lg' }} gap="lg">
|
||||||
<Skeleton h={500} radius="md" />
|
<Skeleton h={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack py="md" gap="lg">
|
<Stack py={{ base: 'md', md: 'lg' }} gap="lg">
|
||||||
{/* Table Section */}
|
{/* Table / Card Section */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>Daftar Detail Data Pengangguran</Title>
|
<Title order={4} lh={1.2}>
|
||||||
|
Daftar Detail Data Pengangguran
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -95,23 +98,45 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover striped withTableBorder withRowBorders>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Bulan</TableTh>
|
<TableTh style={{ width: '30%' }}>Bulan</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Terdidik</TableTh>
|
<TableTh style={{ width: '25%' }}>Terdidik</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Tidak Terdidik</TableTh>
|
<TableTh style={{ width: '25%' }}>Tidak Terdidik</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.month} {item.year}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.educatedUnemployment}</TableTd>
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
{item.month} {item.year}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.educatedUnemployment}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" fw={500} lh={1.5}>
|
||||||
|
{item.uneducatedUnemployment}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -119,7 +144,9 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}
|
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}
|
||||||
>
|
>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
<Text ml={5}>Detail</Text>
|
<Text ml={5} fz="sm" fw={500} lh={1.4}>
|
||||||
|
Detail
|
||||||
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -128,7 +155,9 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text c="dimmed">Tidak ada data yang cocok</Text>
|
<Text c="dimmed" fz="sm" fw={500} lh={1.4}>
|
||||||
|
Tidak ada data yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -136,7 +165,66 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card View */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
<Stack gap="xs">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Bulan
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.month} {item.year}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Terdidik
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.educatedUnemployment}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Tidak Terdidik
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.uneducatedUnemployment}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}
|
||||||
|
mt="xs"
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={18} />
|
||||||
|
<Text ml={5} fz="sm" fw={500} lh={1.4}>
|
||||||
|
Detail
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" fw={500} lh={1.4}>
|
||||||
|
Tidak ada data yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
{totalPages > 1 && (
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -151,10 +239,11 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Chart Section */}
|
{/* Chart Section */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Title order={4} mb="md">
|
<Title order={4} lh={1.2} mb={{ base: 'sm', md: 'md' }}>
|
||||||
Data Pengangguran Terdidik & Tidak Terdidik
|
Data Pengangguran Terdidik & Tidak Terdidik
|
||||||
</Title>
|
</Title>
|
||||||
{mounted && chartData.length > 0 ? (
|
{mounted && chartData.length > 0 ? (
|
||||||
@@ -170,7 +259,9 @@ function ListDetailDataPengangguran({ search }: { search: string }) {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} fw={500} lh={1.5}>
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function EditLowonganKerja() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function DetailLowonganKerjaLokal() {
|
|||||||
const data = lowonganState.findUnique.data;
|
const data = lowonganState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
@@ -99,13 +99,17 @@ function DetailLowonganKerjaLokal() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Box pl={8}>
|
||||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} />
|
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Kualifikasi</Text>
|
<Text fz="lg" fw="bold">Kualifikasi</Text>
|
||||||
|
<Box pl={8}>
|
||||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.kualifikasi || '-' }} />
|
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.kualifikasi || '-' }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Group gap="sm" mt="sm">
|
<Group gap="sm" mt="sm">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function CreateLowonganKerja() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -46,27 +46,28 @@ function LowonganKerjaLokal() {
|
|||||||
function ListLowonganKerjaLokal({ search }: { search: string }) {
|
function ListLowonganKerjaLokal({ search }: { search: string }) {
|
||||||
const stateLowongan = useProxy(lowonganKerjaState);
|
const stateLowongan = useProxy(lowonganKerjaState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = stateLowongan.findMany;
|
const { data, page, totalPages, loading, load } = stateLowongan.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="md">
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="md">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="lg">
|
||||||
<Title order={4}>Daftar Lowongan Kerja Lokal</Title>
|
<Title order={4}>Daftar Lowongan Kerja Lokal</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
@@ -80,36 +81,52 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Pekerjaan</TableTh>
|
<TableTh style={{ width: '25%' }}>
|
||||||
<TableTh style={{ width: '25%' }}>Nama Perusahaan</TableTh>
|
<Text fz="sm" fw={600} lh={1.2} c="black">Pekerjaan</Text>
|
||||||
<TableTh style={{ width: '20%' }}>Lokasi</TableTh>
|
</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '25%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.2} c="black">Nama Perusahaan</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.2} c="black">Lokasi</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.2} c="black">Aksi</Text>
|
||||||
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
|
||||||
{item.posisi}
|
{item.posisi}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Text fz="sm" fw={500} lh={1.5} truncate="end">
|
||||||
{item.namaPerusahaan}
|
{item.namaPerusahaan}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Text fz="sm" fw={500} lh={1.5} truncate="end">
|
||||||
{item.lokasi}
|
{item.lokasi}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -118,9 +135,11 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`
|
`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fullWidth
|
||||||
|
radius="sm"
|
||||||
>
|
>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
<Text ml={5}>Detail</Text>
|
<Text ml="xs">Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -128,8 +147,8 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py="xl">
|
||||||
<Text color="dimmed">
|
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||||
Tidak ada data lowongan kerja yang cocok
|
Tidak ada data lowongan kerja yang cocok
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -139,6 +158,57 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card List */}
|
||||||
|
<Stack gap="sm" hiddenFrom="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}>Pekerjaan</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.5}>
|
||||||
|
{item.posisi}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama Perusahaan</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.5}>
|
||||||
|
{item.namaPerusahaan}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Lokasi</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.5}>
|
||||||
|
{item.lokasi}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
radius="sm"
|
||||||
|
mt="xs"
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml="xs">Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py="xl">
|
||||||
|
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||||
|
Tidak ada data lowongan kerja yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Stack,
|
Stack,
|
||||||
Tabs,
|
Tabs,
|
||||||
@@ -68,6 +69,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
{/* ✅ Scroll horizontal wrapper */}
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
|
<Box visibleFrom='md' pb={10}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
<TabsList
|
<TabsList
|
||||||
p="sm"
|
p="sm"
|
||||||
@@ -78,7 +80,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "nowrap",
|
flexWrap: "nowrap",
|
||||||
gap: "0.5rem",
|
gap: "0.5rem",
|
||||||
paddingInline: "0.5rem",
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
@@ -90,7 +92,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: "0.9rem",
|
fontSize: "0.9rem",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
flexShrink: 0,
|
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -98,6 +100,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</ScrollArea>
|
</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) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function EditKategoriProduk() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header dengan tombol back */}
|
{/* Header dengan tombol back */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function CreateKategoriProduk() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header dengan tombol kembali */}
|
{/* Header dengan tombol kembali */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,7 +1,24 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 {
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -10,9 +27,8 @@ import HeaderSearch from '../../../_com/header';
|
|||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
|
import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
|
||||||
|
|
||||||
|
|
||||||
function KategoriProduk() {
|
function KategoriProduk() {
|
||||||
const [search2, setSearch2] = useState("")
|
const [search2, setSearch2] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
@@ -28,63 +44,82 @@ function KategoriProduk() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListKategoriProduk({ search2 }: { search2: string }) {
|
function ListKategoriProduk({ search2 }: { search2: string }) {
|
||||||
const statePasar = useProxy(pasarDesaState.kategoriProduk)
|
const statePasar = useProxy(pasarDesaState.kategoriProduk);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search2, 1000);
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = statePasar.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = statePasar.findMany
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search2)
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search2])
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
statePasar.delete.byId(selectedId)
|
statePasar.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="md">
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="md">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Kategori Produk</Title>
|
<Title order={4} lh={1.2}>
|
||||||
|
Daftar Kategori Produk
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/ekonomi/pasar-desa/kategori-produk/create')}
|
onClick={() =>
|
||||||
|
router.push('/admin/ekonomi/pasar-desa/kategori-produk/create')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '60%' }}>Nama Kategori</TableTh>
|
<TableTh style={{ width: '60%' }}>
|
||||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
<TableTh style={{ width: '20%' }}>Delete</TableTh>
|
Nama Kategori
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="center">
|
||||||
|
Edit
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4} ta="center">
|
||||||
|
Delete
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -92,38 +127,48 @@ function ListKategoriProduk({ search2 }: { search2: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fz="md" fw={500} lh={1.5} truncate="end">
|
||||||
{item.nama}
|
{item.nama}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Center>
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push(`/admin/ekonomi/pasar-desa/kategori-produk/${item.id}`)}
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/pasar-desa/kategori-produk/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<IconEdit size={18} />
|
<IconEdit size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Center>
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedId(item.id)
|
setSelectedId(item.id);
|
||||||
setModalHapus(true)
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX size={18} />
|
<IconX size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={3}>
|
<TableTd colSpan={3}>
|
||||||
<Center py={20}>
|
<Center py="xl">
|
||||||
<Text color="dimmed">Tidak ada data kategori produk yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data kategori produk yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -131,14 +176,69 @@ function ListKategoriProduk({ search2 }: { search2: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Card */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
<Stack gap="sm">
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<Paper
|
||||||
|
key={item.id}
|
||||||
|
withBorder
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
bg={colors['white-1']}
|
||||||
|
>
|
||||||
|
<Box mb="xs">
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Nama Kategori
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="flex-end" mt="md">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/pasar-desa/kategori-produk/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconX size={18} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Center py="xl">
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data kategori produk yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10)
|
load(newPage, 10);
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
@@ -156,7 +256,7 @@ function ListKategoriProduk({ search2 }: { search2: string }) {
|
|||||||
text='Apakah anda yakin ingin menghapus kategori produk ini?'
|
text='Apakah anda yakin ingin menghapus kategori produk ini?'
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KategoriProduk;
|
export default KategoriProduk;
|
||||||
@@ -1,9 +1,29 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import LayoutTabs from "./_lib/layoutTabs"
|
import LayoutTabs from "./_lib/layoutTabs"
|
||||||
|
import { Box } from "@mantine/core";
|
||||||
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
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 (
|
return (
|
||||||
<LayoutTabs>
|
<LayoutTabs>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ function EditPasarDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function DetailPasarDesa() {
|
|||||||
const data = statePasar.pasarDesa.findUnique.data;
|
const data = statePasar.pasarDesa.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
@@ -52,7 +52,7 @@ function DetailPasarDesa() {
|
|||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w={{ base: "100%", md: "70%" }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default function CreatePasarDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
{/* Header dengan tombol kembali */}
|
{/* Header dengan tombol kembali */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -45,28 +45,29 @@ function PasarDesa() {
|
|||||||
function ListPasarDesa({ search }: { search: string }) {
|
function ListPasarDesa({ search }: { search: string }) {
|
||||||
const statePasar = useProxy(pasarDesaState.pasarDesa);
|
const statePasar = useProxy(pasarDesaState.pasarDesa);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = statePasar.findMany;
|
const { data, page, totalPages, loading, load } = statePasar.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="lg">
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="lg">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Produk Pasar Desa</Title>
|
<Title order={4} lh={1.2}>Daftar Produk Pasar Desa</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -79,15 +80,23 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
{/* Desktop Table */}
|
||||||
<Table highlightOnHover>
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Nama Produk</TableTh>
|
<TableTh style={{ width: '25%' }}><Text fz="sm" fw={600} lh={1.4}>Nama Produk</Text></TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Harga Produk</TableTh>
|
<TableTh style={{ width: '20%' }}><Text fz="sm" fw={600} lh={1.4}>Harga Produk</Text></TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Rating</TableTh>
|
<TableTh style={{ width: '15%' }}><Text fz="sm" fw={600} lh={1.4}>Rating</Text></TableTh>
|
||||||
<TableTh style={{ width: '25%' }}>Alamat Usaha</TableTh>
|
<TableTh style={{ width: '25%' }}><Text fz="sm" fw={600} lh={1.4}>Alamat Usaha</Text></TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '15%' }}><Text fz="sm" fw={600} lh={1.4}>Aksi</Text></TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -95,18 +104,18 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
|
||||||
{item.nama}
|
{item.nama}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text>Rp.{item.harga}</Text>
|
<Text fz="md" lh={1.5}>Rp.{item.harga}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text>{item.rating || '-'}</Text>
|
<Text fz="md" lh={1.5}>{item.rating || '-'}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Text fz="sm" lh={1.5} c="dimmed">
|
||||||
{item.alamatUsaha || '-'}
|
{item.alamatUsaha || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -121,7 +130,7 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
<Text ml={5}>Detail</Text>
|
<Text ml={5} fz="sm" fw={500} lh={1.4}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -129,8 +138,8 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={5}>
|
<TableTd colSpan={5}>
|
||||||
<Center py={20}>
|
<Center py={32}>
|
||||||
<Text color="dimmed">
|
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||||
Tidak ada produk pasar desa yang cocok
|
Tidak ada produk pasar desa yang cocok
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -140,6 +149,57 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Stack hiddenFrom="md" 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 Produk</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>{item.nama}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Harga Produk</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>Rp.{item.harga}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Rating</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>{item.rating || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Alamat Usaha</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4} c="dimmed">
|
||||||
|
{item.alamatUsaha || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/pasar-desa/produk-pasar-desa/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml={5} fz="sm" fw={500} lh={1.4}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={32}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||||
|
Tidak ada produk pasar desa yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ function EditProgramKemiskinan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function DetailProgramKemiskinan() {
|
|||||||
const data = programState.findUnique.data;
|
const data = programState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Tombol Kembali */}
|
{/* Tombol Kembali */}
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@@ -64,7 +64,7 @@ function DetailProgramKemiskinan() {
|
|||||||
{/* Card utama */}
|
{/* Card utama */}
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w={{ base: "100%", md: "70%" }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function CreateProgramKemiskinan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Header dengan tombol back */}
|
{/* Header dengan tombol back */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,18 +1,43 @@
|
|||||||
'use client'
|
'use client'
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import colors from '@/con/colors';
|
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 {
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { CartesianGrid, Legend, Line, LineChart, Tooltip as RechartTooltip, XAxis, YAxis } from 'recharts';
|
import {
|
||||||
|
CartesianGrid,
|
||||||
|
Legend,
|
||||||
|
Line,
|
||||||
|
LineChart,
|
||||||
|
Tooltip as RechartTooltip,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
} from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import programKemiskinanState from '../../_state/ekonomi/program-kemiskinan';
|
import programKemiskinanState from '../../_state/ekonomi/program-kemiskinan';
|
||||||
|
|
||||||
function ProgramKemiskinan() {
|
function ProgramKemiskinan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
@@ -32,21 +57,22 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [lineChart, setLineChart] = useState<any[]>([]);
|
const [lineChart, setLineChart] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = programState.findMany;
|
const { data, page, totalPages, loading, load } = programState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const chartData = data
|
const chartData = data
|
||||||
.filter(item => item.statistik)
|
.filter((item) => item.statistik)
|
||||||
.map(item => ({
|
.map((item) => ({
|
||||||
tahun: item.statistik?.tahun,
|
tahun: item.statistik?.tahun,
|
||||||
jumlah: Number(item.statistik?.jumlah)
|
jumlah: Number(item.statistik?.jumlah),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => (a.tahun || 0) - (b.tahun || 0));
|
.sort((a, b) => (a.tahun || 0) - (b.tahun || 0));
|
||||||
|
|
||||||
@@ -58,49 +84,90 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'md', md: 'lg' }}>
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={{ base: 'md', md: 'lg' }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
{/* Daftar Program Kemiskinan */}
|
||||||
<Group justify="space-between" mb="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Title order={4}>Daftar Program Kemiskinan</Title>
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/ekonomi/program-kemiskinan/create')}>
|
<Title order={4} lh={1.2}>
|
||||||
|
Daftar Program Kemiskinan
|
||||||
|
</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/program-kemiskinan/create')}
|
||||||
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
|
||||||
<Table highlightOnHover>
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '30%' }}>Judul Program</TableTh>
|
<TableTh style={{ width: '30%' }}>
|
||||||
<TableTh style={{ width: '40%' }}>Deskripsi Singkat</TableTh>
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
<TableTh style={{ width: '20%' }}>Jumlah Masyarakat Miskin</TableTh>
|
Judul Program
|
||||||
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '40%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Deskripsi Singkat
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Jumlah Masyarakat Miskin
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
|
<TableTh style={{ width: '10%' }}>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Aksi
|
||||||
|
</Text>
|
||||||
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map(item => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate lineClamp={1}>{item.nama}</Text>
|
<Text fw={500} fz="md" lh={1.45} truncate lineClamp={1}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz="sm" truncate lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text fz="sm" lh={1.45} truncate lineClamp={2} c="dark.9" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="md" lh={1.45} fw={500}>
|
||||||
|
{item.statistik?.jumlah || '-'}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.statistik?.jumlah || '-'}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
onClick={() => router.push(`/admin/ekonomi/program-kemiskinan/${item.id}`)}
|
onClick={() => router.push(`/admin/ekonomi/program-kemiskinan/${item.id}`)}
|
||||||
|
fz="sm"
|
||||||
|
lh={1.4}
|
||||||
>
|
>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={18} />
|
||||||
<Text ml={5}>Detail</Text>
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -110,7 +177,9 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">Tidak ada data program kemiskinan yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data program kemiskinan yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -118,6 +187,61 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Box hiddenFrom="md">
|
||||||
|
<Stack gap="md">
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<Paper key={item.id} withBorder p="md" radius="sm">
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Judul Program
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Deskripsi Singkat
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4} c="dark.9" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Jumlah Masyarakat Miskin
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.statistik?.jumlah || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/program-kemiskinan/${item.id}`)}
|
||||||
|
fz="sm"
|
||||||
|
lh={1.4}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={18} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data program kemiskinan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
@@ -137,25 +261,45 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
<Box py={10}>
|
<Box pt={{ base: 'md', md: 'lg' }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
<Title order={4} lh={1.2} pb={{ base: 'sm', md: 'md' }}>
|
||||||
|
Grafik Berdasarkan Responden
|
||||||
|
</Title>
|
||||||
{mounted && lineChart.length > 0 ? (
|
{mounted && lineChart.length > 0 ? (
|
||||||
<Box style={{ width: '100%', overflowX: 'auto' }}>
|
<Box>
|
||||||
<LineChart width={820} height={300} data={lineChart}>
|
<Box
|
||||||
|
component="div"
|
||||||
|
miw={{ base: 320, md: 820 }}
|
||||||
|
mx="auto"
|
||||||
|
style={{ overflowX: 'auto' }}
|
||||||
|
>
|
||||||
|
<LineChart
|
||||||
|
width={Math.max(320, lineChart.length * 60)}
|
||||||
|
height={300}
|
||||||
|
data={lineChart}
|
||||||
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="tahun" />
|
<XAxis dataKey="tahun" />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<RechartTooltip
|
<RechartTooltip
|
||||||
formatter={(value: any, name: string) => [`${value} orang`, name]}
|
formatter={(value: any) => [`${value} orang`, 'Jumlah']}
|
||||||
labelFormatter={(label: any) => `Tahun: ${label}`}
|
labelFormatter={(label: any) => `Tahun: ${label}`}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Line type="monotone" dataKey="jumlah" name="Jumlah per Tahun" stroke={colors['blue-button']} />
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey="jumlah"
|
||||||
|
name="Jumlah per Tahun"
|
||||||
|
stroke={colors['blue-button']}
|
||||||
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ function EditSektorUnggulanDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function DetailSektorUnggulanDesa() {
|
|||||||
const data = stateGrafik.findUnique.data;
|
const data = stateGrafik.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Tombol kembali */}
|
{/* Tombol kembali */}
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@@ -61,7 +61,7 @@ function DetailSektorUnggulanDesa() {
|
|||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: '100%', md: '60%' }}
|
w={{ base: '100%', md: '70%' }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -81,8 +81,10 @@ function DetailSektorUnggulanDesa() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Box pl={8}>
|
||||||
<Text ta={"justify"} fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.description || '-' }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
|
<Text ta={"justify"} fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.description || '-' }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Jumlah</Text>
|
<Text fz="lg" fw="bold">Jumlah</Text>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function CreateSektorUnggulanDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
{/* Header dengan back button */}
|
{/* Header dengan back button */}
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -58,6 +58,8 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
load,
|
load,
|
||||||
} = state.findMany;
|
} = state.findMany;
|
||||||
|
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.findMany.data) {
|
if (state.findMany.data) {
|
||||||
setChartData(
|
setChartData(
|
||||||
@@ -72,14 +74,14 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
}, [state.findMany.data]);
|
}, [state.findMany.data]);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, debouncedSearch)
|
||||||
}, [page, search])
|
}, [page, debouncedSearch])
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="md">
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -87,24 +89,37 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md" py="md">
|
<Stack gap="md" py="md">
|
||||||
{/* List Table */}
|
{/* List Section */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>List Sektor Unggulan Desa</Title>
|
<Title order={4} lh={1.2}>
|
||||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/ekonomi/sektor-unggulan-desa/create')}>
|
List Sektor Unggulan Desa
|
||||||
|
</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/sektor-unggulan-desa/create')}
|
||||||
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
{loading ? (
|
|
||||||
<Skeleton height={300} radius="md" />
|
{/* Desktop Table */}
|
||||||
) : (
|
<Box visibleFrom="md">
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Table
|
||||||
<Table highlightOnHover>
|
highlightOnHover
|
||||||
|
miw={0}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '30%' }}>Nama Sektor</TableTh>
|
<TableTh style={{ width: '35%' }}>Nama Sektor</TableTh>
|
||||||
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
|
<TableTh style={{ width: '45%' }}>Deskripsi</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Detail</TableTh>
|
<TableTh style={{ width: '20%' }}>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -112,24 +127,23 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="md" fw={500} lh={1.45} truncate="end">
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="sm" lineClamp={3} fw={500} lh={1.4} c={item.description ? 'inherit' : 'dimmed'} dangerouslySetInnerHTML={{ __html: item.description || '-' }} />
|
||||||
<Text truncate="end" fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '-' }} />
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}
|
onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}
|
||||||
|
radius="md"
|
||||||
|
fz="sm"
|
||||||
|
px="sm"
|
||||||
>
|
>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={18} />
|
||||||
<Text ml={6}>Detail</Text>
|
<Text ml={6}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -138,8 +152,10 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={3}>
|
<TableTd colSpan={3}>
|
||||||
<Center py={20}>
|
<Center py={24}>
|
||||||
<Text color="dimmed">Tidak ada data sektor unggulan yang cocok</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data sektor unggulan yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -147,9 +163,57 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Cards */}
|
||||||
|
<Stack gap="sm" hiddenFrom="md">
|
||||||
|
{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 Sektor
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<Box pl={8}>
|
||||||
|
<Text fz="sm" lineClamp={3} fw={500} lh={1.4} c={item.description ? 'inherit' : 'dimmed'} dangerouslySetInnerHTML={{ __html: item.description || '-' }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}
|
||||||
|
fullWidth
|
||||||
|
mt="xs"
|
||||||
|
radius="md"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={18} />
|
||||||
|
<Text ml={6}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Center py={24}>
|
||||||
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Tidak ada data sektor unggulan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -158,22 +222,20 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
color="blue"
|
color="blue"
|
||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart Section */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Title order={4} pb="sm">
|
<Title order={4} lh={1.2} pb={{ base: 'sm', md: 'md' }}>
|
||||||
Grafik Sektor Unggulan Desa
|
Grafik Sektor Unggulan Desa
|
||||||
</Title>
|
</Title>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton height={350} radius="md" />
|
<Skeleton height={350} radius="md" />
|
||||||
) : chartData.length > 0 ? (
|
) : chartData.length > 0 ? (
|
||||||
<Box style={{ width: '100%', height: 400 }}>
|
<Box style={{ width: '100%', height: 350 }}>
|
||||||
<ResponsiveContainer>
|
<ResponsiveContainer>
|
||||||
<BarChart data={chartData}>
|
<BarChart data={chartData}>
|
||||||
<XAxis dataKey="name" />
|
<XAxis dataKey="name" />
|
||||||
@@ -186,7 +248,9 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Center py={50}>
|
<Center py={50}>
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
/* 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 {
|
|
||||||
IconBuildingCommunity,
|
|
||||||
IconHierarchy,
|
|
||||||
IconUsers
|
|
||||||
} from '@tabler/icons-react';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
label: "Pegawai",
|
|
||||||
value: "pegawai",
|
|
||||||
href: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai",
|
|
||||||
icon: <IconUsers size={18} stroke={1.8} />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Posisi Organisasi",
|
|
||||||
value: "posisiorganisasi",
|
|
||||||
href: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi",
|
|
||||||
icon: <IconHierarchy size={18} stroke={1.8} />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Struktur Organisasi",
|
|
||||||
value: "strukturorganisasi",
|
|
||||||
href: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/struktur-organisasi",
|
|
||||||
icon: <IconBuildingCommunity size={18} stroke={1.8} />
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const currentTab = tabs.find((tab) => tab.href === pathname);
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(
|
|
||||||
currentTab?.value || tabs[0].value
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
|
||||||
const tab = tabs.find((t) => t.value === value);
|
|
||||||
if (tab) {
|
|
||||||
router.push(tab.href);
|
|
||||||
}
|
|
||||||
setActiveTab(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const match = tabs.find((tab) => tab.href === pathname);
|
|
||||||
if (match) {
|
|
||||||
setActiveTab(match.value);
|
|
||||||
}
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack gap="lg">
|
|
||||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
|
||||||
Struktur Organisasi & SK Pengurus BUMDesa
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Tabs
|
|
||||||
color={colors["blue-button"]}
|
|
||||||
variant="pills"
|
|
||||||
value={activeTab}
|
|
||||||
onChange={handleTabChange}
|
|
||||||
radius="lg"
|
|
||||||
keepMounted={false}
|
|
||||||
>
|
|
||||||
{/* ✅ Scroll horizontal biar rapi kalau label panjang */}
|
|
||||||
<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",
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tab.label}
|
|
||||||
</TabsTab>
|
|
||||||
))}
|
|
||||||
</TabsList>
|
|
||||||
</ScrollArea>
|
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
|
||||||
<TabsPanel
|
|
||||||
key={i}
|
|
||||||
value={tab.value}
|
|
||||||
style={{
|
|
||||||
padding: "1.5rem",
|
|
||||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
|
||||||
borderRadius: "1rem",
|
|
||||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</TabsPanel>
|
|
||||||
))}
|
|
||||||
</Tabs>
|
|
||||||
</Stack >
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LayoutTabs;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import LayoutTabs from "./_lib/layoutTabs"
|
|
||||||
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<LayoutTabs>
|
|
||||||
{children}
|
|
||||||
</LayoutTabs>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title } from '@mantine/core';
|
|
||||||
import { IconCheck, IconDeviceImacCog, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import HeaderSearch from '../../../_com/header';
|
|
||||||
import stateStrukturBumDes from '../../../_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
|
||||||
|
|
||||||
|
|
||||||
function PegawaiBumDes() {
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<HeaderSearch
|
|
||||||
title='Pegawai BUMDesa'
|
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<ListPegawaiBumdes search={search} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListPegawaiBumdes({ search }: { search: string }) {
|
|
||||||
const stateOrganisasi = useProxy(stateStrukturBumDes.pegawai);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = stateOrganisasi.findMany;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
load(page, 10, search);
|
|
||||||
}, [page, search]);
|
|
||||||
|
|
||||||
const filteredData = data || []
|
|
||||||
|
|
||||||
// Handle loading state
|
|
||||||
if (loading || !data) {
|
|
||||||
return (
|
|
||||||
<Stack py={10}>
|
|
||||||
<Skeleton height={300} />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
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 Pegawai BUMDesa</Title>
|
|
||||||
<Button
|
|
||||||
leftSection={<IconPlus size={18} />}
|
|
||||||
color="blue"
|
|
||||||
variant="light"
|
|
||||||
onClick={() => router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create')}
|
|
||||||
>
|
|
||||||
Tambah Baru
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
<Center py="xl">
|
|
||||||
<Text c="dimmed">Tidak ada data pegawai yang ditemukan</Text>
|
|
||||||
</Center>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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 Pegawai BUMDesa</Title>
|
|
||||||
<Button
|
|
||||||
leftSection={<IconPlus size={18} />}
|
|
||||||
color="blue"
|
|
||||||
variant="light"
|
|
||||||
onClick={() => router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create')}
|
|
||||||
>
|
|
||||||
Tambah Baru
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table highlightOnHover>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh style={{ width: '25%' }}>Nama Lengkap</TableTh>
|
|
||||||
<TableTh style={{ width: '20%' }}>Posisi</TableTh>
|
|
||||||
<TableTh style={{ width: '10%' }}>Status</TableTh>
|
|
||||||
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{(() => {
|
|
||||||
console.log('Rendering table with items:', stateOrganisasi.findMany.data);
|
|
||||||
return null;
|
|
||||||
})()}
|
|
||||||
{([...filteredData]
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (a.isActive === b.isActive) {
|
|
||||||
return a.namaLengkap.localeCompare(b.namaLengkap); // kalau status sama, urut nama
|
|
||||||
}
|
|
||||||
return Number(b.isActive) - Number(a.isActive); // aktif duluan
|
|
||||||
}) // Aktif di atas
|
|
||||||
).map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>
|
|
||||||
<Box w={150}>
|
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
|
||||||
{item.namaLengkap}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Box w={150}>
|
|
||||||
<Badge variant="light" color="blue">
|
|
||||||
{item.posisi?.nama || 'Belum diatur'}
|
|
||||||
</Badge>
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Group gap="xs" wrap="nowrap">
|
|
||||||
<Box visibleFrom="sm">
|
|
||||||
<Badge color={item.isActive ? "green" : "red"}>
|
|
||||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
|
||||||
</Badge>
|
|
||||||
</Box>
|
|
||||||
<Box hiddenFrom="sm">
|
|
||||||
{item.isActive ? (
|
|
||||||
<ThemeIcon color="green" variant="light" size="sm">
|
|
||||||
<IconCheck size={16} />
|
|
||||||
</ThemeIcon>
|
|
||||||
) : (
|
|
||||||
<ThemeIcon color="red" variant="light" size="sm">
|
|
||||||
<IconX size={16} />
|
|
||||||
</ThemeIcon>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Group>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
radius="md"
|
|
||||||
variant="light"
|
|
||||||
color="blue"
|
|
||||||
leftSection={<IconDeviceImacCog size={16} />}
|
|
||||||
onClick={() => router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/${item.id}`)}
|
|
||||||
>
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
|
||||||
))}
|
|
||||||
</TableTbody>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
<Center mt="lg">
|
|
||||||
<Pagination
|
|
||||||
value={page}
|
|
||||||
onChange={(newPage) => {
|
|
||||||
load(newPage, 10);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}}
|
|
||||||
total={totalPages}
|
|
||||||
withEdges
|
|
||||||
withControls
|
|
||||||
radius="md"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PegawaiBumDes;
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
'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 { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import HeaderSearch from '../../../_com/header';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
import stateStrukturBumDes from '../../../_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
|
||||||
|
|
||||||
|
|
||||||
function PosisiOrganisasiBumDes() {
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<HeaderSearch
|
|
||||||
title='Posisi Organisasi BUMDes'
|
|
||||||
placeholder='Cari posisi organisasi...'
|
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<ListPosisiOrganisasiBumDes search={search} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListPosisiOrganisasiBumDes({ search }: { search: string }) {
|
|
||||||
const stateOrganisasi = useProxy(stateStrukturBumDes.posisiOrganisasi)
|
|
||||||
const router = useRouter();
|
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = stateOrganisasi.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
load(page, 10, search);
|
|
||||||
}, [page, search]);
|
|
||||||
|
|
||||||
const handleHapus = async () => {
|
|
||||||
if (selectedId) {
|
|
||||||
await stateOrganisasi.delete.byId(selectedId);
|
|
||||||
setModalHapus(false)
|
|
||||||
setSelectedId(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredData = data || []
|
|
||||||
|
|
||||||
if (loading || !data) {
|
|
||||||
return (
|
|
||||||
<Stack py={10}>
|
|
||||||
<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 Posisi Organisasi BumDes</Title>
|
|
||||||
<Button
|
|
||||||
leftSection={<IconPlus size={18} />}
|
|
||||||
color="blue"
|
|
||||||
variant="light"
|
|
||||||
onClick={() => router.push('/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi/create')}
|
|
||||||
>
|
|
||||||
Tambah Baru
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
|
||||||
<Table highlightOnHover>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh style={{ width: '20%' }}>Nama Posisi</TableTh>
|
|
||||||
<TableTh style={{ width: '20%' }}>Deskripsi</TableTh>
|
|
||||||
<TableTh style={{ width: '20%' }}>Hierarki</TableTh>
|
|
||||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
|
||||||
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.length > 0 ? (
|
|
||||||
filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd style={{ width: '20%' }}>
|
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd style={{ width: '20%' }}>
|
|
||||||
<Box w={200}>
|
|
||||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd style={{ width: '20%' }}>
|
|
||||||
<Text>{item.hierarki || '-'}</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd style={{ width: '20%' }}>
|
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
color="green"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi/${item.id}`)}
|
|
||||||
>
|
|
||||||
<IconEdit size={18} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd style={{ width: '20%' }}>
|
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
color="red"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id);
|
|
||||||
setModalHapus(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconTrash size={18} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableTr>
|
|
||||||
<TableTd colSpan={4}>
|
|
||||||
<Center py={20}>
|
|
||||||
<Text color="dimmed">Tidak ada data posisi organisasi yang cocok</Text>
|
|
||||||
</Center>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
|
||||||
)}
|
|
||||||
</TableTbody>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
<Center>
|
|
||||||
<Pagination
|
|
||||||
value={page}
|
|
||||||
onChange={(newPage) => {
|
|
||||||
load(newPage, 10, search);
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
}}
|
|
||||||
total={totalPages}
|
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
color="blue"
|
|
||||||
radius="md"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
{/* Modal Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus posisi organisasi BumDes ini?"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PosisiOrganisasiBumDes;
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import { ModalKonfirmasiNonAktif } from '@/app/admin/(dashboard)/_com/modalNonaktif';
|
||||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
@@ -186,7 +187,7 @@ function DetailPegawai() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal NonActive */}
|
{/* Modal NonActive */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiNonAktif
|
||||||
opened={modalNonActive}
|
opened={modalNonActive}
|
||||||
onClose={() => setModalNonActive(false)}
|
onClose={() => setModalNonActive(false)}
|
||||||
onConfirm={handleNonActive}
|
onConfirm={handleNonActive}
|
||||||
|
|||||||
@@ -223,8 +223,8 @@ export const devBar = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Ekonomi_3",
|
id: "Ekonomi_3",
|
||||||
name: "Struktur Organisasi Dan Sk Pengurus Bumdesa",
|
name: "Struktur Organisasi Dan Sk Pengurus BumDes",
|
||||||
path: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai"
|
path: "/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Ekonomi_4",
|
id: "Ekonomi_4",
|
||||||
@@ -628,7 +628,7 @@ export const navBar = [
|
|||||||
{
|
{
|
||||||
id: "Ekonomi_3",
|
id: "Ekonomi_3",
|
||||||
name: "Struktur Organisasi Dan Sk Pengurus Bumdesa",
|
name: "Struktur Organisasi Dan Sk Pengurus Bumdesa",
|
||||||
path: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai"
|
path: "/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Ekonomi_4",
|
id: "Ekonomi_4",
|
||||||
@@ -990,7 +990,7 @@ export const role1 = [
|
|||||||
{
|
{
|
||||||
id: "Ekonomi_3",
|
id: "Ekonomi_3",
|
||||||
name: "Struktur Organisasi Dan Sk Pengurus Bumdesa",
|
name: "Struktur Organisasi Dan Sk Pengurus Bumdesa",
|
||||||
path: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai"
|
path: "/admin/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/pegawai"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Ekonomi_4",
|
id: "Ekonomi_4",
|
||||||
|
|||||||
@@ -509,7 +509,7 @@ function NodeCard({ node, router }: any) {
|
|||||||
mt={8}
|
mt={8}
|
||||||
radius="md"
|
radius="md"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(`/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/${node.data.id}`)
|
router.push(`/darmasaba/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes/${node.data.id}`)
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
height: 32,
|
height: 32,
|
||||||
@@ -47,7 +47,7 @@ const getDetailUrl = (item: { type?: string; id: string | number;[key: string]:
|
|||||||
tipsKeamanan: () => '/darmasaba/keamanan/tips-keamanan',
|
tipsKeamanan: () => '/darmasaba/keamanan/tips-keamanan',
|
||||||
pasarDesa: () => '/darmasaba/ekonomi/pasar-desa',
|
pasarDesa: () => '/darmasaba/ekonomi/pasar-desa',
|
||||||
lowonganKerjaLokal: () => '/darmasaba/ekonomi/lowongan-kerja-lokal',
|
lowonganKerjaLokal: () => '/darmasaba/ekonomi/lowongan-kerja-lokal',
|
||||||
strukturOrganisasi: () => '/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa',
|
strukturOrganisasi: () => '/darmasaba/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes',
|
||||||
jumlahPendudukUsiaKerjaYangMenganggurUsia: () => '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
jumlahPendudukUsiaKerjaYangMenganggurUsia: () => '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
||||||
jumlahPendudukUsiaKerjaYangMenganggurPendidikan: () => '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
jumlahPendudukUsiaKerjaYangMenganggurPendidikan: () => '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
||||||
jumlahPendudukMiskin: () => '/darmasaba/ekonomi/jumlah-penduduk-miskin',
|
jumlahPendudukMiskin: () => '/darmasaba/ekonomi/jumlah-penduduk-miskin',
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const navbarListMenu = [
|
|||||||
{
|
{
|
||||||
id: "5.3",
|
id: "5.3",
|
||||||
name: "Struktur Organisasi dan SK Pengurus BUMDesa",
|
name: "Struktur Organisasi dan SK Pengurus BUMDesa",
|
||||||
href: "/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa"
|
href: "/darmasaba/ekonomi/Struktur-Organisasi-Dan-Sk-Pengurus-BumDes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5.4",
|
id: "5.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user