Fix QC Kak Ayu 15 Des
Fix QC Kak Inno 15 Des Fix UI User Font Size, Font Weight, Line Height Fix UI Admin Font Size, Font Weight, Line Height & UI Mobile
This commit is contained in:
@@ -123,7 +123,7 @@ export default function EditKolaborasiInovasi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: "sm", md: "lg" }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors["blue-button"]} size={24} />
|
||||
|
||||
@@ -42,7 +42,7 @@ function DetailSDGSDesa() {
|
||||
const data = sdgsState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -54,7 +54,7 @@ function DetailSDGSDesa() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -65,7 +65,7 @@ function CreateSDGsDesa() {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -204,7 +204,7 @@ function EditAPBDes() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
@@ -215,7 +215,7 @@ function EditAPBDes() {
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '100%' }}
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -65,7 +65,7 @@ function DetailAPBDes() {
|
||||
});
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -77,7 +77,7 @@ function DetailAPBDes() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '100%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -155,7 +155,7 @@ function CreateAPBDes() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -56,71 +56,85 @@ function ListAPBDes({ search }: { search: string }) {
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py={{ base: 'md', md: 'lg' }}>
|
||||
<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 APBDes</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/apbdes/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={2} size="lg" lh={1.2}>
|
||||
Daftar APBDes
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/apbdes/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>APBDes</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Tahun</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Dokumen</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text fw={500} lineClamp={1}>
|
||||
APBDes {item.tahun}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text fw={500}>{item.tahun || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
{item.file?.link ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={item.file.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="light"
|
||||
leftSection={<IconFile size={16} />}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
>
|
||||
Lihat Dokumen
|
||||
</Button>
|
||||
) : (
|
||||
<Text c="dimmed" fz="sm">
|
||||
Tidak ada dokumen
|
||||
<Box>
|
||||
<Table highlightOnHover miw={0}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
APBDes
|
||||
</TableTh>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
Tahun
|
||||
</TableTh>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
Dokumen
|
||||
</TableTh>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
Aksi
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fz="md" fw={500} lh={1.5} lineClamp={1}>
|
||||
APBDes {item.tahun}
|
||||
</Text>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box w={100}>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="md" fw={500} lh={1.5}>
|
||||
{item.tahun || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{item.file?.link ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={item.file.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="light"
|
||||
leftSection={<IconFile size={16} />}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
fz="sm"
|
||||
>
|
||||
Lihat Dokumen
|
||||
</Button>
|
||||
) : (
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
Tidak ada dokumen
|
||||
</Text>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
@@ -128,29 +142,126 @@ function ListAPBDes({ search }: { search: string }) {
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={14} />}
|
||||
onClick={() => router.push(`/admin/landing-page/apbdes/${item.id}`)}
|
||||
fullWidth
|
||||
fz="sm"
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Box>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py="lg">
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
Tidak ada data APBDes yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data APBDes yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Center mt="md">
|
||||
{/* Mobile Cards */}
|
||||
<Box hiddenFrom="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={2} size="lg" lh={1.2}>
|
||||
Daftar APBDes
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/apbdes/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Stack gap="md">
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<Paper
|
||||
key={item.id}
|
||||
withBorder
|
||||
bg={colors['white-1']}
|
||||
p="md"
|
||||
shadow="sm"
|
||||
radius="md"
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
APBDes {item.tahun}
|
||||
</Text>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||
Tahun
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.tahun || '-'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||
Dokumen
|
||||
</Text>
|
||||
{item.file?.link ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={item.file.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="light"
|
||||
leftSection={<IconFile size={14} />}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
fz="xs"
|
||||
lh={1.4}
|
||||
>
|
||||
Lihat
|
||||
</Button>
|
||||
) : (
|
||||
<Text fz="xs" c="dimmed" lh={1.4}>
|
||||
Tidak ada
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={14} />}
|
||||
onClick={() => router.push(`/admin/landing-page/apbdes/${item.id}`)}
|
||||
mt="sm"
|
||||
fz="xs"
|
||||
lh={1.4}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
) : (
|
||||
<Paper withBorder bg={colors['white-1']} p="md" radius="md">
|
||||
<Center py="lg">
|
||||
<Text c="dimmed" fz="xs" lh={1.4}>
|
||||
Tidak ada data APBDes yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
|
||||
@@ -69,7 +69,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<Box visibleFrom='md'>
|
||||
<Box visibleFrom='md' pb={10}>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
@@ -102,7 +102,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
|
||||
<Box hiddenFrom='md'>
|
||||
<Box hiddenFrom='md' pb={10}>
|
||||
<ScrollArea
|
||||
type="auto"
|
||||
offsetScrollbars={false}
|
||||
|
||||
@@ -56,6 +56,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
@@ -63,6 +64,10 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
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) => (
|
||||
@@ -74,6 +79,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
|
||||
@@ -78,7 +78,7 @@ function EditKategoriPrestasi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -40,7 +40,7 @@ function CreateKategoriPrestasi() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -57,7 +57,7 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py="md">
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
@@ -65,28 +65,33 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* DESKTOP: Table */}
|
||||
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm" withBorder>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4} c="dark">List Kategori Prestasi</Title>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/landing-page/prestasi-desa/kategori-prestasi-desa/create')}>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
<Group justify="space-between" mb="xl">
|
||||
<Title order={2} size="lg" lh={1.2}>List Kategori Prestasi</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/prestasi-desa/kategori-prestasi-desa/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Box visibleFrom="md">
|
||||
<Table verticalSpacing="sm" highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh style={{ width: '120px' }} ta={'center'}>Edit</TableTh>
|
||||
<TableTh ta={'center'} style={{ width: '120px' }}>Delete</TableTh>
|
||||
<TableTh><Text fz="sm" fw={600} c="dark">Nama Kategori</Text></TableTh>
|
||||
<TableTh w={120} ta="center"><Text fz="sm" fw={600} c="dark">Edit</Text></TableTh>
|
||||
<TableTh w={120} ta="center"><Text fz="sm" fw={600} c="dark">Delete</Text></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={2} style={{ textAlign: 'center' }}>
|
||||
<Text py="md" c="dimmed">
|
||||
<TableTd colSpan={3} ta="center">
|
||||
<Text py="md" c="dimmed" fz="sm" lh={1.5}>
|
||||
{search ? 'Tidak ada hasil yang cocok' : 'Belum ada data kategori'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
@@ -95,68 +100,130 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||
</Box>
|
||||
<Text truncate="end" fz="md" lh={1.5} c="dark">
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
<TableTd ta="center" w={120}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
<TableTd ta="center" w={120}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="sm"
|
||||
styles={{
|
||||
control: {
|
||||
'&[data-active]': {
|
||||
background: `${colors['blue-button']} !important`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="lg">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="sm"
|
||||
styles={{
|
||||
control: {
|
||||
'&[data-active]': {
|
||||
background: `${colors['blue-button']} !important`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
{/* MOBILE: Card */}
|
||||
<Box hiddenFrom="md">
|
||||
<Stack gap="md">
|
||||
{filteredData.length === 0 ? (
|
||||
<Paper p="lg" ta="center">
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
{search ? 'Tidak ada hasil yang cocok' : 'Belum ada data kategori'}
|
||||
</Text>
|
||||
</Paper>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<Paper key={item.id} p="md" withBorder bg={colors['white-1']}>
|
||||
<Stack gap="xs">
|
||||
<Text fz="sm" lh={1.5} fw={600} c="dark">{item.name}</Text>
|
||||
<Group justify="flex-end" gap="xs">
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="xs"
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
)}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center py="lg">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="xs"
|
||||
styles={{
|
||||
control: {
|
||||
'&[data-active]': {
|
||||
background: `${colors['blue-button']} !important`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus kategori prestasi ini?'
|
||||
/>
|
||||
</Paper>
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus kategori prestasi ini?'
|
||||
/>
|
||||
</Box >
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default KategoriPrestasiDesa
|
||||
export default KategoriPrestasiDesa
|
||||
@@ -128,7 +128,7 @@ export default function EditPrestasiDesa() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -41,7 +41,7 @@ function DetailPrestasiDesa() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -53,7 +53,7 @@ function DetailPrestasiDesa() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -69,7 +69,7 @@ function CreatePrestasiDesa() {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -28,6 +28,7 @@ function ListPrestasiDesa() {
|
||||
function ListPrestasi({ search }: { search: string }) {
|
||||
const listState = useProxy(prestasiState.prestasiDesa)
|
||||
const router = useRouter();
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -39,60 +40,65 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
}, []);
|
||||
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Prestasi Desa</Title>
|
||||
<Box py={{ base: 'sm', md: 'md' }}>
|
||||
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||
<Title order={2} size={isMobile ? 'md' : 'lg'} lh={1.2}>
|
||||
Daftar Prestasi Desa
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/prestasi-desa/list-prestasi-desa/create')}
|
||||
size={isMobile ? 'xs' : 'sm'}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover miw={0}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>Nama Prestasi</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Kategori</TableTh>
|
||||
<TableTh style={{ width: '25%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
<TableTh w="25%">Nama Prestasi</TableTh>
|
||||
<TableTh w="25%">Deskripsi</TableTh>
|
||||
<TableTh w="25%">Kategori</TableTh>
|
||||
<TableTh w="25%" ta="center">Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||
</Box>
|
||||
<TableTd w="25%">
|
||||
<Text truncate="end" fz="md" lh={1.5}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<TableTd w="25%">
|
||||
<Text lineClamp={1} fz="md" c="dimmed" lh={1.5} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box w={150}>
|
||||
<Text truncate="end" fz={"sm"}>{item.kategori?.name || 'Tidak ada kategori'}</Text>
|
||||
</Box>
|
||||
<TableTd w="25%">
|
||||
<Text truncate="end" fz="md" lh={1.5}>
|
||||
{item.kategori?.name || 'Tidak ada kategori'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
||||
<TableTd w="25%" ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
@@ -108,23 +114,63 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4} style={{ textAlign: 'center' }}>
|
||||
<Text c="dimmed" py="md">Tidak ada data prestasi</Text>
|
||||
<TableTd colSpan={4} ta="center">
|
||||
<Text c="dimmed" py="md" fz="sm" lh={1.4}>
|
||||
Tidak ada data prestasi
|
||||
</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Cards */}
|
||||
<Stack hiddenFrom="md" gap="xs">
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||
<Stack gap={4}>
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Text fz="xs" c="dimmed" lh={1.4}>
|
||||
Kategori: {item.kategori?.name || 'Tidak ada kategori'}
|
||||
</Text>
|
||||
<Group justify="flex-end" mt="xs">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={14} />}
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
) : (
|
||||
<Center py="md">
|
||||
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||
Tidak ada data prestasi
|
||||
</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="lg">
|
||||
<Center mt={{ base: 'md', md: 'lg' }}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={load}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="sm"
|
||||
size={isMobile ? 'xs' : 'sm'}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
@@ -132,4 +178,4 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
export default ListPrestasiDesa;
|
||||
export default ListPrestasiDesa;
|
||||
@@ -75,7 +75,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<Box visibleFrom='md'>
|
||||
<Box visibleFrom='md' pb={10}>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
@@ -108,7 +108,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
|
||||
<Box hiddenFrom='md'>
|
||||
<Box hiddenFrom='md' pb={10}>
|
||||
<ScrollArea
|
||||
type="auto"
|
||||
offsetScrollbars={false}
|
||||
|
||||
@@ -33,7 +33,7 @@ import { useEffect, useState } from "react";
|
||||
import { getNavbar } from "./(dashboard)/user&role/_com/dynamicNavbar";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [opened, { toggle }] = useDisclosure();
|
||||
const [opened, { toggle, close }] = useDisclosure(); // ✅ Tambahkan 'close'
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
|
||||
@@ -45,21 +45,19 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me', {
|
||||
credentials: 'include' // ✅ ADD credentials
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.user) {
|
||||
// ✅ Check if user is NOT active → redirect to waiting room
|
||||
if (!data.user.isActive) {
|
||||
authStore.setUser(null);
|
||||
router.replace('/waiting-room');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Fetch menuIds
|
||||
const menuRes = await fetch(`/api/admin/user-menu-access?userId=${data.user.id}`, {
|
||||
credentials: 'include' // ✅ ADD credentials
|
||||
credentials: 'include'
|
||||
});
|
||||
const menuData = await menuRes.json();
|
||||
|
||||
@@ -67,7 +65,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
? [...menuData.menuIds]
|
||||
: null;
|
||||
|
||||
// ✅ Set user dengan menuIds yang fresh
|
||||
authStore.setUser({
|
||||
id: data.user.id,
|
||||
name: data.user.name,
|
||||
@@ -76,7 +73,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
isActive: data.user.isActive
|
||||
});
|
||||
|
||||
// ✅ IMPROVED: Redirect ONLY if di root /admin
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
if (currentPath === '/admin') {
|
||||
@@ -84,7 +80,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
console.log('🔄 Redirecting from /admin to:', expectedPath);
|
||||
router.replace(expectedPath);
|
||||
}
|
||||
// ✅ Jangan redirect jika user sudah di path yang valid
|
||||
|
||||
} else {
|
||||
authStore.setUser(null);
|
||||
@@ -100,17 +95,17 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
|
||||
fetchUser();
|
||||
}, [router]); // ✅ Only depend on router
|
||||
}, [router]);
|
||||
|
||||
const getRedirectPath = (roleId: number): string => {
|
||||
switch (roleId) {
|
||||
case 0: // DEVELOPER
|
||||
case 1: // SUPERADMIN
|
||||
case 2: // ADMIN_DESA
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
return '/admin/landing-page/profil/program-inovasi';
|
||||
case 3: // ADMIN_KESEHATAN
|
||||
case 3:
|
||||
return '/admin/kesehatan/posyandu';
|
||||
case 4: // ADMIN_PENDIDIKAN
|
||||
case 4:
|
||||
return '/admin/pendidikan/info-sekolah/jenjang-pendidikan';
|
||||
default:
|
||||
return '/admin';
|
||||
@@ -139,7 +134,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const response = await fetch('/api/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include' // ✅ ADD credentials
|
||||
credentials: 'include'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
@@ -163,6 +158,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Handler untuk menutup mobile menu saat navigasi
|
||||
const handleNavClick = (path: string) => {
|
||||
router.push(path);
|
||||
close(); // Tutup mobile menu
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
suppressHydrationWarning
|
||||
@@ -177,7 +178,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
}}
|
||||
padding="md"
|
||||
>
|
||||
{/* ... rest of your JSX (Header, Navbar, Main) sama seperti sebelumnya ... */}
|
||||
<AppShellHeader
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #ffffff, #f9fbff)",
|
||||
@@ -230,16 +230,48 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
</AppShellHeader>
|
||||
|
||||
<AppShellNavbar component={ScrollArea} style={{ background: "#ffffff", borderRight: `1px solid ${colors["blue-button"]}20` }} p={{ base: 'xs', sm: 'sm' }}>
|
||||
{/* ... Navbar content sama seperti sebelumnya ... */}
|
||||
<AppShell.Section p="sm">
|
||||
{currentNav.map((v, k) => {
|
||||
const isParentActive = segments.includes(_.lowerCase(v.name));
|
||||
return (
|
||||
<NavLink key={k} defaultOpened={isParentActive} c={isParentActive ? colors["blue-button"] : "gray"} label={<Text fw={isParentActive ? 600 : 400} fz="sm">{v.name}</Text>} style={{ borderRadius: rem(10), marginBottom: rem(4), transition: "background 150ms ease" }} styles={{ root: { '&:hover': { backgroundColor: 'rgba(25, 113, 194, 0.05)' } } }} variant="light" active={isParentActive}>
|
||||
<NavLink
|
||||
key={k}
|
||||
defaultOpened={isParentActive}
|
||||
c={isParentActive ? colors["blue-button"] : "gray"}
|
||||
label={<Text fw={isParentActive ? 600 : 400} fz="sm">{v.name}</Text>}
|
||||
style={{ borderRadius: rem(10), marginBottom: rem(4), transition: "background 150ms ease" }}
|
||||
styles={{ root: { '&:hover': { backgroundColor: 'rgba(25, 113, 194, 0.05)' } } }}
|
||||
variant="light"
|
||||
active={isParentActive}
|
||||
>
|
||||
{v.children.map((child, key) => {
|
||||
const isChildActive = segments.includes(_.lowerCase(child.name));
|
||||
return (
|
||||
<NavLink key={key} href={child.path} c={isChildActive ? colors["blue-button"] : "gray"} label={<Text fw={isChildActive ? 600 : 400} fz="sm">{child.name}</Text>} styles={{ root: { borderRadius: rem(8), marginBottom: rem(2), transition: 'background 150ms ease', padding: '6px 12px', '&:hover': { backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)' }, ...(isChildActive && { backgroundColor: 'rgba(25, 113, 194, 0.1)' }) } }} active={isChildActive} component={Link} />
|
||||
<NavLink
|
||||
key={key}
|
||||
// ✅ PERBAIKAN: Gunakan onClick untuk handle navigasi dan close menu
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNavClick(child.path);
|
||||
}}
|
||||
href={child.path}
|
||||
c={isChildActive ? colors["blue-button"] : "gray"}
|
||||
label={<Text fw={isChildActive ? 600 : 400} fz="sm">{child.name}</Text>}
|
||||
styles={{
|
||||
root: {
|
||||
borderRadius: rem(8),
|
||||
marginBottom: rem(2),
|
||||
transition: 'background 150ms ease',
|
||||
padding: '6px 12px',
|
||||
'&:hover': {
|
||||
backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)'
|
||||
},
|
||||
...(isChildActive && { backgroundColor: 'rgba(25, 113, 194, 0.1)' })
|
||||
}
|
||||
}}
|
||||
active={isChildActive}
|
||||
component={Link}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</NavLink>
|
||||
|
||||
Reference in New Issue
Block a user