Compare commits

...

2 Commits

Author SHA1 Message Date
ce79d7c240 Voting
Add:
- api-client/api-voting: kumpulan fetching api voting

Fix:
- UI create dan (tabs) status udah terintegrasi ke API

### No Isuue
2025-09-17 17:31:44 +08:00
d09a566903 Job
Add:
- add file: (user)/job/[id]/archive

Fix:
- Semua tampilan telah terintergrasi ke API Job

### No Issue
2025-09-17 14:26:10 +08:00
16 changed files with 585 additions and 93 deletions

View File

@@ -509,6 +509,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen
name="job/[id]/archive"
options={{
title: "Arsip Job",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Job Section ========= */} {/* ========== End Job Section ========= */}

View File

@@ -1,16 +1,57 @@
import { BaseBox, TextCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { BaseBox, LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function JobArchive() { export default function JobArchive() {
const { user } = useAuth();
const [listData, setListData] = useState<any[]>([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetAll({
category: "archive",
authorId: user?.id,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{jobDataDummy.map((e, i) => ( {isLoadData ? (
<BaseBox key={i} paddingTop={20} paddingBottom={20}> <LoaderCustom />
<TextCustom align="center" bold truncate size="large"> ) : _.isEmpty(listData) ? (
{e.posisi} <TextCustom align="center">Anda tidak memiliki arsip</TextCustom>
</TextCustom> ) : (
</BaseBox> listData.map((item, index) => (
))} <BaseBox
key={index}
paddingTop={20}
paddingBottom={20}
href={`/job/${item.id}/archive`}
>
<TextCustom align="center" bold truncate size="large">
{item?.title || "-"}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -28,7 +28,7 @@ export default function JobBeranda() {
const onLoadData = async (search: string) => { const onLoadData = async (search: string) => {
try { try {
setIsLoadData(true); setIsLoadData(true);
const response = await apiJobGetAll({ search }); const response = await apiJobGetAll({ search, category: "beranda" });
setListData(response.data); setListData(response.data);
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);

View File

@@ -77,6 +77,7 @@ export default function JobDetailStatus() {
status={status as string} status={status as string}
isLoading={isLoading} isLoading={isLoading}
onSetLoading={setIsLoading} onSetLoading={setIsLoading}
isArchive={true}
/> />
</StackCustom> </StackCustom>
<Spacing /> <Spacing />

View File

@@ -0,0 +1,100 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ButtonCustom,
LoaderCustom,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function JobDetailArchive() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetOne({ id: id as string });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handleArchive = async () => {
try {
setIsLoading(true);
const response = await apiJobUpdateData({
id: id as string,
data: false,
category: "archive",
});
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 {
setIsLoading(false);
}
};
return (
<>
{isLoadData ? (
<LoaderCustom />
) : (
<ViewWrapper>
<>
<StackCustom>
<Job_BoxDetailSection data={data} />
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handleArchive();
}}
>
Publish kembali
</ButtonCustom>
{/* <Job_ButtonStatusSection
id={id as string}
status={status as string}
isLoading={isLoading}
onSetLoading={setIsLoading}
isArchive={true}
/> */}
</StackCustom>
<Spacing />
</>
</ViewWrapper>
)}
</>
);
}

View File

@@ -99,6 +99,7 @@ export default function JobEdit() {
const response = await apiJobUpdateData({ const response = await apiJobUpdateData({
id: id as string, id: id as string,
data: newData, data: newData,
category: "edit",
}); });
if (response.success) { if (response.success) {

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, ViewWrapper } from "@/components"; import { ButtonCustom, LoaderCustom, Spacing, StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
@@ -90,9 +90,11 @@ export default function JobDetail() {
) : ( ) : (
<> <>
<Job_BoxDetailSection data={data} /> <Job_BoxDetailSection data={data} />
<OpenLinkButton id={id as string} /> <StackCustom>
<OpenLinkButton id={id as string} />
<CopyLinkButton id={id as string} />
</StackCustom>
<Spacing /> <Spacing />
<CopyLinkButton id={id as string} />
</> </>
)} )}
</ViewWrapper> </ViewWrapper>

View File

@@ -1,20 +1,55 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
LoaderCustom,
ScrollableCustom, ScrollableCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
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 { dateTimeView } from "@/utils/dateTimeView";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useState } from "react"; import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function VotingStatus() { export default function VotingStatus() {
const { user } = useAuth();
const id = user?.id || "";
console.log("ID >> ", id);
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
const [listData, setListData] = useState([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory, id])
);
async function onLoadData() {
try {
setLoadingGetData(true);
const response = await apiVotingGetByStatus({
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);
} finally {
setLoadingGetData(false);
}
}
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -34,27 +69,33 @@ export default function VotingStatus() {
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, i) => ( {loadingGetData ? (
<BaseBox <LoaderCustom />
key={i} ) : _.isEmpty(listData) ? (
paddingTop={20} <TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
paddingBottom={20} ) : (
href={`/voting/${i}/${activeCategory}/detail`} listData.map((item: any, i: number) => (
> <BaseBox
<StackCustom> key={i}
<TextCustom align="center" bold truncate size="large"> paddingTop={20}
Lorem ipsum dolor sit {activeCategory} paddingBottom={20}
</TextCustom> href={`/voting/${item.id}/${activeCategory}/detail`}
<BadgeCustom >
style={{ width: "70%", alignSelf: "center" }} <StackCustom>
variant="light" <TextCustom align="center" bold truncate={2} size="large">
> {item?.title || ""}
{dayjs().format("DD/MM/YYYY")} -{" "} </TextCustom>
{dayjs().add(1, "day").format("DD/MM/YYYY")} <BadgeCustom
</BadgeCustom> style={{ width: "70%", alignSelf: "center" }}
</StackCustom> variant="light"
</BaseBox> >
))} {item?.awalVote && dateTimeView({date: item?.awalVote, withoutTime: true})} -{" "}
{item?.akhirVote && dateTimeView({date: item?.akhirVote, withoutTime: true})}
</BadgeCustom>
</StackCustom>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
@@ -11,14 +12,42 @@ import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
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 { router, Stack, useLocalSearchParams } from "expo-router"; import { apiVotingGetOne } from "@/service/api-client/api-voting";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import { useAuth } from "@/hooks/use-auth";
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 [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiVotingGetOne({ id: id as string });
console.log("Response", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log(error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -57,8 +86,8 @@ export default function VotingDetailStatus() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Voting_BoxDetailSection /> <Voting_BoxDetailSection data={data as any}/>
<Voting_ButtonStatusSection status={status as string} /> <Voting_ButtonStatusSection id={id as string} status={status as string} />
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>

View File

@@ -1,30 +1,103 @@
import { import {
ActionIcon,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
Grid, CenterCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
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 { useAuth } from "@/hooks/use-auth";
import { apiVotingCreate } 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 } from "expo-router";
import { TouchableOpacity } from "react-native"; import _ from "lodash";
import { useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function VotingCreate() { export default function VotingCreate() {
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState({
authorId: "",
title: "",
deskripsi: "",
awalVote: "",
akhirVote: "",
listVote: [],
});
const [listVote, setListVote] = useState([
{
name: "Nama Pilihan",
value: "",
},
{
name: "Nama Pilihan",
value: "",
},
]);
const handlerSubmit = async () => {
if (!data.title || !data.deskripsi || !data.awalVote || !data.akhirVote) {
Toast.show({
type: "info",
text1: "Lengkapi semua data",
});
return;
}
if (listVote.some((item: any) => item.value === "")) {
Toast.show({
type: "info",
text1: "Lengkapi semua data pilihan",
});
return;
}
try {
setIsLoading(true);
const newData = {
...data,
authorId: user?.id,
awalVote: new Date(data.awalVote as any).toISOString(),
akhirVote: new Date(data.akhirVote as any).toISOString(),
listVote: listVote,
};
const response = await apiVotingCreate(newData);
// console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil disimpan",
});
router.replace("/(application)/(user)/voting/(tabs)/status");
} else {
Toast.show({
type: "error",
text1: "Data gagal disimpan",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>
onPress={() =>
router.replace("/(application)/(user)/voting/(tabs)/status")
}
>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
@@ -39,6 +112,8 @@ export default function VotingCreate() {
label="Judul Voting" label="Judul Voting"
placeholder="MasukanJudul Voting" placeholder="MasukanJudul Voting"
required required
value={data.title}
onChangeText={(value: any) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi" label="Deskripsi"
@@ -46,11 +121,28 @@ export default function VotingCreate() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
/>
<DateTimePickerCustom
label="Mulai Voting"
required
// value={data.awalVote ? new Date(data.awalVote) : null}
onChange={(value: any) => setData({ ...data, awalVote: value })}
minimumDate={new Date(Date.now())}
/>
<DateTimePickerCustom
disabled={!data.awalVote}
label="Voting Berakhir"
required
// value={data.akhirVote ? new Date(data.akhirVote) : null}
onChange={(value: any) => setData({ ...data, akhirVote: value })}
minimumDate={
data.awalVote ? new Date(data.awalVote) : new Date(Date.now())
}
/> />
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
<Grid> {/* <Grid>
<Grid.Col span={10}> <Grid.Col span={10}>
<TextInputCustom <TextInputCustom
label="Pilihan" label="Pilihan"
@@ -66,11 +158,64 @@ export default function VotingCreate() {
<Ionicons name="trash" size={24} color={MainColor.red} /> <Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity> </TouchableOpacity>
</Grid.Col> </Grid.Col>
</Grid> </Grid> */}
{/* <ButtonCenteredOnly onPress={() => console.log("add")}>
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan Tambah Pilihan
</ButtonCenteredOnly> </ButtonCenteredOnly> */}
{listVote.map((item, index) => (
<TextInputCustom
key={index}
label="Pilihan"
placeholder="Masukan Pilihan"
required
value={item.value}
onChangeText={(value: any) =>
setListVote(
listVote.map((item, i) =>
i === index ? { ...item, value } : item
)
)
}
/>
))}
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={listVote.length >= 4}
onPress={() => {
setListVote([...listVote, { name: "Nama Pilihan", value: "" }]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listVote.length <= 2}
onPress={() => {
const list = _.clone(listVote);
list.pop();
setListVote(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>

View File

@@ -1,5 +1,9 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { apiJobDelete, apiJobUpdateStatus } from "@/service/api-client/api-job"; import {
apiJobDelete,
apiJobUpdateData,
apiJobUpdateStatus,
} from "@/service/api-client/api-job";
import { router } from "expo-router"; import { router } from "expo-router";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -8,11 +12,13 @@ export default function Job_ButtonStatusSection({
status, status,
isLoading, isLoading,
onSetLoading, onSetLoading,
isArchive,
}: { }: {
id: string; id: string;
status: string; status: string;
isLoading: boolean; isLoading: boolean;
onSetLoading: (value: boolean) => void; onSetLoading: (value: boolean) => void;
isArchive?: boolean;
}) { }) {
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
@@ -27,7 +33,7 @@ export default function Job_ButtonStatusSection({
id: id, id: id,
status: "draft", status: "draft",
}); });
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -64,7 +70,7 @@ export default function Job_ButtonStatusSection({
id: id, id: id,
status: "review", status: "review",
}); });
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -101,7 +107,7 @@ export default function Job_ButtonStatusSection({
id: id, id: id,
status: "draft", status: "draft",
}); });
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -137,7 +143,7 @@ export default function Job_ButtonStatusSection({
const response = await apiJobDelete({ const response = await apiJobDelete({
id: id, id: id,
}); });
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -161,6 +167,45 @@ export default function Job_ButtonStatusSection({
}); });
}; };
const handleArchive = () => {
AlertDefaultSystem({
title: "Arsipkan",
message: "Apakah Anda yakin ingin mengarsipkan data ini?",
textLeft: "Batal",
textRight: "Arsipkan",
onPressRight: async () => {
try {
onSetLoading(true);
const response = await apiJobUpdateData({
id: id,
data: isArchive,
category: "archive",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
// router.back();
router.replace("/(application)/(user)/job/(tabs)/archive");
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
onSetLoading(false);
}
},
});
};
const DeleteButton = () => { const DeleteButton = () => {
return ( return (
<> <>
@@ -181,9 +226,9 @@ export default function Job_ButtonStatusSection({
return ( return (
<> <>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
console.log("Arsipkan"); handleArchive();
router.replace("/(application)/(user)/job/(tabs)/archive");
}} }}
> >
Arsipkan Arsipkan

View File

@@ -2,35 +2,38 @@ import {
BoxWithHeaderSection, BoxWithHeaderSection,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom TextCustom,
} from "@/components"; } from "@/components";
import { View } from "react-native";
import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection"; import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection";
import { Ionicons } from "@expo/vector-icons";
export function Voting_BoxDetailSection({ export function Voting_BoxDetailSection({
headerAvatar, headerAvatar,
data,
}: { }: {
headerAvatar?: React.ReactNode; headerAvatar?: React.ReactNode;
data?: any;
}) { }) {
return ( return (
<> <>
<BoxWithHeaderSection> <BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />} {headerAvatar ? headerAvatar : <Spacing />}
<StackCustom> <StackCustom>
<Voting_ComponentDetailDataSection/> <Voting_ComponentDetailDataSection data={data} />
<Spacing/> <Spacing height={0} />
<View> <StackCustom>
<TextCustom bold size="small"> <TextCustom bold size="small">
Pilihan : Pilihan :
</TextCustom> </TextCustom>
{Array.from({ length: 3 }).map((_, i) => ( {data?.Voting_DaftarNamaVote?.map((item: any, i: number) => (
<View key={i}> <StackCustom key={i}>
<TextCustom>Nama Pilihan {i + 1}</TextCustom> <TextCustom>
<Spacing /> <Ionicons name="caret-forward" size={14} /> {item?.value}
</View> </TextCustom>
</StackCustom>
))} ))}
</View> </StackCustom>
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
</> </>

View File

@@ -1,10 +1,13 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { apiVotingUpdateStatus } from "@/service/api-client/api-voting";
import { router } from "expo-router"; import { router } from "expo-router";
import { View } from "react-native"; import { View } from "react-native";
export default function Voting_ButtonStatusSection({ export default function Voting_ButtonStatusSection({
id,
status, status,
}: { }: {
id: string;
status: string; status: string;
}) { }) {
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
@@ -13,9 +16,15 @@ 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: () => { onPressRight: async() => {
console.log("Hapus"); // console.log("Hapus");
router.back(); // router.back();
const response = await apiVotingUpdateStatus({
id: id as string,
status: "draft",
})
console.log("[RES BATALKAN REVIEW]", JSON.stringify(response, null, 2));
// router.back();
}, },
}); });
}; };

View File

@@ -1,21 +1,17 @@
import { BadgeCustom, 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 dayjs from "dayjs"; import dayjs from "dayjs";
import { View } from "react-native"; import { View } from "react-native";
export function Voting_ComponentDetailDataSection() { export function Voting_ComponentDetailDataSection({ data }: { data?: any }) {
return ( return (
<> <>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Title of Voting Here {data?.title || "-"}
</TextCustom> </TextCustom>
<TextCustom> <TextCustom>{data?.deskripsi || "-"}</TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis <StackCustom gap={"sm"}>
corporis blanditiis est provident corrupti facilis iste cum voluptate.
Natus eum aut quos consequatur doloribus fugiat sit ullam minima non
enim?
</TextCustom>
<View>
<TextCustom bold size="small" align="center"> <TextCustom bold size="small" align="center">
Batas Voting Batas Voting
</TextCustom> </TextCustom>
@@ -23,10 +19,13 @@ export function Voting_ComponentDetailDataSection() {
style={[GStyles.alignSelfCenter, { width: "70%" }]} style={[GStyles.alignSelfCenter, { width: "70%" }]}
variant="light" variant="light"
> >
{dayjs().format("DD/MM/YYYY")} -{" "} {data?.awalVote &&
{dayjs().add(1, "day").format("DD/MM/YYYY")} dateTimeView({ date: data?.awalVote, withoutTime: true })}{" "}
-{" "}
{data?.akhirVote &&
dateTimeView({ date: data?.akhirVote, withoutTime: true })}
</BadgeCustom> </BadgeCustom>
</View> </StackCustom>
</> </>
); );
} }

View File

@@ -59,10 +59,24 @@ export async function apiJobDelete({ id }: { id: string }) {
} }
} }
export async function apiJobGetAll({ search }: { search?: string }) { export async function apiJobGetAll({
search,
category,
authorId,
}: {
search?: string;
category: "archive" | "beranda";
authorId?: string;
}) {
try { try {
const searchText = search ? `?search=${search}` : ""; let categoryText = category ? `?category=${category}` : "";
const response = await apiConfig.get(`/mobile/job${searchText}`); if (category === "archive") {
categoryText = `?category=${category}&authorId=${authorId}`;
}
const searchText = search ? `&search=${search}` : "";
const response = await apiConfig.get(
`/mobile/job${categoryText}${searchText}`
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
@@ -72,12 +86,15 @@ export async function apiJobGetAll({ search }: { search?: string }) {
export async function apiJobUpdateData({ export async function apiJobUpdateData({
id, id,
data, data,
category,
}: { }: {
id: string; id: string;
data: any; data: any;
category: "edit" | "archive";
}) { }) {
try { try {
const response = await apiConfig.put(`/mobile/job/${id}`, { const categoryJob = category ? `?category=${category}` : "";
const response = await apiConfig.put(`/mobile/job/${id}${categoryJob}`, {
data: data, data: data,
}); });
return response.data; return response.data;

View File

@@ -0,0 +1,52 @@
import { apiConfig } from "../api-config";
export async function apiVotingCreate(data: any) {
try {
const response = await apiConfig.post(`/mobile/voting`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingGetByStatus({
id,
status,
}: {
id: string;
status: string;
}) {
try {
const response = await apiConfig.get(`/mobile/voting/${id}/${status}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingGetOne({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/voting/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiVotingUpdateStatus({
id,
status,
}: {
id: string;
status: "draft" | "review" | "publish" | "reject";
}) {
try {
const response = await apiConfig.put(`/mobile/voting/${id}/${status}`);
return response.data;
} catch (error) {
throw error;
}
}