diff --git a/app/(application)/(user)/voting/(tabs)/index.tsx b/app/(application)/(user)/voting/(tabs)/index.tsx index cc1a1a7..f5a1d62 100644 --- a/app/(application)/(user)/voting/(tabs)/index.tsx +++ b/app/(application)/(user)/voting/(tabs)/index.tsx @@ -1,23 +1,63 @@ +/* eslint-disable react-hooks/exhaustive-deps */ import { FloatingButton, + LoaderCustom, SearchInput, - ViewWrapper + TextCustom, + ViewWrapper, } from "@/components"; import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection"; +import { apiVotingGetAll } from "@/service/api-client/api-voting"; import { router } from "expo-router"; +import _ from "lodash"; +import { useEffect, useState } from "react"; export default function VotingBeranda() { + const [listData, setListData] = useState([]); + const [loadingGetData, setLoadingGetData] = useState(false); + const [search, setSearch] = useState(""); + + useEffect(() => { + onLoadData(); + }, [search]); + + const onLoadData = async () => { + try { + setLoadingGetData(true); + const response = await apiVotingGetAll({ search }); + if (response.success) { + setListData(response.data); + } + } catch (error) { + console.log("[ERROR]", error); + } finally { + setLoadingGetData(false); + } + }; + return ( router.push("/voting/create")} /> } - headerComponent={} + headerComponent={ + + } > - {Array.from({ length: 5 }).map((_, index) => ( - - ))} + {loadingGetData ? ( + + ) : _.isEmpty(listData) ? ( + Tidak ada data + ) : ( + listData.map((item: any, index: number) => ( + + )) + )} ); } diff --git a/app/(application)/(user)/voting/(tabs)/status.tsx b/app/(application)/(user)/voting/(tabs)/status.tsx index f97588e..770930b 100644 --- a/app/(application)/(user)/voting/(tabs)/status.tsx +++ b/app/(application)/(user)/voting/(tabs)/status.tsx @@ -12,7 +12,6 @@ import { useAuth } from "@/hooks/use-auth"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { apiVotingGetByStatus } from "@/service/api-client/api-voting"; import { dateTimeView } from "@/utils/dateTimeView"; -import dayjs from "dayjs"; import { useFocusEffect } from "expo-router"; import _ from "lodash"; import { useCallback, useState } from "react"; @@ -20,7 +19,6 @@ import { useCallback, useState } from "react"; export default function VotingStatus() { const { user } = useAuth(); const id = user?.id || ""; - console.log("ID >> ", id); const [activeCategory, setActiveCategory] = useState( "publish" ); @@ -41,7 +39,6 @@ export default function VotingStatus() { id: id as string, status: activeCategory!, }); - console.log("[RES LIST STATUS]", JSON.stringify(response.data, null, 2)); setListData(response.data); } catch (error) { console.log(error); diff --git a/app/(application)/(user)/voting/[id]/[status]/detail.tsx b/app/(application)/(user)/voting/[id]/[status]/detail.tsx index b677050..5f3e792 100644 --- a/app/(application)/(user)/voting/[id]/[status]/detail.tsx +++ b/app/(application)/(user)/voting/[id]/[status]/detail.tsx @@ -20,15 +20,12 @@ import { useLocalSearchParams, } from "expo-router"; import { useCallback, useState } from "react"; -import { useAuth } from "@/hooks/use-auth"; export default function VotingDetailStatus() { - const {user} = useAuth() const { id, status } = useLocalSearchParams(); - console.log("ID >> ", id); - console.log("STATUS >> ", status); const [openDrawerDraft, setOpenDrawerDraft] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [data, setData] = useState(null); @@ -41,10 +38,11 @@ export default function VotingDetailStatus() { const onLoadData = async () => { try { 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) { - console.log(error); + console.log("[ERROR]", error); } }; @@ -62,7 +60,7 @@ export default function VotingDetailStatus() { textLeft: "Batal", textRight: "Ya", onPressRight: () => { - console.log("Hapus"); + console.log("Arsip voting"); router.back(); }, }); @@ -86,8 +84,13 @@ export default function VotingDetailStatus() { }} /> - - + + diff --git a/app/(application)/(user)/voting/[id]/edit.tsx b/app/(application)/(user)/voting/[id]/edit.tsx index 3dc0724..ba2823d 100644 --- a/app/(application)/(user)/voting/[id]/edit.tsx +++ b/app/(application)/(user)/voting/[id]/edit.tsx @@ -1,29 +1,183 @@ +/* eslint-disable react-hooks/exhaustive-deps */ import { + ActionIcon, BoxButtonOnFooter, - ButtonCenteredOnly, ButtonCustom, - Grid, + CenterCustom, + LoaderCustom, Spacing, StackCustom, TextAreaCustom, + TextCustom, TextInputCustom, ViewWrapper, } from "@/components"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; 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 { router } from "expo-router"; -import { TouchableOpacity } from "react-native"; +import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; +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() { + const { id } = useLocalSearchParams(); + const [loadingGetData, setLoadingGetData] = useState(false); + const [data, setData] = useState(); + 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, + }); + + 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 = () => { return ( <> - router.back() - } + isLoading={isLoading} + onPress={() => handlerUpdateSubmit()} > Update @@ -34,45 +188,144 @@ export default function VotingEdit() { return ( - - - - - + {loadingGetData ? ( + + ) : ( + + setData({ ...data, title: text })} + /> + setData({ ...data, deskripsi: text })} + /> - - + + + { + setData({ ...data, awalVote: date }); + }} + /> + + + { + setData({ ...data, akhirVote: date }); + }} + /> + + {validateDateRange({ + selectedStratDate: data?.awalVote as any, + selectedEndDate: data?.akhirVote as any, + }).isValid ? ( + + { + validateDateRange({ + selectedStratDate: data?.awalVote as any, + selectedEndDate: data?.akhirVote as any, + }).error + } + + ) : ( + + { + validateDateRange({ + selectedStratDate: data?.awalVote as any, + selectedEndDate: data?.akhirVote as any, + }).error + } + + )} + + + + {data?.Voting_DaftarNamaVote?.map((item: any, index: number) => ( + setData({ + ...(data as any), + Voting_DaftarNamaVote: data?.Voting_DaftarNamaVote?.map( + (item: any, i: any) => + i === index ? { ...item, value } : item + ), + }) + } /> - - - console.log("delete")}> - - - - + ))} - console.log("add")}> - Tambah Pilihan - - - + + + = 4} + onPress={() => { + setData({ + ...(data as any), + Voting_DaftarNamaVote: [ + ...(data as any)?.Voting_DaftarNamaVote, + { value: "" }, + ], + }); + }} + icon={ + + } + size="xl" + /> + { + const list = _.clone((data as any)?.Voting_DaftarNamaVote); + list.pop(); + setData({ + ...(data as any), + Voting_DaftarNamaVote: list, + }); + }} + icon={ + + } + size="xl" + /> + + + + + )} ); } diff --git a/app/(application)/(user)/voting/[id]/index.tsx b/app/(application)/(user)/voting/[id]/index.tsx index 5bd3f44..5b76ac4 100644 --- a/app/(application)/(user)/voting/[id]/index.tsx +++ b/app/(application)/(user)/voting/[id]/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ import { AlertDefaultSystem, AvatarUsernameAndOtherComponent, @@ -5,20 +6,90 @@ import { DotButton, DrawerCustom, InformationBox, + LoaderCustom, MenuDrawerDynamicGrid, StackCustom, + TextCustom, ViewWrapper, } from "@/components"; import { IconArchive, IconContribution } from "@/components/_Icon"; import { IMenuDrawerItem } from "@/components/_Interface/types"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection"; -import { router, Stack, useLocalSearchParams } from "expo-router"; -import React, { useState } from "react"; +import { + apiVotingCheckContribution, + apiVotingGetOne, +} from "@/service/api-client/api-voting"; +import { + router, + Stack, + useFocusEffect, + useLocalSearchParams, +} from "expo-router"; +import React, { useCallback, useState } from "react"; +import { useAuth } from "@/hooks/use-auth"; export default function VotingDetail() { const { id } = useLocalSearchParams(); + const { user } = useAuth(); + console.log("[ID]", id); + const dateNow = new Date(); + console.log("[DATE NOW]", dateNow); + const [openDrawerPublish, setOpenDrawerPublish] = useState(false); + const [data, setData] = useState(null); + const [loadingGetData, setLoadingGetData] = useState(false); + const [isContribution, setIsContribution] = useState(false); + const [nameChoice, setNameChoice] = useState(""); + + console.log("[DATA AWAL]", data?.awalVote); + + 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 }); + // console.log("[DATA]", JSON.stringify(response.data, null, 2)); + if (response.success) { + setData(response.data); + } + } catch (error) { + console.log("[ERROR]", error); + } + }; + + const onLoadCheckContribution = async () => { + try { + const response = await apiVotingCheckContribution({ + id: id as string, + authorId: user?.id as string, + }); + console.log("[DATA CONTRIBUION]", response.data); + if (response.success) { + setIsContribution(response.data.isContribution); + setNameChoice(response.data.nameChoice); + } + } catch (error) { + console.log("[ERROR]", error); + } + }; + const handlePressPublish = (item: IMenuDrawerItem) => { if (item.path === "") { AlertDefaultSystem({ @@ -49,15 +120,32 @@ export default function VotingDetail() { /> - - + {loadingGetData ? ( + + ) : ( + + {dateNow < new Date(data?.awalVote) && ( + + )} + + } + /> - } - /> - - - + + + )} {/* ========= Publish Drawer ========= */} diff --git a/app/(application)/(user)/voting/create.tsx b/app/(application)/(user)/voting/create.tsx index c656683..9fe2eed 100644 --- a/app/(application)/(user)/voting/create.tsx +++ b/app/(application)/(user)/voting/create.tsx @@ -142,26 +142,6 @@ export default function VotingCreate() { } /> - {/* - - - - - console.log("delete")}> - - - - */} - {/* console.log("add")}> - Tambah Pilihan - */} {listVote.map((item, index) => ( @@ -16,10 +20,12 @@ export default function Voting_BoxDetailHasilVotingSection() { - {Array.from({ length: 4 }).map((_, i) => ( - - - Pilihan {i + 1} + {listData?.map((item: any, i: number) => ( + + + + {item?.value} + ))} diff --git a/screens/Voting/BoxDetailPublishSection.tsx b/screens/Voting/BoxDetailPublishSection.tsx index c4addf5..99ed3ff 100644 --- a/screens/Voting/BoxDetailPublishSection.tsx +++ b/screens/Voting/BoxDetailPublishSection.tsx @@ -1,45 +1,95 @@ import { + BadgeCustom, BoxWithHeaderSection, ButtonCustom, + CenterCustom, Spacing, StackCustom, - TextCustom + TextCustom, } from "@/components"; import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; import { useState } from "react"; import { View } from "react-native"; import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection"; +import { apiVotingVote } from "@/service/api-client/api-voting"; +import { useAuth } from "@/hooks/use-auth"; export function Voting_BoxDetailPublishSection({ headerAvatar, + data, + userId, + isContribution, + nameChoice, }: { headerAvatar?: React.ReactNode; + data?: any; + userId: string; + isContribution?: boolean; + nameChoice?: string; }) { const [value, setValue] = useState(""); + + const handlerSubmitVote = async () => { + const newData = { + chooseId: value, + userId: userId, + }; + + try { + const response = await apiVotingVote({ + id: data?.id, + data: newData, + }); + console.log("[RES VOTE]", response); + } catch (error) { + console.log("[ERROR]", error); + } + }; + return ( <> - {headerAvatar ? headerAvatar : } + {headerAvatar && ( + <> + {headerAvatar} + + + )} + - + - - - Pilihan : - - - {Array.from({ length: 4 }).map((_, i) => ( - - - - ))} - - + {isContribution ? ( + + + Pilihan Anda : + + + + {nameChoice || "-"} + + + + ) : ( + <> + + + Pilihan : + + + {data?.Voting_DaftarNamaVote?.map((item: any, i: number) => ( + + + + ))} + + - console.log("vote")}>Vote + + Vote + + + )} diff --git a/screens/Voting/BoxPublishSection.tsx b/screens/Voting/BoxPublishSection.tsx index 6728512..fa17017 100644 --- a/screens/Voting/BoxPublishSection.tsx +++ b/screens/Voting/BoxPublishSection.tsx @@ -15,37 +15,43 @@ export default function Voting_BoxPublishSection({ href, id, bottomComponent, + data, }: { - href?: Href - id?: string + href?: Href; + id?: string; bottomComponent?: React.ReactNode; + data?: any; }) { return ( <> - - - + + + - Voting Title {id} + {data?.title || "-"} - {dayjs().format("DD/MM/YYYY")} -{" "} - {dayjs().add(1, "day").format("DD/MM/YYYY")} + {dayjs(data?.awalVote).format("DD/MM/YYYY")} -{" "} + {dayjs(data?.akhirVote).format("DD/MM/YYYY")} - + {/* {Array.from({ length: 4 }).map((_, i) => ( Pilihan {i + 1} ))} - + */} {bottomComponent} diff --git a/screens/Voting/ButtonStatusSection.tsx b/screens/Voting/ButtonStatusSection.tsx index 15d4ba1..451d791 100644 --- a/screens/Voting/ButtonStatusSection.tsx +++ b/screens/Voting/ButtonStatusSection.tsx @@ -1,14 +1,21 @@ 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 { View } from "react-native"; +import Toast from "react-native-toast-message"; export default function Voting_ButtonStatusSection({ id, status, + isLoading, + onSetLoading, }: { id: string; status: string; + isLoading: boolean; + onSetLoading: (value: boolean) => void; }) { const handleBatalkanReview = () => { AlertDefaultSystem({ @@ -16,15 +23,33 @@ export default function Voting_ButtonStatusSection({ message: "Apakah Anda yakin ingin batalkan review ini?", textLeft: "Batal", textRight: "Ya", - onPressRight: async() => { - // console.log("Hapus"); - // router.back(); - const response = await apiVotingUpdateStatus({ - id: id as string, - status: "draft", - }) - console.log("[RES BATALKAN REVIEW]", JSON.stringify(response, null, 2)); - // router.back(); + onPressRight: async () => { + try { + 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); + } }, }); }; @@ -35,9 +60,33 @@ export default function Voting_ButtonStatusSection({ message: "Apakah Anda yakin ingin ajukan review ini?", textLeft: "Batal", textRight: "Ya", - onPressRight: () => { - console.log("Hapus"); - router.back(); + onPressRight: async () => { + try { + 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?", textLeft: "Batal", textRight: "Ya", - onPressRight: () => { - console.log("Hapus"); - router.back(); + onPressRight: async () => { + try { + 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?", textLeft: "Batal", textRight: "Hapus", - onPressRight: () => { - console.log("Hapus"); - router.back(); + onPressRight: async () => { + try { + 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 ( <> + Batalkan Review ); @@ -97,15 +194,14 @@ export default function Voting_ButtonStatusSection({ return ( <> - - + + Ajukan Review - - + + {DeleteButton()} - {DeleteButton()} ); @@ -114,15 +210,14 @@ export default function Voting_ButtonStatusSection({ return ( <> - - + + Edit Kembali - - + + {DeleteButton()} - {DeleteButton()} ); diff --git a/screens/Voting/ComponentDetailDataSection.tsx b/screens/Voting/ComponentDetailDataSection.tsx index c33f341..0efc593 100644 --- a/screens/Voting/ComponentDetailDataSection.tsx +++ b/screens/Voting/ComponentDetailDataSection.tsx @@ -1,8 +1,6 @@ import { BadgeCustom, StackCustom, TextCustom } from "@/components"; import { GStyles } from "@/styles/global-styles"; import { dateTimeView } from "@/utils/dateTimeView"; -import dayjs from "dayjs"; -import { View } from "react-native"; export function Voting_ComponentDetailDataSection({ data }: { data?: any }) { return ( diff --git a/service/api-client/api-voting.ts b/service/api-client/api-voting.ts index 089118f..62f00eb 100644 --- a/service/api-client/api-voting.ts +++ b/service/api-client/api-voting.ts @@ -35,7 +35,6 @@ export async function apiVotingGetOne({ id }: { id: string }) { } } - export async function apiVotingUpdateStatus({ id, status, @@ -50,3 +49,68 @@ export async function apiVotingUpdateStatus({ 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, +}: { + id: string; + data: any; +}) { + try { + const response = await apiConfig.put(`/mobile/voting/${id}`, { + data: data, + }); + return response.data; + } catch (error) { + throw error; + } +} + +export async function apiVotingGetAll({ search }: { search: string }) { + try { + const searchQuery = search ? `?search=${search}` : ""; + const response = await apiConfig.get(`/mobile/voting${searchQuery}`); + 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 apiVotingCheckContribution({ + id, + authorId, +}: { + id: string; + authorId: string; +}) { + try { + const response = await apiConfig.get( + `/mobile/voting/${id}/contribution?authorId=${authorId}` + ); + console.log("[DATA CONTRIBUION]", response.data); + return response.data; + } catch (error) { + throw error; + } +}