Integrasi API: Admin Voting

Add:
-  service/api-admin/api-admin-voting.ts

Fix:
- app/(application)/admin/voting/[id]/[status]/index.tsx
- app/(application)/admin/voting/[status]/status.tsx
- app/(application)/admin/voting/index.tsx

### No Issue
This commit is contained in:
2025-10-20 17:39:12 +08:00
parent 1fd9694ebf
commit 57285e5697
4 changed files with 210 additions and 94 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BadgeCustom,
@@ -14,14 +15,42 @@ import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { dateTimeView } from "@/utils/dateTimeView";
import { Entypo } from "@expo/vector-icons";
import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { List } from "react-native-paper";
export default function AdminVotingDetail() {
const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminVotingById({
id: id as string,
});
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const colorBadge = () => {
if (status === "publish") {
return MainColor.green;
@@ -37,63 +66,53 @@ export default function AdminVotingDetail() {
const listData = [
{
label: "Username",
value: "Bagas Banuna",
value: (data && data?.Author?.username) || "-",
},
{
label: "Judul",
value: `Judul Proyek: ${id}Lorem ipsum dolor sit amet consectetur adipisicing elit.`,
value: (data && data?.title) || "-",
},
{
label: "Status",
value: (
<BadgeCustom color={colorBadge()}>
{_.startCase(status as string)}
</BadgeCustom>
),
value:
data && data?.Voting_Status?.name ? (
<BadgeCustom color={colorBadge()}>
{_.startCase(data?.Voting_Status?.name)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Mulai Voting",
value: dayjs().format("DD/MM/YYYY"),
value:
(data && data?.awalVote && dateTimeView({ date: data?.awalVote })) ||
"-",
},
{
label: "Voting Berakhir",
value: dayjs().format("DD/MM/YYYY"),
value:
(data && data?.akhirVote && dateTimeView({ date: data?.akhirVote })) ||
"-",
},
{
label: "Deskripsi",
value: "Lorem ipsum dolor sit amet consectetur adipisicing elit.",
value: (data && data?.deskripsi) || "-",
},
{
label: "Daftar Pilhan",
value: (
<>
<List.Item
title={<TextCustom>Pilihan 1</TextCustom>}
left={(props) => (
<List.Icon {...props} icon="circle" color={MainColor.yellow} />
)}
/>
<List.Item
title={<TextCustom>Pilihan 2</TextCustom>}
left={(props) => (
<List.Icon {...props} icon="circle" color={MainColor.yellow} />
)}
/>
<List.Item
title={<TextCustom>Pilihan 3</TextCustom>}
left={(props) => (
<List.Icon {...props} icon="circle" color={MainColor.yellow} />
)}
/>
<List.Item
title={<TextCustom>Pilihan 4</TextCustom>}
left={(props) => (
<List.Icon {...props} icon="circle" color={MainColor.yellow} />
)}
/>
</>
),
label: "Daftar Pilihan",
value:
data && data?.Voting_DaftarNamaVote
? data?.Voting_DaftarNamaVote?.map((item: any, i: number) => (
<List.Item
key={i}
title={<TextCustom>{item?.value}</TextCustom>}
left={(props) => (
<Entypo name="chevron-right" color={MainColor.yellow} />
)}
/>
))
: "-",
},
];
@@ -121,17 +140,23 @@ export default function AdminVotingDetail() {
</TextCustom>
<Spacing />
<Grid>
{Array.from({length: 4}).map((_, index) => (
<Grid.Col key={index} span={3} style={{ paddingRight: 3, paddingLeft: 3 }}>
<StackCustom gap={"sm"}>
<CircleContainer value={index % 3 * 3} style={{ alignSelf: "center" }} />
<TextCustom size="small" align="center">
Pilihan {index + 1}
</TextCustom>
</StackCustom>
</Grid.Col>
))}
{Array.from({ length: 4 }).map((_, index) => (
<Grid.Col
key={index}
span={3}
style={{ paddingRight: 3, paddingLeft: 3 }}
>
<StackCustom gap={"sm"}>
<CircleContainer
value={(index % 3) * 3}
style={{ alignSelf: "center" }}
/>
<TextCustom size="small" align="center">
Pilihan {index + 1}
</TextCustom>
</StackCustom>
</Grid.Col>
))}
</Grid>
</BaseBox>
)}

View File

@@ -1,27 +1,66 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BaseBox,
SearchInput,
Spacing,
TextCustom,
ViewWrapper,
ActionIcon,
BaseBox,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
import { Octicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
import { Divider } from "react-native-paper";
export default function AdminVotingStatus() {
const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState<string>("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [status, search])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminVoting({
category: status as "publish" | "review" | "reject" as any,
search,
});
// console.log("[LIST BY STATUS]", JSON.stringify(response, null, 2));
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari"
value={search}
onChangeText={setSearch}
/>
);
return (
@@ -32,18 +71,17 @@ export default function AdminVotingStatus() {
rightComponent={rightComponent}
/>
<BaseBox>
<StackCustom gap={"sm"}>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Voting"
/>
<Spacing />
<Divider />
{Array.from({ length: 10 }).map((_, index) => (
{loadList ? <LoaderCustom/> : _.isEmpty(list) ? <TextCustom align="center" bold color="gray">Belum ada data</TextCustom> : list.map((item: any, i: number) => (
<AdminTableValue
key={index}
key={i}
value1={
<ActionIcon
icon={
@@ -54,22 +92,19 @@ export default function AdminVotingStatus() {
/>
}
onPress={() => {
router.push(`/admin/voting/${index}/${status}`);
router.push(`/admin/voting/${item.id}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>Username username</TextCustom>}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae mollitia.
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))}
</BaseBox>
</StackCustom>
</ViewWrapper>
</>
);

View File

@@ -8,8 +8,56 @@ import {
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet";
import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
import { useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
export default function AdminVoting() {
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
const response = await apiAdminVoting({
category: "dashboard",
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Publish",
value: (data && data?.publish) || 0,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: (data && data?.review) || 0,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: (data && data?.reject) || 0,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Riwayat",
value: (data && data?.history) || 0,
icon: <IconArchive size={25} color={MainColor.white_gray} />,
},
];
return (
<>
<ViewWrapper>
@@ -24,26 +72,3 @@ export default function AdminVoting() {
</>
);
}
const listData = [
{
label: "Publish",
value: 4,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: 7,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: 5,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Riwayat",
value: 5,
icon: <IconArchive size={25} color={MainColor.white_gray} />,
},
];

View File

@@ -0,0 +1,31 @@
import { apiConfig } from "../api-config";
export async function apiAdminVoting({
category,
search,
}: {
category: "dashboard" | "history" | "publish" | "review" | "report";
search?: string;
}) {
try {
const response = await apiConfig.get(
`/mobile/admin/voting?category=${category}&search=${search}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminVotingById({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.get(`/mobile/admin/voting/${id}`);
return response.data;
} catch (error) {
throw error;
}
}