Fix QC Kak Inno 16 Des

Fix QC Kak Ayu 16 Des
FIx UI Admin Mobile Menu PPID
Fix Search Admin Menu Landing Page & Menu PPID
This commit is contained in:
2025-12-17 17:37:58 +08:00
parent c8484357cb
commit dc8793e3ae
44 changed files with 2028 additions and 833 deletions

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconBuildingCommunity, IconHierarchy2, IconUsers } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -63,51 +63,92 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false}
>
{/* ✅ Scroll horizontal wrapper */}
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
<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={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
padding: "1.5rem",
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
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
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
{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>
{children}
</TabsPanel>
))}
</Tabs>
</Stack >
);
}

View File

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

View File

@@ -51,7 +51,7 @@ function DetailPegawai() {
const data = statePegawai.findUnique.data;
return (
<Box>
<Box px={{ base: 0, md: 'xs' }} py="xs">
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
@@ -59,7 +59,7 @@ function DetailPegawai() {
</Box>
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

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

View File

@@ -1,13 +1,32 @@
/* 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 {
Badge,
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title
} from '@mantine/core';
import { IconDeviceImacCog, IconPlus, IconSearch } 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 stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
import { useDebouncedValue } from '@mantine/hooks';
function PegawaiPPID() {
const [search, setSearch] = useState("");
@@ -28,6 +47,7 @@ function PegawaiPPID() {
function ListPegawaiPPID({ search }: { search: string }) {
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -38,47 +58,28 @@ function ListPegawaiPPID({ search }: { search: string }) {
} = stateOrganisasi.findMany;
useEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
<Stack py="xl">
<Skeleton height={300} radius="md" />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Box py="xl">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Pegawai PPID</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/ppid/struktur-ppid/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 PPID</Title>
<Title order={2} lh={1.2}>
Daftar Pegawai PPID
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -87,53 +88,70 @@ function ListPegawaiPPID({ search }: { search: string }) {
>
Tambah Baru
</Button>
</Group>
<Center py="xl">
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Tidak ada data pegawai yang ditemukan
</Text>
</Center>
</Paper>
</Box>
);
}
return (
<Box py="xl">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={2} lh={1.2}>
Daftar Pegawai PPID
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/ppid/struktur-ppid/pegawai/create')}
>
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 Lengkap</TableTh>
<TableTh style={{ width: '20%' }}>Posisi</TableTh>
<TableTh style={{ width: '10%' }}>Status</TableTh>
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>
Nama Lengkap
</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>
Posisi
</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>
Status
</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>
Aksi
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={150}>
<Text fw={500} truncate="end" lineClamp={1}>
{item.namaLengkap}
</Text>
</Box>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.namaLengkap}
</Text>
</TableTd>
<TableTd>
<Box w={150}>
<Badge variant="light" color="blue">
{item.posisi?.nama || 'Belum diatur'}
</Badge>
</Box>
<Badge variant="light" color="blue" fz="sm" lh={1.4}>
{item.posisi?.nama || 'Belum diatur'}
</Badge>
</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>
<Badge color={item.isActive ? "green" : "red"} fz="sm" lh={1.4}>
{item.isActive ? "Aktif" : "Tidak Aktif"}
</Badge>
</TableTd>
<TableTd>
<Button
@@ -143,6 +161,7 @@ function ListPegawaiPPID({ search }: { search: string }) {
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}
fz="sm"
>
Detail
</Button>
@@ -152,7 +171,47 @@ function ListPegawaiPPID({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
<Center mt="lg">
{/* Mobile: Card List */}
<Stack hiddenFrom="md" gap="sm" mt="md">
{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 Lengkap</Text>
<Text fz="md" fw={500} lh={1.4}>
{item.namaLengkap}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Posisi</Text>
<Badge variant="light" color="blue" fz="xs" lh={1.4}>
{item.posisi?.nama || 'Belum diatur'}
</Badge>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Status</Text>
<Badge color={item.isActive ? "green" : "red"} fz="xs" lh={1.4}>
{item.isActive ? "Aktif" : "Tidak Aktif"}
</Badge>
</Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}
fz="xs"
>
Detail
</Button>
</Stack>
</Paper>
))}
</Stack>
<Center mt="xl">
<Pagination
value={page}
onChange={(newPage) => {
@@ -170,4 +229,4 @@ function ListPegawaiPPID({ search }: { search: string }) {
);
}
export default PegawaiPPID;
export default PegawaiPPID;

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -31,6 +31,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -41,8 +42,8 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
} = stateOrganisasi.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const handleHapus = async () => {
if (selectedId) {
@@ -56,63 +57,63 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
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 Posisi Organisasi PPID</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
>
Tambah Baru
</Button>
<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}>Daftar Posisi Organisasi PPID</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<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>
<TableTh fz="sm" fw={600} lh={1.4}>Nama Posisi</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>Deskripsi</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>Hierarki</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>Edit</TableTh>
<TableTh fz="sm" fw={600} lh={1.4}>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>
<Text fz="md" fw={600} lh={1.5} 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 w={200}>
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
</TableTd>
<TableTd style={{ width: '20%' }}>
<Text>{item.hierarki || '-'}</Text>
<TableTd>
<Text fz="md" lh={1.5}>{item.hierarki || '-'}</Text>
</TableTd>
<TableTd style={{ width: '20%' }}>
<Button
variant="light"
color="green"
size="sm"
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
>
<IconEdit size={18} />
</Button>
<TableTd>
<Button
variant="light"
color="green"
size="sm"
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
>
<IconEdit size={18} />
</Button>
</TableTd>
<TableTd style={{ width: '20%' }}>
<TableTd>
<Button
variant="light"
color="red"
@@ -129,9 +130,11 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
))
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text color="dimmed">Tidak ada data posisi organisasi yang cocok</Text>
<TableTd colSpan={5}>
<Center py={{ base: 'sm', md: 'md' }}>
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
Tidak ada data posisi organisasi yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -139,7 +142,59 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Stack gap="xs" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={4}>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Nama Posisi</Text>
<Text fz="sm" fw={600} lh={1.5}>{item.nama}</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Deskripsi</Text>
<Text fz="sm" lh={1.5} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Hierarki</Text>
<Text fz="sm" lh={1.5}>{item.hierarki || '-'}</Text>
</Box>
<Group justify="flex-end" gap="xs">
<Button
variant="light"
color="green"
size="xs"
onClick={() => router.push(`/admin/ppid/struktur-ppid/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 fz="sm" c="dimmed" ta="center" lh={1.5}>
Tidak ada data posisi organisasi yang cocok
</Text>
</Center>
)}
</Stack>
</Paper>
<Center>
<Pagination
value={page}
@@ -154,6 +209,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
radius="md"
/>
</Center>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
@@ -165,4 +221,4 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
);
}
export default PosisiOrganisasiPPID;
export default PosisiOrganisasiPPID;