Compare commits

...

13 Commits

Author SHA1 Message Date
9fed41cbe8 fix: testing surat 2026-01-13 17:10:59 +08:00
fc387fe8e6 fix: menu setting
deskiripsi:
- navigate
- list length

No Issues
2026-01-13 15:38:29 +08:00
80df579499 qc: nomer 2 dan 4
Deskripsi:
- button back
- breadcrumb
- active menu

No Issues
2026-01-13 15:10:08 +08:00
5bbbc15c27 Merge pull request 'fix: qc' (#108) from amalia/12-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/108
2026-01-12 17:47:04 +08:00
82765f6ef0 fix: qc
Deskripsi:
- breadcumbs
- back
- active menu

nb: blm selesai

No Issues
2026-01-12 17:43:04 +08:00
e8b5720118 Merge pull request 'amalia/09-jan-26' (#107) from amalia/09-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/107
2026-01-09 17:23:26 +08:00
01334ec573 upd: login
Deskripsi:
- update design login page

No Issues
2026-01-09 16:43:19 +08:00
98ad9b0d72 upd: loading saat melakukan aksi pada detail pengaduan
- mencegah 2x klik

NO Issues
2026-01-09 15:53:41 +08:00
c0471f47f3 upd: detail warga
Deskripsi:
- pagination pada list pengaduan dan list pengajuan surat
- search pada list pengaduan dan list pengajuan surat

No Issues
2026-01-09 15:46:30 +08:00
3d641d2035 Merge pull request 'upd: hapus console log' (#106) from amalia/08-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/106
2026-01-08 17:38:19 +08:00
7de5078868 Merge pull request 'upd: console log server2' (#105) from amalia/08-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/105
2026-01-08 16:25:03 +08:00
ea5072d9ab Merge pull request 'upd: console log server' (#104) from amalia/08-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/104
2026-01-08 16:03:03 +08:00
d63bf024d3 Merge pull request 'upd: console log send wa' (#103) from amalia/08-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/103
2026-01-08 15:50:37 +08:00
10 changed files with 439 additions and 109 deletions

View File

@@ -0,0 +1,44 @@
import { ActionIcon, Anchor, Breadcrumbs, Card, Group } from "@mantine/core";
import { IconChevronLeft } from "@tabler/icons-react";
import { useNavigate } from "react-router-dom";
export default function BreadCrumbs({ dataLink, back, linkBack }: { dataLink: { title: string, link: string, active: boolean }[], back?: boolean, linkBack?: string }) {
const navigate = useNavigate();
return (
<Card
radius="md"
p="sm"
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)",
}}
>
<Group>
{
back &&
<ActionIcon variant="outline" aria-label="Settings" radius={"lg"} onClick={() => window.history.back()}>
<IconChevronLeft size={20} stroke={1.5} />
</ActionIcon>
}
<Breadcrumbs>
{
dataLink.map((item, index) => (
<Anchor
c={item.active ? "gray.0" : "gray.5"}
onClick={() => item.active || item.link == "#" ? null : navigate(item.link)}
key={index}
>
{item.title}
</Anchor>
))
}
</Breadcrumbs>
</Group>
</Card>
)
}

View File

@@ -207,7 +207,7 @@ export default function DesaSetting({
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{list?.map((v: any) => ( {list.length > 0 && list?.map((v: any) => (
<Table.Tr key={v.id}> <Table.Tr key={v.id}>
<Table.Td>{v.name}</Table.Td> <Table.Td>{v.name}</Table.Td>
<Table.Td> <Table.Td>

View File

@@ -4,19 +4,17 @@ import {
Button, Button,
Divider, Divider,
Flex, Flex,
Grid,
Group, Group,
Input,
List, List,
Modal, Modal,
Stack, Stack,
Table, Table,
Text, Text,
Title, Title,
Tooltip, Tooltip
} from "@mantine/core"; } from "@mantine/core";
import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react"; import { IconEye, IconTrash } from "@tabler/icons-react";
import type { JsonValue } from "generated/prisma/runtime/library"; import type { JsonValue } from "generated/prisma/runtime/library";
import { useState } from "react"; import { useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
@@ -625,7 +623,7 @@ export default function KategoriPelayananSurat({
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{list?.map((v: any) => ( {list.length > 0 && list?.map((v: any) => (
<Table.Tr key={v.id}> <Table.Tr key={v.id}>
<Table.Td>{v.name}</Table.Td> <Table.Td>{v.name}</Table.Td>
<Table.Td> <Table.Td>

View File

@@ -1,12 +1,12 @@
import clientRoutes from "@/clientRoutes"; import clientRoutes from "@/clientRoutes";
import { import {
Button, Button,
Container, Center,
Group, Paper,
PasswordInput, PasswordInput,
Stack, Stack,
Text, Text,
TextInput, TextInput
} from "@mantine/core"; } from "@mantine/core";
import { useState } from "react"; import { useState } from "react";
import apiFetch from "../lib/apiFetch"; import apiFetch from "../lib/apiFetch";
@@ -73,25 +73,73 @@ export default function Login() {
}; };
return ( return (
<Container> <Center
<Stack> h="100vh"
<Text>Login</Text> style={{
<TextInput background:
placeholder="Email" "radial-gradient(circle at top, #1f2d2b 0%, #0b0f0e 60%)",
value={email} }}
onChange={(e) => setEmail(e.target.value)} >
/> <Paper
<PasswordInput radius="lg"
placeholder="Password" p="xl"
value={password} w={420}
onChange={(e) => setPassword(e.target.value)} style={{
/> background: "rgba(20, 20, 20, 0.75)",
<Group justify="right"> backdropFilter: "blur(12px)",
<Button onClick={handleSubmit} disabled={loading}> border: "1px solid rgba(255, 255, 255, 0.08)",
boxShadow: "0 20px 60px rgba(0,0,0,0.6)",
}}
>
<Stack>
<Text
size="xl"
fw={700}
ta="center"
c="white"
>
Welcome Back
</Text>
<Text
size="sm"
ta="center"
c="dimmed"
>
Sign in to continue to your dashboard
</Text>
<TextInput
label="Email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<PasswordInput
label="Password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Button
fullWidth
mt="md"
radius="md"
size="md"
variant="gradient"
gradient={{ from: "teal", to: "cyan", deg: 45 }}
style={{
transition: "all 0.2s ease",
}}
onClick={handleSubmit}
disabled={loading}
>
Login Login
</Button> </Button>
</Group> </Stack>
</Stack> </Paper>
</Container> </Center>
); );
} }

View File

@@ -73,7 +73,7 @@ export default function DashboardLayout() {
<AppShell <AppShell
padding="lg" padding="lg"
navbar={{ navbar={{
width: 260, width: 300,
breakpoint: "sm", breakpoint: "sm",
collapsed: { mobile: !opened, desktop: !opened }, collapsed: { mobile: !opened, desktop: !opened },
}} }}
@@ -290,22 +290,31 @@ function NavigationDashboard() {
.map((item) => ( .map((item) => (
<NavLink <NavLink
key={item.path} key={item.path}
active={isActive(item.path as keyof typeof clientRoute)} active={isActive(item.path as keyof typeof clientRoute) ||
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")}
leftSection={item.icon} leftSection={item.icon}
label={ label={
<Flex align="center" gap={6}> <Flex align="center" gap={6}>
<Text fw={500}>{item.label}</Text> <Text fw={500}>{item.label}</Text>
{isActive(item.path as keyof typeof clientRoute) && ( {(
<Badge isActive(item.path as keyof typeof clientRoute) ||
variant="light" (location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
color="teal" (location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
radius="sm" (location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")
size="xs" )
style={{ textTransform: "none" }} && (
> <Badge
Active variant="light"
</Badge> color="teal"
)} radius="sm"
size="xs"
style={{ textTransform: "none" }}
>
Active
</Badge>
)}
</Flex> </Flex>
} }
description={item.description} description={item.description}
@@ -313,7 +322,10 @@ function NavigationDashboard() {
navigate(clientRoutes[item.path as keyof typeof clientRoute]) navigate(clientRoutes[item.path as keyof typeof clientRoute])
} }
style={{ style={{
backgroundColor: isActive(item.path as keyof typeof clientRoute) backgroundColor: isActive(item.path as keyof typeof clientRoute) ||
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")
? "rgba(0,255,200,0.1)" ? "rgba(0,255,200,0.1)"
: "transparent", : "transparent",
borderRadius: "8px", borderRadius: "8px",

View File

@@ -1,3 +1,4 @@
import BreadCrumbs from "@/components/BreadCrumbs";
import ModalFile from "@/components/ModalFile"; import ModalFile from "@/components/ModalFile";
import ModalSurat from "@/components/ModalSurat"; import ModalSurat from "@/components/ModalSurat";
import notification from "@/components/notificationGlobal"; import notification from "@/components/notificationGlobal";
@@ -49,6 +50,11 @@ import { useLocation } from "react-router-dom";
import useSwr from "swr"; import useSwr from "swr";
export default function DetailPengajuanPage() { export default function DetailPengajuanPage() {
const dataMenu = [
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
{ title: "Pelayanan Surat", link: "/scr/dashboard/pelayanan-surat/list-pelayanan", active: false },
{ title: "Detail Pengajuan Surat", link: "#", active: true },
];
const { search } = useLocation(); const { search } = useLocation();
const query = new URLSearchParams(search); const query = new URLSearchParams(search);
const id = query.get("id"); const id = query.get("id");
@@ -67,6 +73,9 @@ export default function DetailPengajuanPage() {
return ( return (
<Container size="xl" py="xl" w={"100%"}> <Container size="xl" py="xl" w={"100%"}>
<Grid> <Grid>
<Grid.Col span={12}>
<BreadCrumbs dataLink={dataMenu} back />
</Grid.Col>
<Grid.Col span={8}> <Grid.Col span={8}>
<Stack gap={"xl"}> <Stack gap={"xl"}>
<DetailDataPengajuan <DetailDataPengajuan
@@ -266,6 +275,7 @@ function DetailDataPengajuan({
} }
useShallowEffect(() => { useShallowEffect(() => {
console.log('jalan', viewImg)
if (viewImg) { if (viewImg) {
setOpenedPreviewFile(true); setOpenedPreviewFile(true);
} }
@@ -365,6 +375,7 @@ function DetailDataPengajuan({
open={openedPreviewFile && !_.isEmpty(viewImg.file)} open={openedPreviewFile && !_.isEmpty(viewImg.file)}
onClose={() => { onClose={() => {
setOpenedPreviewFile(false); setOpenedPreviewFile(false);
setViewImg({ file: "", folder: "" })
}} }}
folder={viewImg.folder} folder={viewImg.folder}
fileName={viewImg.file} fileName={viewImg.file}

View File

@@ -1,3 +1,4 @@
import BreadCrumbs from "@/components/BreadCrumbs";
import ModalFile from "@/components/ModalFile"; import ModalFile from "@/components/ModalFile";
import notification from "@/components/notificationGlobal"; import notification from "@/components/notificationGlobal";
import apiFetch from "@/lib/apiFetch"; import apiFetch from "@/lib/apiFetch";
@@ -40,6 +41,11 @@ import useSwr from "swr";
export default function DetailPengaduanPage() { export default function DetailPengaduanPage() {
const dataMenu = [
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
{ title: "Pengaduan", link: "/scr/dashboard/pengaduan/list", active: false },
{ title: "Detail Pengaduan", link: "#", active: true },
];
const { search } = useLocation(); const { search } = useLocation();
const query = new URLSearchParams(search); const query = new URLSearchParams(search);
const id = query.get("id"); const id = query.get("id");
@@ -58,6 +64,9 @@ export default function DetailPengaduanPage() {
return ( return (
<Container size="xl" py="xl" w={"100%"}> <Container size="xl" py="xl" w={"100%"}>
<Grid> <Grid>
<Grid.Col span={12}>
<BreadCrumbs dataLink={dataMenu} back />
</Grid.Col>
<Grid.Col span={8}> <Grid.Col span={8}>
<Stack gap={"xl"}> <Stack gap={"xl"}>
<DetailDataPengaduan <DetailDataPengaduan
@@ -93,6 +102,7 @@ function DetailDataPengaduan({
const [keterangan, setKeterangan] = useState(""); const [keterangan, setKeterangan] = useState("");
const [host, setHost] = useState<User | null>(null); const [host, setHost] = useState<User | null>(null);
const [permissions, setPermissions] = useState<JsonValue[]>([]); const [permissions, setPermissions] = useState<JsonValue[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => { useEffect(() => {
async function fetchHost() { async function fetchHost() {
@@ -111,6 +121,7 @@ function DetailDataPengaduan({
const handleKonfirmasi = async (cat: "terima" | "tolak") => { const handleKonfirmasi = async (cat: "terima" | "tolak") => {
try { try {
setIsLoading(true);
const res = await apiFetch.api.pengaduan["update-status"].post({ const res = await apiFetch.api.pengaduan["update-status"].post({
id: data?.id, id: data?.id,
status: status:
@@ -184,6 +195,8 @@ function DetailDataPengaduan({
message: "Failed to update pengaduan", message: "Failed to update pengaduan",
type: "error", type: "error",
}); });
} finally {
setIsLoading(false);
} }
}; };
@@ -218,6 +231,7 @@ function DetailDataPengaduan({
color="red" color="red"
disabled={keterangan.length < 1} disabled={keterangan.length < 1}
onClick={() => handleKonfirmasi("tolak")} onClick={() => handleKonfirmasi("tolak")}
loading={isLoading}
> >
Tolak Tolak
</Button> </Button>
@@ -242,6 +256,7 @@ function DetailDataPengaduan({
variant="filled" variant="filled"
color="green" color="green"
onClick={() => handleKonfirmasi("terima")} onClick={() => handleKonfirmasi("terima")}
loading={isLoading}
> >
Ya Ya
</Button> </Button>

View File

@@ -1,3 +1,4 @@
import BreadCrumbs from "@/components/BreadCrumbs";
import DesaSetting from "@/components/DesaSetting"; import DesaSetting from "@/components/DesaSetting";
import KategoriPelayananSurat from "@/components/KategoriPelayananSurat"; import KategoriPelayananSurat from "@/components/KategoriPelayananSurat";
import KategoriPengaduan from "@/components/KategoriPengaduan"; import KategoriPengaduan from "@/components/KategoriPengaduan";
@@ -15,14 +16,21 @@ import {
IconUsersGroup, IconUsersGroup,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import type { JsonValue } from "generated/prisma/runtime/library"; import type { JsonValue } from "generated/prisma/runtime/library";
import _ from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
export default function DetailSettingPage() { export default function DetailSettingPage() {
const { search } = useLocation(); const { search } = useLocation();
const query = new URLSearchParams(search); const query = new URLSearchParams(search);
const type = query.get("type"); const type = query.get("type");
const navigate = useNavigate();
const [permissions, setPermissions] = useState<JsonValue[]>([]); const [permissions, setPermissions] = useState<JsonValue[]>([]);
const dataMenu = [
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
{ title: "Setting", link: "#", active: false },
{ title: type == "cat-pengaduan" ? "Kategori Pengaduan" : type == "cat-pelayanan" ? "Kategori Pelayanan Surat" : type ? _.upperFirst(type) : "Profile", link: "#", active: true },
];
useEffect(() => { useEffect(() => {
async function fetchPermissions() { async function fetchPermissions() {
@@ -87,6 +95,9 @@ export default function DetailSettingPage() {
return ( return (
<Container size="xl" py="xl" w={"100%"}> <Container size="xl" py="xl" w={"100%"}>
<Grid> <Grid>
<Grid.Col span={12}>
<BreadCrumbs dataLink={dataMenu} back />
</Grid.Col>
<Grid.Col span={3}> <Grid.Col span={3}>
<Card <Card
radius="md" radius="md"
@@ -104,7 +115,7 @@ export default function DetailSettingPage() {
.map((item) => ( .map((item) => (
<NavLink <NavLink
key={item.key} key={item.key}
href={"?type=" + item.path} onClick={()=>{navigate("?type=" + item.path)}}
label={item.label} label={item.label}
leftSection={item.icon} leftSection={item.icon}
active={ active={

View File

@@ -1,62 +1,75 @@
import BreadCrumbs from "@/components/BreadCrumbs";
import notification from "@/components/notificationGlobal";
import apiFetch from "@/lib/apiFetch"; import apiFetch from "@/lib/apiFetch";
import { import {
Avatar, Avatar,
Box, Box,
Button, Button,
Card, Card,
CloseButton,
Container, Container,
Divider, Divider,
Flex, Flex,
Grid, Grid,
Group, Group,
Input,
LoadingOverlay, LoadingOverlay,
Pagination,
Stack, Stack,
Table, Table,
Text, Text,
Title, Title,
} from "@mantine/core"; } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useShallowEffect } from "@mantine/hooks";
import { IconPhone } from "@tabler/icons-react"; import { IconPhone, IconSearch } from "@tabler/icons-react";
import _ from "lodash"; import _ from "lodash";
import { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import useSwr from "swr";
export default function DetailWargaPage() { export default function DetailWargaPage() {
const dataMenu = [
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
{ title: "Warga", link: "/scr/dashboard/warga/list-warga", active: false },
{ title: "Detail Warga", link: "#", active: true },
];
const { search } = useLocation(); const { search } = useLocation();
const query = new URLSearchParams(search); const query = new URLSearchParams(search);
const id = query.get("id"); const id = query.get("id");
const { data, mutate, isLoading } = useSwr("/", () => // const { data, mutate, isLoading } = useSwr("/", () =>
apiFetch.api.warga.detail.get({ // apiFetch.api.warga.detail.get({
query: { // query: {
id: id!, // id: id!,
}, // },
}), // }),
); // );
useShallowEffect(() => { // useShallowEffect(() => {
mutate(); // mutate();
}, []); // }, []);
return ( return (
<> <>
<LoadingOverlay <LoadingOverlay
visible={isLoading} // visible={isLoading}
zIndex={1000} zIndex={1000}
overlayProps={{ radius: "sm", blur: 2 }} overlayProps={{ radius: "sm", blur: 2 }}
/> />
<Container size="xl" py="xl" w={"100%"}> <Container size="xl" py="xl" w={"100%"}>
<Grid> <Grid>
<Grid.Col span={12}>
<BreadCrumbs dataLink={dataMenu} back />
</Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<DetailWarga data={data?.data?.warga} /> <DetailWarga id={id!} />
</Grid.Col> </Grid.Col>
<Grid.Col span={8}> <Grid.Col span={8}>
<Stack gap={"xl"}> <Stack gap={"xl"}>
<DetailDataHistori <DetailDataHistori
data={data?.data?.pengaduan} id={id!}
kategori="pengaduan" kategori="pengaduan"
/> />
<DetailDataHistori <DetailDataHistori
data={data?.data?.pelayanan} id={id!}
kategori="pelayanan" kategori="pelayanan"
/> />
</Stack> </Stack>
@@ -68,13 +81,66 @@ export default function DetailWargaPage() {
} }
function DetailDataHistori({ function DetailDataHistori({
data, id,
kategori, kategori,
}: { }: {
data: any; id: string;
kategori: "pengaduan" | "pelayanan"; kategori: "pengaduan" | "pelayanan";
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const [data, setData] = useState<any>([]);
const [totalPages, setTotalPages] = useState(1);
const [totalRows, setTotalRows] = useState(0);
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
async function getData() {
try {
const res = await apiFetch.api.warga.detail.get({
query: {
id,
category: kategori,
page: String(page),
search
}
}) as { data: { success: boolean; data: any[]; totalPages: number, totalRows: number } };
if (res?.data?.success) {
setData(res.data.data)
setTotalPages(res?.data?.totalPages)
setTotalRows(res?.data?.totalRows)
} else {
setData([])
setTotalPages(1)
setTotalRows(0)
notification({
title: "Failed",
message: "Failed to get data",
type: "error",
});
}
} catch (error) {
console.error(error);
notification({
title: "Failed",
message: "Failed to get data",
type: "error",
});
}
}
useShallowEffect(() => {
getData()
}, [page])
useShallowEffect(() => {
setPage(1)
if (page == 1) {
getData()
}
}, [search]);
return ( return (
<Card <Card
@@ -93,6 +159,36 @@ function DetailDataHistori({
<Title order={4} c="gray.2"> <Title order={4} c="gray.2">
Histori {_.upperFirst(kategori)} Histori {_.upperFirst(kategori)}
</Title> </Title>
<Flex
gap="md"
justify="flex-start"
align="center"
direction="row"
>
<Input
value={search}
placeholder="Cari data..."
onChange={(event) => setSearch(event.currentTarget.value)}
leftSection={<IconSearch size={16} />}
rightSectionPointerEvents="all"
rightSection={
<CloseButton
aria-label="Clear input"
onClick={() => setSearch("")}
style={{ display: search ? undefined : "none" }}
/>
}
/>
<Text size="sm" c="gray.5" >
{`${5 * (page - 1) + 1} ${Math.min(totalRows, 5 * page)} of ${totalRows}`}
</Text>
<Pagination
total={totalPages}
value={page}
onChange={setPage}
withPages={false}
/>
</Flex>
</Flex> </Flex>
<Divider my={0} /> <Divider my={0} />
<Table> <Table>
@@ -110,7 +206,7 @@ function DetailDataHistori({
{data?.length > 0 ? ( {data?.length > 0 ? (
data?.map((item: any, index: number) => ( data?.map((item: any, index: number) => (
<Table.Tr key={index}> <Table.Tr key={index}>
<Table.Td>{item.noPengaduan}</Table.Td> <Table.Td w={"180"}>{item.noPengaduan}</Table.Td>
<Table.Td> <Table.Td>
{kategori == "pengaduan" ? item.title : item.category} {kategori == "pengaduan" ? item.title : item.category}
</Table.Td> </Table.Td>
@@ -121,11 +217,11 @@ function DetailDataHistori({
onClick={() => { onClick={() => {
kategori == "pengaduan" kategori == "pengaduan"
? navigate( ? navigate(
`/scr/dashboard/pengaduan/detail?id=${item.id}`, `/scr/dashboard/pengaduan/detail?id=${item.id}`,
) )
: navigate( : navigate(
`/scr/dashboard/pelayanan-surat/detail-pelayanan?id=${item.id}`, `/scr/dashboard/pelayanan-surat/detail-pelayanan?id=${item.id}`,
); );
}} }}
> >
Detail Detail
@@ -147,7 +243,33 @@ function DetailDataHistori({
); );
} }
function DetailWarga({ data }: { data: any }) { function DetailWarga({ id }: { id: string }) {
const [data, setData] = useState<any>(null);
async function getWarga() {
try {
const res = await apiFetch.api.warga.detail.get({
query: {
id: id,
category: "warga",
page: "1",
search: "",
},
});
setData(res.data);
} catch (error) {
console.error(error);
notification({
title: "Failed",
message: "Failed to get data warga",
type: "error",
});
}
}
useShallowEffect(() => {
getWarga();
}, []);
return ( return (
<Card <Card
radius="md" radius="md"

View File

@@ -97,68 +97,137 @@ const WargaRoute = new Elysia({
} }
}) })
.get("/detail", async ({ query }) => { .get("/detail", async ({ query }) => {
const { id } = query const { id, category, search, page } = query
const skip = !page ? 0 : (Number(page) - 1) * 5
const dataWarga = await prisma.warga.findUnique({ const dataWarga = await prisma.warga.findUnique({
where: { where: {
id id
} }
}) })
const dataPengaduan = await prisma.pengaduan.findMany({ if (!dataWarga)
orderBy: { return { success: false, message: "data warga tidak ditemukan", data: null, totalPages: 1, totalRows: 0 }
createdAt: "desc"
}, if (category == "warga") {
where: { return dataWarga
} else if (category == "pengaduan") {
const where: any = {
isActive: true, isActive: true,
idWarga: id idWarga: id,
}, OR: [
select: { {
id: true, title: {
status: true, contains: search ?? "",
noPengaduan: true, mode: "insensitive"
title: true },
},
{
noPengaduan: {
contains: search ?? "",
mode: "insensitive"
},
}
]
} }
})
const totalData = await prisma.pengaduan.count({
where
});
const dataPengaduan = await prisma.pengaduan.findMany({
skip,
take: 5,
orderBy: {
createdAt: "desc"
},
where,
select: {
id: true,
status: true,
noPengaduan: true,
title: true
}
})
const dataReturn = {
success: true,
message: "data pengaduan berhasil diambil",
data: dataPengaduan,
totalRows: totalData,
totalPages: Math.ceil(totalData / 5)
}
const dataPelayanan = await prisma.pelayananAjuan.findMany({ return dataReturn
orderBy: { } else if (category == "pelayanan") {
createdAt: "desc" const where: any = {
},
where: {
isActive: true, isActive: true,
idWarga: id idWarga: id,
}, OR: [
select: { {
id: true, CategoryPelayanan: {
noPengajuan: true, name: {
status: true, contains: search ?? "",
CategoryPelayanan: { mode: "insensitive"
select: { },
name: true },
},
{
noPengajuan: {
contains: search ?? "",
mode: "insensitive"
},
},
]
}
const totalData = await prisma.pelayananAjuan.count({
where
});
const dataPelayanan = await prisma.pelayananAjuan.findMany({
skip,
take: 5,
orderBy: {
createdAt: "desc"
},
where,
select: {
id: true,
noPengajuan: true,
status: true,
CategoryPelayanan: {
select: {
name: true
}
} }
} }
})
const dataPelayanFix = dataPelayanan.map((v: any) => ({
..._.omit(v, ["CategoryPelayanan"]),
id: v.id,
noPengaduan: v.noPengajuan,
status: v.status,
category: v.CategoryPelayanan.name
}))
const dataReturn = {
success: true,
message: "data pelayanan berhasil diambil",
data: dataPelayanFix,
totalRows: totalData,
totalPages: Math.ceil(totalData / 5)
} }
})
const dataPelayanFix = dataPelayanan.map((v: any) => ({ return dataReturn
..._.omit(v, ["CategoryPelayanan"]),
id: v.id,
noPengaduan: v.noPengajuan,
status: v.status,
category: v.CategoryPelayanan.name
}))
return {
warga: dataWarga,
pengaduan: dataPengaduan,
pelayanan: dataPelayanFix
} }
}, { }, {
query: t.Object({ query: t.Object({
id: t.String({ minLength: 1, error: "id harus diisi" }) id: t.String({ minLength: 1, error: "id harus diisi" }),
category: t.String({ minLength: 1, error: "kategori harus diisi" }),
page: t.String({ optional: true }),
search: t.String({ optional: true }),
}), }),
detail: { detail: {
summary: "Detail Warga", summary: "Detail Warga",