Compare commits

...

2 Commits

Author SHA1 Message Date
333b1d2512 Voting
Fix: Semua tampilan sudah terintegrasi API

### No Issue
2025-09-19 17:51:08 +08:00
391430de46 Voting
Fix:
- Integrasi API pada (tabs) status & detail
- Integrasi API beranda & detail
- Integrasi API pada voting

### No Issue
2025-09-18 17:35:18 +08:00
20 changed files with 1213 additions and 232 deletions

View File

@@ -1,15 +1,57 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
LoaderCustom,
TextCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection"; import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
export default function VotingContribution() { export default function VotingContribution() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
category: "contribution",
authorId: user?.id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 5 }).map((_, index) => ( {loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada kontribusi</TextCustom>
) : listData.map((item: any, index: number) => (
<Voting_BoxPublishSection <Voting_BoxPublishSection
data={item}
key={index} key={index}
href={`/voting/${index}/contribution`} href={`/voting/${item.id}/contribution`}
/> />
))} ))}
</ViewWrapper> </ViewWrapper>

View File

@@ -1,11 +1,44 @@
import { ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import TabsTwoButtonCustom from "@/components/_ShareComponent/TabsTwoHeaderCustom"; import TabsTwoButtonCustom from "@/components/_ShareComponent/TabsTwoHeaderCustom";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection"; import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { useState } from "react"; import { useAuth } from "@/hooks/use-auth";
import { useCallback, useState } from "react";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
export default function VotingHistory() { export default function VotingHistory() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("all"); const [activeCategory, setActiveCategory] = useState<string | null>("all");
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
authorId: user?.id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item); setActiveCategory(item);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -25,13 +58,20 @@ export default function VotingHistory() {
/> />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<Voting_BoxPublishSection <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={activeCategory as any} <TextCustom align="center">Tidak ada riwayat</TextCustom>
href={`/voting/${index}/history`} ) : (
/> listData.map((item: any, index: number) => (
))} <Voting_BoxPublishSection
key={index}
id={item.id}
data={item}
href={`/voting/${item.id}/history`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,68 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
FloatingButton, FloatingButton,
LoaderCustom,
SearchInput, SearchInput,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection"; import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { router } from "expo-router"; import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function VotingBeranda() { export default function VotingBeranda() {
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [search])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
search,
category: "beranda",
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
floatingButton={ floatingButton={
<FloatingButton onPress={() => router.push("/voting/create")} /> <FloatingButton onPress={() => router.push("/voting/create")} />
} }
headerComponent={<SearchInput placeholder="Cari voting" />} headerComponent={
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
}
> >
{Array.from({ length: 5 }).map((_, index) => ( {loadingGetData ? (
<Voting_BoxPublishSection key={index} href={`/voting/${index}`} /> <LoaderCustom />
))} ) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
data={item}
key={index}
href={`/voting/${item.id}`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -12,7 +12,6 @@ import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiVotingGetByStatus } from "@/service/api-client/api-voting"; import { apiVotingGetByStatus } from "@/service/api-client/api-voting";
import { dateTimeView } from "@/utils/dateTimeView"; import { dateTimeView } from "@/utils/dateTimeView";
import dayjs from "dayjs";
import { useFocusEffect } from "expo-router"; import { useFocusEffect } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@@ -20,7 +19,6 @@ import { useCallback, useState } from "react";
export default function VotingStatus() { export default function VotingStatus() {
const { user } = useAuth(); const { user } = useAuth();
const id = user?.id || ""; const id = user?.id || "";
console.log("ID >> ", id);
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
@@ -41,7 +39,6 @@ export default function VotingStatus() {
id: id as string, id: id as string,
status: activeCategory!, status: activeCategory!,
}); });
console.log("[RES LIST STATUS]", JSON.stringify(response.data, null, 2));
setListData(response.data); setListData(response.data);
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@@ -2,17 +2,24 @@
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon"; import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection"; import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection";
import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection"; import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection";
import { apiVotingGetOne } from "@/service/api-client/api-voting"; import {
apiVotingGetOne,
apiVotingUpdateData,
} from "@/service/api-client/api-voting";
import { import {
router, router,
Stack, Stack,
@@ -20,15 +27,14 @@ import {
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useAuth } from "@/hooks/use-auth"; import Toast from "react-native-toast-message";
export default function VotingDetailStatus() { export default function VotingDetailStatus() {
const {user} = useAuth()
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
console.log("ID >> ", id);
console.log("STATUS >> ", status);
const [openDrawerDraft, setOpenDrawerDraft] = useState(false); const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [loadingGetData, setLoadingGetData] = useState(false);
const [data, setData] = useState<any>(null); const [data, setData] = useState<any>(null);
@@ -40,11 +46,16 @@ export default function VotingDetailStatus() {
const onLoadData = async () => { const onLoadData = async () => {
try { try {
setLoadingGetData(true);
const response = await apiVotingGetOne({ id: id as string }); const response = await apiVotingGetOne({ id: id as string });
console.log("Response", JSON.stringify(response.data, null, 2));
setData(response.data); if (response.success) {
setData(response.data);
}
} catch (error) { } catch (error) {
console.log(error); console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
} }
}; };
@@ -61,9 +72,24 @@ export default function VotingDetailStatus() {
message: "Apakah Anda yakin ingin mengarsipkan voting ini?", message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: () => { onPressRight: async () => {
console.log("Hapus"); try {
router.back(); const response = await apiVotingUpdateData({
id: id as string,
data: data.isArsip ? false : true,
category: "archive",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
}
}, },
}); });
} }
@@ -75,7 +101,7 @@ export default function VotingDetailStatus() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: `Detail ${status}`, title: `Detail`,
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => headerRight: () =>
status === "draft" ? ( status === "draft" ? (
@@ -86,9 +112,37 @@ export default function VotingDetailStatus() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Voting_BoxDetailSection data={data as any}/> {loadingGetData ? (
<Voting_ButtonStatusSection id={id as string} status={status as string} /> <LoaderCustom />
<Spacing /> ) : (
<>
{status === "publish" && (
<BaseBox>
<TextCustom bold>
Status:{" "}
<TextCustom color={data?.isArsip ? "red" : "green"}>
{data?.isArsip ? "Arsip" : "Publish"}
</TextCustom>
</TextCustom>
</BaseBox>
)}
<Spacing height={0} />
<Voting_BoxDetailSection data={data as any} />
{status === "publish" ? (
<Voting_BoxDetailHasilVotingSection
listData={data?.Voting_DaftarNamaVote}
/>
) : (
<Voting_ButtonStatusSection
isLoading={isLoading}
onSetLoading={setIsLoading}
id={id as string}
status={status as string}
/>
)}
<Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
{/* ========= Draft Drawer ========= */} {/* ========= Draft Drawer ========= */}

View File

@@ -1,22 +1,76 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
MenuDrawerDynamicGrid, LoaderCustom,
Spacing, MenuDrawerDynamicGrid,
ViewWrapper, Spacing,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconContribution } from "@/components/_Icon"; import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
import { Voting_BoxDetailContributionSection } from "@/screens/Voting/BoxDetailContribution"; import { Voting_BoxDetailContributionSection } from "@/screens/Voting/BoxDetailContribution";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import {
apiVotingContribution,
apiVotingGetOne,
} from "@/service/api-client/api-voting";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useEffect, useState } from "react";
export default function VotingDetailContribution() { export default function VotingDetailContribution() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
const [loadingGetData, setLoadingGetData] = useState(false);
const [nameChoice, setNameChoice] = useState("");
useEffect(() => {
handlerLoadData();
}, [id, user?.id]);
async function handlerLoadData() {
try {
setLoadingGetData(true);
await onLoadData();
await onLoadCheckContribution();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
}
const onLoadData = async () => {
try {
const response = await apiVotingGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadCheckContribution = async () => {
try {
const response = await apiVotingContribution({
id: id as string,
authorId: user?.id as string,
category: "checked",
});
if (response.success) {
setNameChoice(response.data.nameChoice);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressPublish = (item: IMenuDrawerItem) => { const handlePressPublish = (item: IMenuDrawerItem) => {
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -36,11 +90,27 @@ export default function VotingDetailContribution() {
/> />
<ViewWrapper> <ViewWrapper>
<Voting_BoxDetailContributionSection {loadingGetData ? (
headerAvatar={<AvatarUsernameAndOtherComponent />} <LoaderCustom />
/> ) : (
<Voting_BoxDetailHasilVotingSection /> <>
<Spacing /> <Voting_BoxDetailContributionSection
data={data}
nameChoice={nameChoice}
headerAvatar={
<AvatarUsernameAndOtherComponent
avatar={data?.Author?.Profile?.imageId || ""}
name={data?.Author?.username || "Username"}
avatarHref={`/profile/${data?.Author?.Profile?.id}`}
/>
}
/>
<Voting_BoxDetailHasilVotingSection
listData={data?.Voting_DaftarNamaVote}
/>
<Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
{/* ========= Publish Drawer ========= */} {/* ========= Publish Drawer ========= */}

View File

@@ -1,29 +1,184 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
Grid, CenterCustom,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import {
apiVotingGetOne,
apiVotingUpdateData,
} from "@/service/api-client/api-voting";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { TouchableOpacity } from "react-native"; import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
interface IEditData {
title?: string;
deskripsi?: string;
awalVote?: string;
akhirVote?: string;
Voting_DaftarNamaVote?: [
{
value?: string;
}
];
}
export default function VotingEdit() { export default function VotingEdit() {
const { id } = useLocalSearchParams();
const [loadingGetData, setLoadingGetData] = useState(false);
const [data, setData] = useState<IEditData>();
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetOne({ id: id as string });
if (response.success) {
const data = response.data;
setData({
title: data.title,
deskripsi: data.deskripsi,
awalVote: data.awalVote,
akhirVote: data.akhirVote,
Voting_DaftarNamaVote: data.Voting_DaftarNamaVote,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const validateDateRange = ({
selectedStratDate,
selectedEndDate,
}: {
selectedStratDate: string | Date;
selectedEndDate: string | Date;
}): { isValid: boolean; error?: string } => {
const startDate = new Date(selectedStratDate);
const endDate = new Date(selectedEndDate);
// Cek apakah tanggal valid
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
return {
isValid: false,
error: "Invalid date provided",
};
}
if (startDate >= endDate) {
return {
isValid: false,
error: "Ubah tanggal berakhirnya event",
};
}
return {
isValid: true,
error: undefined,
};
};
const validateForm = async () => {
if (!data?.title || !data?.deskripsi) {
Toast.show({
type: "info",
text1: "Lengkapi semua data",
});
return false;
}
if (data?.Voting_DaftarNamaVote?.some((item: any) => item.value === "")) {
Toast.show({
type: "info",
text1: "Isi semua data pilihan",
});
return false;
}
const startDate = new Date(data?.awalVote as any);
const endDate = new Date(data?.akhirVote as any);
if (startDate >= endDate) {
Toast.show({
type: "info",
text1: "Ubah tanggal berakhirnya event",
});
return false;
}
return true;
};
const handlerUpdateSubmit = async () => {
const isValid = await validateForm();
if (!isValid) return;
try {
setIsLoading(true);
const newData = {
...data,
awalVote: new Date(data?.awalVote as any).toISOString(),
akhirVote: new Date(data?.akhirVote as any).toISOString(),
listVote: data?.Voting_DaftarNamaVote?.map((item: any) => item.value),
};
const response = await apiVotingUpdateData({
id: id as string,
data: newData,
category: "edit",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
return router.back();
} else {
Toast.show({
type: "error",
text1: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => isLoading={isLoading}
router.back() onPress={() => handlerUpdateSubmit()}
}
> >
Update Update
</ButtonCustom> </ButtonCustom>
@@ -34,45 +189,144 @@ export default function VotingEdit() {
return ( return (
<ViewWrapper footerComponent={buttonSubmit()}> <ViewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}> {loadingGetData ? (
<TextInputCustom <LoaderCustom />
label="Judul Voting" ) : (
placeholder="MasukanJudul Voting" <StackCustom gap={"xs"}>
required <TextInputCustom
/> label="Judul Voting"
<TextAreaCustom placeholder="MasukanJudul Voting"
label="Deskripsi" required
placeholder="Masukan Deskripsi" value={data?.title}
required onChangeText={(text) => setData({ ...data, title: text })}
showCount />
maxLength={1000} <TextAreaCustom
/> label="Deskripsi"
<DateTimePickerCustom label="Mulai Voting" required /> placeholder="Masukan Deskripsi"
<DateTimePickerCustom label="Voting Berakhir" required /> required
showCount
maxLength={1000}
value={data?.deskripsi}
onChangeText={(text) => setData({ ...data, deskripsi: text })}
/>
<Grid> <Spacing />
<Grid.Col span={10}>
<DateTimePickerCustom
minimumDate={new Date(Date.now())}
label="Mulai Voting"
required
value={new Date(data?.awalVote as any)}
onChange={(date: any) => {
setData({ ...data, awalVote: date });
}}
/>
<StackCustom gap={0}>
<DateTimePickerCustom
minimumDate={new Date(data?.awalVote as any)}
label="Voting Berakhir"
required
value={new Date(data?.akhirVote as any)}
onChange={(date: any) => {
setData({ ...data, akhirVote: date });
}}
/>
{validateDateRange({
selectedStratDate: data?.awalVote as any,
selectedEndDate: data?.akhirVote as any,
}).isValid ? (
<TextCustom style={{ color: "green" }}>
{
validateDateRange({
selectedStratDate: data?.awalVote as any,
selectedEndDate: data?.akhirVote as any,
}).error
}
</TextCustom>
) : (
<TextCustom style={{ color: "red" }}>
{
validateDateRange({
selectedStratDate: data?.awalVote as any,
selectedEndDate: data?.akhirVote as any,
}).error
}
</TextCustom>
)}
<Spacing />
</StackCustom>
{data?.Voting_DaftarNamaVote?.map((item: any, index: number) => (
<TextInputCustom <TextInputCustom
key={index}
label="Pilihan" label="Pilihan"
placeholder="Masukan Pilihan" placeholder="Masukan Pilihan"
required required
value={item.value}
onChangeText={(value: any) =>
setData({
...(data as any),
Voting_DaftarNamaVote: data?.Voting_DaftarNamaVote?.map(
(item: any, i: any) =>
i === index ? { ...item, value } : item
),
})
}
/> />
</Grid.Col> ))}
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}> <CenterCustom>
Tambah Pilihan <View
</ButtonCenteredOnly> style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
<Spacing /> >
</StackCustom> <ActionIcon
disabled={(data as any)?.Voting_DaftarNamaVote?.length >= 4}
onPress={() => {
setData({
...(data as any),
Voting_DaftarNamaVote: [
...(data as any)?.Voting_DaftarNamaVote,
{ value: "" },
],
});
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={
((data as any)?.Voting_DaftarNamaVote?.length as any) <= 2
}
onPress={() => {
const list = _.clone((data as any)?.Voting_DaftarNamaVote);
list.pop();
setData({
...(data as any),
Voting_DaftarNamaVote: list,
});
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,78 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
MenuDrawerDynamicGrid, LoaderCustom,
Spacing, MenuDrawerDynamicGrid,
ViewWrapper, Spacing,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconContribution } from "@/components/_Icon"; import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailHistorySection } from "@/screens/Voting/BoxDetailHistorySection"; import { Voting_BoxDetailHistorySection } from "@/screens/Voting/BoxDetailHistorySection";
import {
apiVotingContribution,
apiVotingGetOne,
} from "@/service/api-client/api-voting";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useEffect, useState } from "react";
export default function VotingDetailHistory() { export default function VotingDetailHistory() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
const [loadingGetData, setLoadingGetData] = useState(false);
const [nameChoice, setNameChoice] = useState("");
useEffect(() => {
handlerLoadData();
}, [id, user?.id]);
async function handlerLoadData() {
try {
setLoadingGetData(true);
await onLoadData();
await onLoadCheckContribution();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
}
const onLoadData = async () => {
try {
const response = await apiVotingGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadCheckContribution = async () => {
try {
const response = await apiVotingContribution({
id: id as string,
authorId: user?.id as string,
category: "checked",
});
if (response.success) {
setNameChoice(response.data.nameChoice);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressPublish = (item: IMenuDrawerItem) => { const handlePressPublish = (item: IMenuDrawerItem) => {
router.navigate(item.path as any); router.navigate(item.path as any);
setOpenDrawerPublish(false); setOpenDrawerPublish(false);
@@ -35,11 +90,27 @@ export default function VotingDetailHistory() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Voting_BoxDetailHistorySection {loadingGetData ? (
headerAvatar={<AvatarUsernameAndOtherComponent />} <LoaderCustom />
/> ) : (
<Voting_BoxDetailHasilVotingSection /> <>
<Spacing /> <Voting_BoxDetailHistorySection
data={data}
nameChoice={nameChoice}
headerAvatar={
<AvatarUsernameAndOtherComponent
avatar={data?.Author?.Profile?.imageId || ""}
name={data?.Author?.username || "Username"}
avatarHref={`/profile/${data?.Author?.Profile?.id}`}
/>
}
/>
<Voting_BoxDetailHasilVotingSection
listData={data?.Voting_DaftarNamaVote}
/>
<Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
{/* ========= Publish Drawer ========= */} {/* ========= Publish Drawer ========= */}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
@@ -5,20 +6,87 @@ import {
DotButton, DotButton,
DrawerCustom, DrawerCustom,
InformationBox, InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
StackCustom, StackCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconArchive, IconContribution } from "@/components/_Icon"; import { IconArchive, IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection"; import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import React, { useState } from "react"; apiVotingContribution,
apiVotingGetOne,
apiVotingUpdateData,
} from "@/service/api-client/api-voting";
import { today } from "@/utils/dateTimeView";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import React, { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function VotingDetail() { export default function VotingDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
const [loadingGetData, setLoadingGetData] = useState(false);
const [isContribution, setIsContribution] = useState(false);
const [nameChoice, setNameChoice] = useState("");
useFocusEffect(
useCallback(() => {
handlerLoadData();
}, [id, user?.id])
);
async function handlerLoadData() {
try {
setLoadingGetData(true);
await onLoadData();
await onLoadCheckContribution();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
}
const onLoadData = async () => {
try {
const response = await apiVotingGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadCheckContribution = async () => {
try {
const response = await apiVotingContribution({
id: id as string,
authorId: user?.id as string,
category: "checked",
});
if (response.success) {
setIsContribution(response.data.isContribution);
setNameChoice(response.data.nameChoice);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressPublish = (item: IMenuDrawerItem) => { const handlePressPublish = (item: IMenuDrawerItem) => {
if (item.path === "") { if (item.path === "") {
AlertDefaultSystem({ AlertDefaultSystem({
@@ -26,9 +94,24 @@ export default function VotingDetail() {
message: "Apakah Anda yakin ingin mengarsipkan voting ini?", message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: () => { onPressRight: async () => {
console.log("Hapus"); try {
router.back(); const response = await apiVotingUpdateData({
id: id as string,
data: data.isArsip ? false : true,
category: "archive",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
}
}, },
}); });
} }
@@ -49,15 +132,32 @@ export default function VotingDetail() {
/> />
<ViewWrapper> <ViewWrapper>
<StackCustom> {loadingGetData ? (
<InformationBox text="Untuk sementara voting ini belum di buka. Voting akan dimulai sesuai dengan tanggal awal pemilihan, dan akan ditutup sesuai dengan tanggal akhir pemilihan." /> <LoaderCustom />
) : (
<StackCustom gap={"xs"}>
{today.getDate() < new Date(data?.awalVote).getDate() && (
<InformationBox text="Untuk sementara voting tidak dapat dilakukan. Voting dapat dimulai sesuai dengan tanggal awal pemilihan, dan akan ditutup sesuai dengan tanggal akhir pemilihan." />
)}
<Voting_BoxDetailPublishSection
data={data}
userId={user?.id as string}
isContribution={isContribution}
nameChoice={nameChoice}
headerAvatar={
<AvatarUsernameAndOtherComponent
avatar={data?.Author?.Profile?.imageId || ""}
name={data?.Author?.username || "Username"}
avatarHref={`/profile/${data?.Author?.Profile?.id}`}
/>
}
/>
<Voting_BoxDetailPublishSection <Voting_BoxDetailHasilVotingSection
headerAvatar={<AvatarUsernameAndOtherComponent />} listData={data?.Voting_DaftarNamaVote}
/> />
</StackCustom>
<Voting_BoxDetailHasilVotingSection /> )}
</StackCustom>
</ViewWrapper> </ViewWrapper>
{/* ========= Publish Drawer ========= */} {/* ========= Publish Drawer ========= */}
@@ -67,18 +167,28 @@ export default function VotingDetail() {
height={"auto"} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={[ data={
{ user?.id === data?.Author?.id
icon: <IconContribution />, ? [
label: "Daftar Kontributor", {
path: `/voting/${id}/list-of-contributor`, icon: <IconContribution />,
}, label: "Daftar Kontributor",
{ path: `/voting/${id}/list-of-contributor`,
icon: <IconArchive />, },
label: "Update Arsip", {
path: "" as any, icon: <IconArchive />,
}, label: "Update Arsip",
]} path: "" as any,
},
]
: [
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
]
}
onPressItem={handlePressPublish as any} onPressItem={handlePressPublish as any}
/> />
</DrawerCustom> </DrawerCustom>

View File

@@ -1,26 +1,69 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { apiVotingContribution } from "@/service/api-client/api-voting";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Voting_ListOfContributor() { export default function Voting_ListOfContributor() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any>([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setIsLoadData(true);
const response = await apiVotingContribution({
id: id as string,
authorId: "",
category: "list",
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadData ? (
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}> <LoaderCustom />
<AvatarUsernameAndOtherComponent ) : _.isEmpty(listData) ? (
rightComponent={ <TextCustom align="center">Tidak ada kontributor</TextCustom>
<BadgeCustom ) : (
style={{alignSelf: "flex-end" }} listData.map((item: any, index: number) => (
> <BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
Pilihan {index + 1} <AvatarUsernameAndOtherComponent
</BadgeCustom> avatar={item?.Author?.Profile?.imageId || ""}
} name={item?.Author?.username || "Username"}
/> avatarHref={`/profile/${item?.Author?.Profile?.id}`}
</BaseBox> rightComponent={
))} <BadgeCustom style={{ alignSelf: "flex-end" }}>
{item?.Voting_DaftarNamaVote?.value}
</BadgeCustom>
}
/>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -142,26 +142,6 @@ export default function VotingCreate() {
} }
/> />
{/* <Grid>
<Grid.Col span={10}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid> */}
{/* <ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly> */}
{listVote.map((item, index) => ( {listVote.map((item, index) => (
<TextInputCustom <TextInputCustom

View File

@@ -9,23 +9,32 @@ import { GStyles } from "@/styles/global-styles";
import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection"; import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection";
export function Voting_BoxDetailContributionSection({ export function Voting_BoxDetailContributionSection({
data,
headerAvatar, headerAvatar,
nameChoice,
}: { }: {
data: any;
headerAvatar?: React.ReactNode; headerAvatar?: React.ReactNode;
nameChoice?: string;
}) { }) {
return ( return (
<> <>
<BoxWithHeaderSection> <BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />} {headerAvatar && (
<>
{headerAvatar}
<Spacing />
</>
)}
<StackCustom gap={"lg"}> <StackCustom gap={"lg"}>
<Voting_ComponentDetailDataSection /> <Voting_ComponentDetailDataSection data={data} />
<StackCustom gap={"xs"}> <StackCustom gap={"sm"}>
<TextCustom bold size="small" align="center"> <TextCustom bold size="small" align="center">
Pilihan Anda Pilihan Anda
</TextCustom> </TextCustom>
<BadgeCustom style={[GStyles.alignSelfCenter]}> <BadgeCustom variant="light" size="lg" style={[GStyles.alignSelfCenter]}>
Pilihan 1 {nameChoice || "-"}
</BadgeCustom> </BadgeCustom>
</StackCustom> </StackCustom>
</StackCustom> </StackCustom>

View File

@@ -6,7 +6,11 @@ import {
CircleContainer, CircleContainer,
} from "@/components"; } from "@/components";
export default function Voting_BoxDetailHasilVotingSection() { export default function Voting_BoxDetailHasilVotingSection({
listData,
}: {
listData: any[];
}) {
return ( return (
<> <>
<BaseBox> <BaseBox>
@@ -16,10 +20,12 @@ export default function Voting_BoxDetailHasilVotingSection() {
</TextCustom> </TextCustom>
<Grid> <Grid>
{Array.from({ length: 4 }).map((_, i) => ( {listData?.map((item: any, i: number) => (
<Grid.Col span={3} style={{ alignItems: "center" }} key={i}> <Grid.Col span={12 / listData?.length} style={{ alignItems: "center" }} key={i}>
<CircleContainer value={9 % (i + 4)} /> <StackCustom>
<TextCustom size="small">Pilihan {i + 1}</TextCustom> <CircleContainer value={item?.jumlah} />
<TextCustom align="center" size="small">{item?.value}</TextCustom>
</StackCustom>
</Grid.Col> </Grid.Col>
))} ))}
</Grid> </Grid>

View File

@@ -1,21 +1,36 @@
import { import {
BadgeCustom,
BoxWithHeaderSection, BoxWithHeaderSection,
Spacing, Spacing,
StackCustom StackCustom,
TextCustom
} from "@/components"; } from "@/components";
import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection"; import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection";
import { GStyles } from "@/styles/global-styles";
export function Voting_BoxDetailHistorySection({ export function Voting_BoxDetailHistorySection({
headerAvatar, headerAvatar,
data,
nameChoice,
}: { }: {
headerAvatar?: React.ReactNode; headerAvatar?: React.ReactNode;
data: any;
nameChoice: string;
}) { }) {
return ( return (
<> <>
<BoxWithHeaderSection> <BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />} {headerAvatar ? headerAvatar : <Spacing />}
<StackCustom> <StackCustom>
<Voting_ComponentDetailDataSection /> <Voting_ComponentDetailDataSection data={data} />
<StackCustom gap={"sm"}>
<TextCustom bold size="small" align="center">
Pilihan Anda
</TextCustom>
<BadgeCustom variant="light" size="lg" style={[GStyles.alignSelfCenter]}>
{nameChoice || "-"}
</BadgeCustom>
</StackCustom>
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
</> </>

View File

@@ -1,4 +1,6 @@
import { import {
AlertDefaultSystem,
BadgeCustom,
BoxWithHeaderSection, BoxWithHeaderSection,
ButtonCustom, ButtonCustom,
Spacing, Spacing,
@@ -6,40 +8,109 @@ import {
TextCustom TextCustom
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { apiVotingVote } from "@/service/api-client/api-voting";
import { today } from "@/utils/dateTimeView";
import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection"; import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection";
export function Voting_BoxDetailPublishSection({ export function Voting_BoxDetailPublishSection({
headerAvatar, headerAvatar,
data,
userId,
isContribution,
nameChoice,
}: { }: {
headerAvatar?: React.ReactNode; headerAvatar?: React.ReactNode;
data?: any;
userId: string;
isContribution?: boolean;
nameChoice?: string;
}) { }) {
const [value, setValue] = useState<any | number>(""); const [value, setValue] = useState<any | number>("");
const handlerSubmitVote = async () => {
const newData = {
chooseId: value,
userId: userId,
};
try {
const response = await apiVotingVote({
id: data?.id,
data: newData,
});
if (response.success) {
router.push(`/voting/${data?.id}/list-of-contributor`);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<BoxWithHeaderSection> <BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />} {headerAvatar && (
<>
{headerAvatar}
<Spacing />
</>
)}
<StackCustom gap={"lg"}> <StackCustom gap={"lg"}>
<Voting_ComponentDetailDataSection /> <Voting_ComponentDetailDataSection data={data} />
<View> {isContribution ? (
<TextCustom bold size="small"> <StackCustom gap={"sm"}>
Pilihan : <TextCustom align="center" size="small" bold>
</TextCustom> Pilihan Anda :
<RadioGroup value={value} onChange={setValue}> </TextCustom>
{Array.from({ length: 4 }).map((_, i) => ( <View style={{ alignSelf: "center" }}>
<View key={i}> <BadgeCustom variant="light" size="lg">
<RadioCustom {nameChoice || "-"}
label={`Pilihan ${i + 1}`} </BadgeCustom>
value={`Pilihan ${i + 1}`} </View>
/> </StackCustom>
</View> ) : (
))} <>
</RadioGroup> <StackCustom>
</View> <TextCustom bold size="small">
Pilihan :
</TextCustom>
<RadioGroup value={value} onChange={setValue}>
{data?.Voting_DaftarNamaVote?.map((item: any, i: number) => (
<View key={i}>
<RadioCustom
disabled={
today.getDate() < new Date(data?.awalVote).getDate()
}
label={item?.value}
value={item?.id}
/>
</View>
))}
</RadioGroup>
</StackCustom>
<ButtonCustom onPress={() => console.log("vote")}>Vote</ButtonCustom> <ButtonCustom
disabled={value === ""}
onPress={() => {
AlertDefaultSystem({
title: "Anda melaukan voting",
message: "Yakin dengan pilihan anda ini ?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => handlerSubmitVote(),
});
}}
>
Vote
</ButtonCustom>
</>
)}
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
</> </>

View File

@@ -15,37 +15,43 @@ export default function Voting_BoxPublishSection({
href, href,
id, id,
bottomComponent, bottomComponent,
data,
}: { }: {
href?: Href href?: Href;
id?: string id?: string;
bottomComponent?: React.ReactNode; bottomComponent?: React.ReactNode;
data?: any;
}) { }) {
return ( return (
<> <>
<BoxWithHeaderSection href={href}> <BoxWithHeaderSection href={href}>
<AvatarUsernameAndOtherComponent avatarHref="/profile/1" /> <AvatarUsernameAndOtherComponent
<Spacing /> avatar={data?.Author?.Profile?.imageId || ""}
<StackCustom> name={data?.Author?.username || "Username"}
avatarHref="/profile/1"
/>
<Spacing height={0} />
<StackCustom gap={"lg"}>
<TextCustom align="center" bold truncate size="large"> <TextCustom align="center" bold truncate size="large">
Voting Title {id} {data?.title || "-"}
</TextCustom> </TextCustom>
<BadgeCustom <BadgeCustom
style={{ width: "70%", alignSelf: "center" }} style={{ width: "70%", alignSelf: "center" }}
variant="light" variant="light"
> >
{dayjs().format("DD/MM/YYYY")} -{" "} {dayjs(data?.awalVote).format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")} {dayjs(data?.akhirVote).format("DD/MM/YYYY")}
</BadgeCustom> </BadgeCustom>
<Grid> {/* <Grid>
{Array.from({ length: 4 }).map((_, i) => ( {Array.from({ length: 4 }).map((_, i) => (
<Grid.Col span={3} style={{ alignItems: "center" }} key={i}> <Grid.Col span={3} style={{ alignItems: "center" }} key={i}>
<CircleContainer value={9 % (i + 4)} /> <CircleContainer value={9 % (i + 4)} />
<TextCustom size="small">Pilihan {i + 1}</TextCustom> <TextCustom size="small">Pilihan {i + 1}</TextCustom>
</Grid.Col> </Grid.Col>
))} ))}
</Grid> </Grid> */}
{bottomComponent} {bottomComponent}
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>

View File

@@ -1,14 +1,21 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { apiVotingUpdateStatus } from "@/service/api-client/api-voting"; import {
apiVotingDelete,
apiVotingUpdateStatus,
} from "@/service/api-client/api-voting";
import { router } from "expo-router"; import { router } from "expo-router";
import { View } from "react-native"; import Toast from "react-native-toast-message";
export default function Voting_ButtonStatusSection({ export default function Voting_ButtonStatusSection({
id, id,
status, status,
isLoading,
onSetLoading,
}: { }: {
id: string; id: string;
status: string; status: string;
isLoading: boolean;
onSetLoading: (value: boolean) => void;
}) { }) {
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
@@ -16,15 +23,33 @@ export default function Voting_ButtonStatusSection({
message: "Apakah Anda yakin ingin batalkan review ini?", message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: async() => { onPressRight: async () => {
// console.log("Hapus"); try {
// router.back(); onSetLoading(true);
const response = await apiVotingUpdateStatus({ const response = await apiVotingUpdateStatus({
id: id as string, id: id as string,
status: "draft", status: "draft",
}) });
console.log("[RES BATALKAN REVIEW]", JSON.stringify(response, null, 2));
// router.back(); if (response?.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
onSetLoading(false);
}
}, },
}); });
}; };
@@ -35,9 +60,33 @@ export default function Voting_ButtonStatusSection({
message: "Apakah Anda yakin ingin ajukan review ini?", message: "Apakah Anda yakin ingin ajukan review ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: () => { onPressRight: async () => {
console.log("Hapus"); try {
router.back(); onSetLoading(true);
const response = await apiVotingUpdateStatus({
id: id as string,
status: "review",
});
if (response?.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
onSetLoading(false);
}
}, },
}); });
}; };
@@ -48,9 +97,33 @@ export default function Voting_ButtonStatusSection({
message: "Apakah Anda yakin ingin edit kembali ini?", message: "Apakah Anda yakin ingin edit kembali ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: () => { onPressRight: async () => {
console.log("Hapus"); try {
router.back(); onSetLoading(true);
const response = await apiVotingUpdateStatus({
id: id as string,
status: "draft",
});
if (response?.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
onSetLoading(false);
}
}, },
}); });
}; };
@@ -61,9 +134,32 @@ export default function Voting_ButtonStatusSection({
message: "Apakah Anda yakin ingin menghapus data ini?", message: "Apakah Anda yakin ingin menghapus data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: async () => {
console.log("Hapus"); try {
router.back(); onSetLoading(true);
const response = await apiVotingDelete({
id: id as string,
});
if (response?.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
onSetLoading(false);
}
}, },
}); });
}; };
@@ -72,6 +168,7 @@ export default function Voting_ButtonStatusSection({
return ( return (
<> <>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
backgroundColor="red" backgroundColor="red"
textColor="white" textColor="white"
onPress={handleOpenDeleteAlert} onPress={handleOpenDeleteAlert}
@@ -88,7 +185,7 @@ export default function Voting_ButtonStatusSection({
case "review": case "review":
return ( return (
<ButtonCustom onPress={handleBatalkanReview}> <ButtonCustom isLoading={isLoading} onPress={handleBatalkanReview}>
Batalkan Review Batalkan Review
</ButtonCustom> </ButtonCustom>
); );
@@ -97,15 +194,14 @@ export default function Voting_ButtonStatusSection({
return ( return (
<> <>
<Grid> <Grid>
<Grid.Col span={5}> <Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleAjukanReview}> <ButtonCustom isLoading={isLoading} onPress={handleAjukanReview}>
Ajukan Review Ajukan Review
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={2}> <Grid.Col span={6} style={{ paddingRight: 10 }}>
<View /> {DeleteButton()}
</Grid.Col> </Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid> </Grid>
</> </>
); );
@@ -114,15 +210,14 @@ export default function Voting_ButtonStatusSection({
return ( return (
<> <>
<Grid> <Grid>
<Grid.Col span={5}> <Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleEditKembali}> <ButtonCustom isLoading={isLoading} onPress={handleEditKembali}>
Edit Kembali Edit Kembali
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={2}> <Grid.Col span={6} style={{ paddingLeft: 10 }}>
<View /> {DeleteButton()}
</Grid.Col> </Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid> </Grid>
</> </>
); );

View File

@@ -1,8 +1,6 @@
import { BadgeCustom, StackCustom, TextCustom } from "@/components"; import { BadgeCustom, StackCustom, TextCustom } from "@/components";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView"; import { dateTimeView } from "@/utils/dateTimeView";
import dayjs from "dayjs";
import { View } from "react-native";
export function Voting_ComponentDetailDataSection({ data }: { data?: any }) { export function Voting_ComponentDetailDataSection({ data }: { data?: any }) {
return ( return (

View File

@@ -35,7 +35,6 @@ export async function apiVotingGetOne({ id }: { id: string }) {
} }
} }
export async function apiVotingUpdateStatus({ export async function apiVotingUpdateStatus({
id, id,
status, status,
@@ -50,3 +49,78 @@ export async function apiVotingUpdateStatus({
throw error; throw error;
} }
} }
export async function apiVotingDelete({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/voting/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingUpdateData({
id,
data,
category,
}: {
id: string;
data: any;
category: "edit" | "archive";
}) {
const categoryQuery = `?category=${category}`;
try {
const response = await apiConfig.put(`/mobile/voting/${id}${categoryQuery}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingGetAll({ search, category, authorId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string }) {
try {
const categoryQuery = category ? `?category=${category}` : "";
const searchQuery = search ? `&search=${search}` : "";
const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingVote({ id, data }: { id: string; data: any }) {
try {
const response = await apiConfig.post(`/mobile/voting/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingContribution({
id,
authorId,
category,
}: {
id: string;
authorId: string;
category: "list" | "checked";
}) {
const query =
category === "list"
? "?category=list"
: `?category=checked&authorId=${authorId}`;
try {
const response = await apiConfig.get(
`/mobile/voting/${id}/contribution${query}`
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -1,5 +1,6 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
export const today = new Date();
export const dateTimeView = ({ export const dateTimeView = ({
date, date,
withoutTime = false, withoutTime = false,