288 lines
7.9 KiB
TypeScript
288 lines
7.9 KiB
TypeScript
import apiFetch from "@/lib/apiFetch";
|
||
import {
|
||
Badge,
|
||
Card,
|
||
CloseButton,
|
||
Container,
|
||
Divider,
|
||
Flex,
|
||
Group,
|
||
Input,
|
||
Stack,
|
||
Tabs,
|
||
Text,
|
||
Title,
|
||
} from "@mantine/core";
|
||
import { useShallowEffect } from "@mantine/hooks";
|
||
import {
|
||
IconAlignJustified,
|
||
IconClockHour3,
|
||
IconFileSad,
|
||
IconMapPin,
|
||
IconSearch,
|
||
} from "@tabler/icons-react";
|
||
import { useState } from "react";
|
||
import { useLocation, useNavigate } from "react-router-dom";
|
||
import useSwr from "swr";
|
||
import { proxy, subscribe } from "valtio";
|
||
|
||
const state = proxy({ reload: "" });
|
||
function reloadState() {
|
||
state.reload = Math.random().toString();
|
||
}
|
||
|
||
export default function PengaduanListPage() {
|
||
const { search } = useLocation();
|
||
const query = new URLSearchParams(search);
|
||
const status = query.get("status") as StatusKey;
|
||
|
||
return (
|
||
<Container size="xl" py="xl" w={"100%"}>
|
||
<Stack gap="xl">
|
||
<TabListPengaduan status={status || "semua"} />
|
||
<ListPengaduan status={status || "semua"} />
|
||
</Stack>
|
||
</Container>
|
||
);
|
||
}
|
||
|
||
function TabListPengaduan({ status }: { status: string }) {
|
||
const navigate = useNavigate();
|
||
const { data, mutate, isLoading } = useSwr("/pengaduan/count", () =>
|
||
apiFetch.api.pengaduan.count.get().then((res) => res.data),
|
||
);
|
||
|
||
useShallowEffect(() => {
|
||
mutate();
|
||
}, []);
|
||
|
||
return (
|
||
<Tabs defaultValue={status || "semua"} color="teal">
|
||
<Tabs.List grow>
|
||
<Tabs.Tab
|
||
value="semua"
|
||
onClick={() => {
|
||
navigate("?status=semua");
|
||
}}
|
||
>
|
||
Semua ({data?.semua || 0})
|
||
</Tabs.Tab>
|
||
<Tabs.Tab
|
||
value="antrian"
|
||
onClick={() => {
|
||
navigate("?status=antrian");
|
||
}}
|
||
>
|
||
Antrian ({data?.antrian || 0})
|
||
</Tabs.Tab>
|
||
<Tabs.Tab
|
||
value="diterima"
|
||
onClick={() => {
|
||
navigate("?status=diterima");
|
||
}}
|
||
>
|
||
Diterima ({data?.diterima || 0})
|
||
</Tabs.Tab>
|
||
<Tabs.Tab
|
||
value="dikerjakan"
|
||
onClick={() => {
|
||
navigate("?status=dikerjakan");
|
||
}}
|
||
>
|
||
Dikerjakan ({data?.dikerjakan || 0})
|
||
</Tabs.Tab>
|
||
<Tabs.Tab
|
||
value="selesai"
|
||
onClick={() => {
|
||
navigate("?status=selesai");
|
||
}}
|
||
>
|
||
Selesai ({data?.selesai || 0})
|
||
</Tabs.Tab>
|
||
<Tabs.Tab
|
||
value="ditolak"
|
||
onClick={() => {
|
||
navigate("?status=ditolak");
|
||
}}
|
||
>
|
||
Ditolak ({data?.ditolak || 0})
|
||
</Tabs.Tab>
|
||
</Tabs.List>
|
||
</Tabs>
|
||
);
|
||
}
|
||
|
||
type StatusKey =
|
||
| "antrian"
|
||
| "diterima"
|
||
| "dikerjakan"
|
||
| "ditolak"
|
||
| "selesai"
|
||
| "semua";
|
||
|
||
function ListPengaduan({ status }: { status: StatusKey }) {
|
||
const navigate = useNavigate();
|
||
const [page, setPage] = useState(1);
|
||
const [value, setValue] = useState("");
|
||
const { data, mutate, isLoading } = useSwr("/", async () => {
|
||
const res = await apiFetch.api.pengaduan.list.get({
|
||
query: {
|
||
status,
|
||
search: value,
|
||
take: "",
|
||
page: "",
|
||
},
|
||
});
|
||
|
||
return Array.isArray(res?.data) ? res.data : []; // ⬅ paksa return array
|
||
});
|
||
|
||
useShallowEffect(() => {
|
||
mutate();
|
||
}, [status, value]);
|
||
|
||
useShallowEffect(() => {
|
||
const unsubscribe = subscribe(state, () => mutate());
|
||
return () => unsubscribe();
|
||
}, []);
|
||
|
||
if (isLoading)
|
||
return (
|
||
<Card
|
||
radius="lg"
|
||
p="xl"
|
||
withBorder
|
||
style={{
|
||
background:
|
||
"linear-gradient(145deg, rgba(25,25,25,0.95), rgba(45,45,45,0.85))",
|
||
}}
|
||
>
|
||
<Text size="sm" c="dimmed">
|
||
Loading pengaduan...
|
||
</Text>
|
||
</Card>
|
||
);
|
||
|
||
const list = data || [];
|
||
|
||
return (
|
||
<Stack gap="xl">
|
||
<Group grow>
|
||
<Input
|
||
value={value}
|
||
placeholder="Cari pengaduan..."
|
||
onChange={(event) => setValue(event.currentTarget.value)}
|
||
leftSection={<IconSearch size={16} />}
|
||
rightSectionPointerEvents="all"
|
||
rightSection={
|
||
<CloseButton
|
||
aria-label="Clear input"
|
||
onClick={() => setValue("")}
|
||
style={{ display: value ? undefined : "none" }}
|
||
/>
|
||
}
|
||
/>
|
||
{/* <Group justify="flex-end">
|
||
<Text size="sm">Menampilkan {Number(data?.data?.length) * (page - 1) + 1} – {Math.min(10, Number(data?.data?.length) * page)} dari {Number(data?.data?.length)}</Text>
|
||
<Pagination total={Number(data?.data?.length)} value={page} onChange={setPage} withPages={false} />
|
||
</Group> */}
|
||
</Group>
|
||
{list.length === 0 ? (
|
||
<Flex justify="center" align="center" py={"xl"}>
|
||
<Stack gap={4} align="center">
|
||
<IconFileSad size={32} color="gray" />
|
||
<Text c="dimmed" size="sm">
|
||
No pengaduan have been added yet.
|
||
</Text>
|
||
</Stack>
|
||
</Flex>
|
||
) : (
|
||
Array.isArray(list) && list?.map((v: any) => (
|
||
<Card
|
||
key={v.id}
|
||
radius="lg"
|
||
p="xl"
|
||
withBorder
|
||
style={{
|
||
background:
|
||
"linear-gradient(145deg, rgba(25,25,25,0.95), rgba(45,45,45,0.85))",
|
||
borderColor: "rgba(100,100,100,0.2)",
|
||
boxShadow: "0 0 20px rgba(0,255,200,0.08)",
|
||
}}
|
||
onClick={() =>
|
||
navigate(`/scr/dashboard/pengaduan/detail?id=${v.id}`)
|
||
}
|
||
>
|
||
<Stack gap="md">
|
||
<Flex align="center" justify="space-between">
|
||
<Flex direction={"column"}>
|
||
<Title order={3} c="gray.2">
|
||
{v.title}
|
||
</Title>
|
||
<Group>
|
||
<Title order={6} c="gray.5">
|
||
#{v.noPengaduan}
|
||
</Title>
|
||
<Text size="sm" c="dimmed">
|
||
{v.updatedAt}
|
||
</Text>
|
||
</Group>
|
||
</Flex>
|
||
<Badge
|
||
size="xl"
|
||
variant="light"
|
||
radius="sm"
|
||
color={
|
||
v.status === "diterima"
|
||
? "green"
|
||
: v.status === "ditolak"
|
||
? "red"
|
||
: v.status === "selesai"
|
||
? "blue"
|
||
: v.status === "dikerjakan"
|
||
? "gray"
|
||
: "yellow"
|
||
}
|
||
style={{ textTransform: "none" }}
|
||
>
|
||
{v.status}
|
||
</Badge>
|
||
</Flex>
|
||
<Divider my={0} />
|
||
<Stack gap="sm">
|
||
<Flex direction={"column"} justify="flex-start">
|
||
<Group gap="xs">
|
||
<IconClockHour3 size={20} color="white" />
|
||
<Text size="md" c="white">
|
||
Tanggal Aduan
|
||
</Text>
|
||
</Group>
|
||
<Text size="md">{v.createdAt}</Text>
|
||
</Flex>
|
||
<Flex direction={"column"} justify="flex-start">
|
||
<Group gap="xs">
|
||
<IconMapPin size={20} color="white" />
|
||
<Text size="md" c="white">
|
||
Lokasi
|
||
</Text>
|
||
</Group>
|
||
<Text size="md">{v.location}</Text>
|
||
</Flex>
|
||
<Flex direction={"column"} justify="flex-start">
|
||
<Group gap="xs">
|
||
<IconAlignJustified size={20} color="white" />
|
||
<Text size="md" c="white">
|
||
Detail
|
||
</Text>
|
||
</Group>
|
||
<Text size="md">{v.detail}</Text>
|
||
</Flex>
|
||
</Stack>
|
||
</Stack>
|
||
</Card>
|
||
))
|
||
)}
|
||
</Stack>
|
||
);
|
||
}
|