Compare commits

...

8 Commits

Author SHA1 Message Date
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
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
60b0befa60 API Job
Add:
- api-client/api-job: kumpulan fetch api

Fix:
- UI beranda , status sudah terintergrasi dengan API
- UI detail status, detail utama sudah terintergrasi dengan API
- Search pada beranda sudah terintegrasi
- Edit sudah terintergrasi

### No Issue
2025-09-16 17:27:58 +08:00
3287f4c287 Event
Fix:
-  user)/event/(tabs)/contribution: tampilan creator yang di depan
- /Event/ButtonStatusSection: return message

### No Issuue
2025-09-16 11:17:36 +08:00
76fb14ed0c Event
Fix: Intergrasi pada UI
- Beranda, Kontibusi & Riwayat
- Detail beranda, detail kontribusi & detail riwayat
- List partisipan

### No issue
2025-09-15 17:17:50 +08:00
1d2153b253 Event
Fix: Intergrasi tampilan ke API

Package.json
Fix: Pem baharuan SDK53 -> SDK54

### No Issue
2025-09-12 15:57:30 +08:00
005b798688 Event
Fix:
- UI: status, detail status, delete button, detail utama, tampilan utama
- Semua terintergrasi ke API mobile

### No Issue
2025-09-12 14:14:37 +08:00
52 changed files with 3194 additions and 18863 deletions

View File

@@ -36,6 +36,7 @@ export default {
plugins: [ plugins: [
'expo-router', 'expo-router',
'expo-web-browser',
[ [
'expo-splash-screen', 'expo-splash-screen',
{ {

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,43 +1,115 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarCustom,
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import React from "react"; import { useAuth } from "@/hooks/use-auth";
import {
apiEventGetAll
} from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
export default function EventContribution() { export default function EventContribution() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
async function onLoadData() {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: "contribution",
userId: user?.id,
});
console.log("[DATA] ", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
// const responseListParticipants = await apiEventListOfParticipants({
// id: response?.data?.Event?.id,
// });
// console.log(
// "[LIST PARTICIPANTS]",
// JSON.stringify(responseListParticipants.data, null, 2)
// );
// if (responseListParticipants.success) {
// setListParticipants(responseListParticipants.data);
// }
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadList ? (
<BoxWithHeaderSection key={index} href={`/event/${index}/contribution`}> <LoaderCustom />
<StackCustom> ) : _.isEmpty(listData) ? (
<AvatarUsernameAndOtherComponent <TextCustom align="center">Belum ada kontribusi</TextCustom>
avatarHref={`/profile/${index}`} ) : (
rightComponent={ listData.map((item: any, index: number) => (
<TextCustom truncate> <BoxWithHeaderSection
{new Date().toLocaleDateString()} key={index}
</TextCustom> href={`/event/${item?.Event?.id}/contribution`}
} >
/> <StackCustom>
<AvatarUsernameAndOtherComponent
avatar={item?.Event?.Author?.Profile?.imageId}
avatarHref={`/profile/${item?.Event?.Author?.Profile?.id}`}
name={item?.Event?.Author?.username}
rightComponent={
<TextCustom truncate>
{dateTimeView({
date: item?.Event?.tanggal,
withoutTime: true,
})}
</TextCustom>
}
/>
<TextCustom bold align="center" size="xlarge"> <TextCustom bold align="center" size="xlarge" truncate={2}>
Judul Event Disini {item?.Event?.title}
</TextCustom> </TextCustom>
<Spacing height={0} />
<Grid> {/* <Grid>
{Array.from({ length: 4 }).map((_, index2) => ( {item?.Event?.Event_Peserta?.map(
<Grid.Col span={3} key={index2}> (item2: any, index2: number) => (
<AvatarCustom size="sm" href={`/profile/${index2}`} /> <Grid.Col
</Grid.Col> style={{ alignItems: "center" }}
))} span={12 / item?.Event?.Event_Peserta?.length}
</Grid> key={index2}
</StackCustom> >
</BoxWithHeaderSection> <AvatarComp
))} size="base"
href={`/profile/${item2?.User?.Profile?.id}`}
fileId={item2?.User?.Profile?.imageId}
/>
</Grid.Col>
)
)}
</Grid> */}
</StackCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,12 +1,41 @@
import { ButtonCustom, Spacing, TextCustom } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection"; import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { useState } from "react"; import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import _ from "lodash";
import { useEffect, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function EventHistory() { export default function EventHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all"); const [activeCategory, setActiveCategory] = useState<string | null>("all");
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useEffect(() => {
onLoadData({ userId: user?.id });
}, [user?.id, activeCategory]);
async function onLoadData({ userId }: { userId?: string }) {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
userId: userId,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item); setActiveCategory(item);
@@ -52,17 +81,24 @@ export default function EventHistory() {
return ( return (
<ViewWrapper headerComponent={headerComponent} hideFooter> <ViewWrapper headerComponent={headerComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadList ? (
<Event_BoxPublishSection <LoaderCustom />
key={index.toString()} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Belum ada riwayat</TextCustom>
username={`Riwayat ${activeCategory === "main" ? "Saya" : "Semua"}`} ) : (
rightComponentAvatar={ listData.map((item: any, index: number) => (
<TextCustom>{new Date().toLocaleDateString()}</TextCustom> <Event_BoxPublishSection
} key={index.toString()}
href={`/event/${index}/history`} data={item}
/> rightComponentAvatar={
))} <TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
href={`/event/${item.id}/history`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,9 +1,36 @@
import { LoaderCustom, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import FloatingButton from "@/components/Button/FloatingButton"; import FloatingButton from "@/components/Button/FloatingButton";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection"; import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { router } from "expo-router"; import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function EventBeranda() { export default function EventBeranda() {
const [listData, setListData] = useState([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiEventGetAll({category: "beranda"});
// console.log("Response", JSON.stringify(response.data, null, 2));
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
@@ -11,13 +38,24 @@ export default function EventBeranda() {
<FloatingButton onPress={() => router.push("/event/create")} /> <FloatingButton onPress={() => router.push("/event/create")} />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {isLoadData ? (
<Event_BoxPublishSection <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Belum ada event</TextCustom>
href={`/event/${index}/publish`} ) : (
/> listData.map((item: any, index) => (
))} <Event_BoxPublishSection
key={index}
href={`/event/${item.id}/publish`}
data={item}
rightComponentAvatar={
<TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,20 +1,43 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
ViewWrapper,
Spacing, Spacing,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection"; import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish"; import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { apiEventGetOne } from "@/service/api-client/api-event";
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 EventDetailContribution() { export default function EventDetailContribution() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any>();
const [isLoadData, setIsLoadData] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
@@ -32,18 +55,22 @@ export default function EventDetailContribution() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Event_BoxDetailPublishSection /> {isLoadData ? (
<LoaderCustom />
) : (
<Event_BoxDetailPublishSection data={data} />
)}
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })} data={menuDrawerPublishEvent({ id: id as string })}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
</> </>

View File

@@ -80,7 +80,7 @@ export default function EventEdit() {
} }
}; };
const validateDate = () => { const validateDate = async () => {
if ( if (
data?.title === "" || data?.title === "" ||
data?.lokasi === "" || data?.lokasi === "" ||
@@ -92,7 +92,8 @@ export default function EventEdit() {
text1: "Info", text1: "Info",
text2: "Lengkapi semua data", text2: "Lengkapi semua data",
}); });
return;
return false;
} }
const startDate = new Date(selectedDate as any); const startDate = new Date(selectedDate as any);
@@ -104,12 +105,17 @@ export default function EventEdit() {
text1: "Info", text1: "Info",
text2: "Ubah tanggal berakhirnya event", text2: "Ubah tanggal berakhirnya event",
}); });
return;
return false;
} }
return true;
}; };
const handlerSubmit = async () => { const handlerSubmit = async () => {
validateDate(); const isValid = await validateDate();
if (!isValid) return;
try { try {
setIsLoading(true); setIsLoading(true);
const newData = { const newData = {

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
DotButton, DotButton,
DrawerCustom, DrawerCustom,
@@ -9,12 +10,29 @@ import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection"; import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish"; import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { apiEventGetOne } from "@/service/api-client/api-event";
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 EventDetailHistory() { export default function EventDetailHistory() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState();
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
@@ -32,7 +50,7 @@ export default function EventDetailHistory() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Event_BoxDetailPublishSection /> <Event_BoxDetailPublishSection data={data} />
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -43,7 +61,7 @@ export default function EventDetailHistory() {
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })} data={menuDrawerPublishEvent({ id: id as string })}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
</> </>

View File

@@ -1,17 +1,102 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox, BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import {
apiEventGetOne,
apiEventListOfParticipants,
} from "@/service/api-client/api-event";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { View } from "react-native";
export default function EventListOfParticipants() { export default function EventListOfParticipants() {
const { id } = useLocalSearchParams();
const [startDate, setStartDate] = useState();
const [listData, setListData] = useState([]);
const [isLoadData, setIsLoadData] = useState(false);
useEffect(() => {
handlerLoadData();
}, [id]);
const handlerLoadData = () => {
try {
setIsLoadData(true);
onLoadData();
onLoadList();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const onLoadData = async () => {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setStartDate(response.data.tanggal);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadList = async () => {
try {
const response = await apiEventListOfParticipants({ id: id as string });
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadData ? (
<BaseBox key={index} paddingBlock={0}> <LoaderCustom />
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${index}`} /> ) : listData.length === 0 ? (
</BaseBox> <TextCustom align="center">Belum ada peserta</TextCustom>
))} ) : (
listData.map((item: any, index: number) => (
<BaseBox key={index}>
<AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId}
name={item?.User?.username}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
rightComponent={
new Date().getTime() > new Date(startDate as any).getTime() ? (
<View
style={{
justifyContent: "flex-end",
}}
>
<BadgeCustom color={item?.isPresent ? "green" : "red"}>
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
</BadgeCustom>
</View>
) : (
<View
style={{
justifyContent: "flex-end",
}}
>
<BadgeCustom color="gray">-</BadgeCustom>
</View>
)
}
/>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,22 +1,73 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCustom, AlertDefaultSystem,
DotButton, ButtonCustom,
DrawerCustom, DotButton,
MenuDrawerDynamicGrid, DrawerCustom,
Spacing, LoaderCustom,
ViewWrapper MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection"; import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish"; import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiEventCheckParticipants,
import { Alert } from "react-native"; apiEventGetOne,
apiEventJoin,
} from "@/service/api-client/api-event";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function EventDetailPublish() { export default function EventDetailPublish() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(false);
const [isLoadingJoin, setIsLoadingJoin] = useState(false);
const [data, setData] = useState();
const [isParticipant, setIsParticipant] = useState<boolean | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
async function onLoadData() {
try {
setIsLoadingData(true);
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
const responseCheckParticipants = await apiEventCheckParticipants({
id: id as string,
userId: user?.id as string,
});
if (
responseCheckParticipants.success &&
responseCheckParticipants.data
) {
setIsParticipant(true);
}
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingData(false);
}
}
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
@@ -24,15 +75,61 @@ export default function EventDetailPublish() {
setOpenDrawer(false); setOpenDrawer(false);
}; };
const footerButton = ( const handlerJoin = async () => {
<ButtonCustom const userId = user?.id;
backgroundColor="green" if (!userId) {
textColor="white" return Toast.show({
onPress={() => Alert.alert("Anda berhasil join event ini")} type: "error",
> text2: "Anda belum login",
Join });
</ButtonCustom> }
);
try {
setIsLoadingJoin(true);
const response = await apiEventJoin({
id: id as string,
userId: userId as string,
});
if (response.success) {
router.navigate(
`/(application)/(user)/event/${id}/list-of-participants`
);
Toast.show({
type: "success",
text1: "Anda berhasil join",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingJoin(false);
}
};
const footerButton = () => {
return (
<>
<ButtonCustom
disabled={isParticipant as any}
isLoading={isLoadingJoin}
backgroundColor="green"
textColor="white"
onPress={() =>
AlertDefaultSystem({
title: "Join event",
message: "Anda yakin ingin join sebagai peserta event ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => handlerJoin(),
})
}
>
{isParticipant ? "Anda sudah tergabung" : "Join"}
</ButtonCustom>
</>
);
};
return ( return (
<> <>
@@ -44,19 +141,25 @@ export default function EventDetailPublish() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Event_BoxDetailPublishSection footerButton={footerButton} /> {isLoadingData ? (
<Spacing /> <LoaderCustom />
) : (
<Event_BoxDetailPublishSection
data={data}
footerButton={footerButton()}
/>
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })} data={menuDrawerPublishEvent({ id: id as string })}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
</> </>

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

@@ -1,34 +1,83 @@
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BoxWithHeaderSection, BoxWithHeaderSection,
FloatingButton, FloatingButton,
SearchInput, LoaderCustom,
Spacing, SearchInput,
TextCustom, Spacing,
ViewWrapper StackCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetAll } from "@/service/api-client/api-job";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function JobBeranda() { export default function JobBeranda() {
const [listData, setListData] = useState<any[]>([]);
const [isLoadData, setIsLoadData] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData(search);
}, [search])
);
const onLoadData = async (search: string) => {
try {
setIsLoadData(true);
const response = await apiJobGetAll({ search, category: "beranda" });
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handleSearch = (search: string) => {
setSearch(search);
onLoadData(search);
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
floatingButton={ floatingButton={
<FloatingButton onPress={() => router.push("/job/create")} /> <FloatingButton onPress={() => router.push("/job/create")} />
} }
headerComponent={<SearchInput placeholder="Cari pekerjaan" />} headerComponent={
<SearchInput placeholder="Cari pekerjaan" onChangeText={handleSearch} />
}
> >
{jobDataDummy.map((item, index) => ( {isLoadData ? (
<BoxWithHeaderSection key={index} onPress={() => router.push(`/job/${item.id}`)}> <LoaderCustom />
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${item.id}`} /> ) : _.isEmpty(listData) ? (
<Spacing /> <TextCustom align="center">Belum ada lowongan</TextCustom>
<TextCustom truncate={2} align="center" bold size="large"> ) : (
{item.posisi} listData.map((item, index) => (
</TextCustom> <BoxWithHeaderSection
<Spacing /> key={index}
</BoxWithHeaderSection> onPress={() => router.push(`/job/${item.id}`)}
))} >
<StackCustom>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
name={item?.Author?.username}
/>
<TextCustom truncate={2} align="center" bold size="large">
{item?.title || "-"}
</TextCustom>
</StackCustom>
<Spacing />
</BoxWithHeaderSection>
))
)}
<Spacing />
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,17 +1,46 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ScrollableCustom, LoaderCustom,
TextCustom, ScrollableCustom,
ViewWrapper, TextCustom,
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 { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useState } from "react"; import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function JobStatus() { export default function JobStatus() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
const [listData, setListData] = useState<any[]>([]);
const [isLoadList, setIsLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id, activeCategory])
);
const onLoadData = async () => {
try {
setIsLoadList(true);
const response = await apiJobGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -32,19 +61,24 @@ export default function JobStatus() {
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <ViewWrapper headerComponent={scrollComponent} hideFooter>
{jobDataDummy.map((e, i) => ( {isLoadList ? (
<BaseBox <LoaderCustom />
key={i} ) : _.isEmpty(listData) ? (
paddingTop={20} <TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
paddingBottom={20} ) : (
href={`/job/${e.id}/${activeCategory}/detail`} listData.map((e, i) => (
// onPress={() => console.log("pressed")} <BaseBox
> key={i}
<TextCustom align="center" bold truncate size="large"> paddingTop={20}
{e.posisi} {activeCategory?.toUpperCase()} paddingBottom={20}
</TextCustom> href={`/job/${e?.id}/${activeCategory}/detail`}
</BaseBox> >
))} <TextCustom align="center" bold truncate size="large">
{e?.title}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,51 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection"; import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetOne } from "@/service/api-client/api-job";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function JobDetailStatus() { export default function JobDetailStatus() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const jobDetail = jobDataDummy.find((e) => e.id === Number(id)); 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 handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -38,9 +66,23 @@ export default function JobDetailStatus() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Job_BoxDetailSection data={jobDetail} /> {isLoadData ? (
<Job_ButtonStatusSection status={status as string} /> <LoaderCustom />
<Spacing /> ) : (
<>
<StackCustom>
<Job_BoxDetailSection data={data} />
<Job_ButtonStatusSection
id={id as string}
status={status as string}
isLoading={isLoading}
onSetLoading={setIsLoading}
isArchive={true}
/>
</StackCustom>
<Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

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

@@ -1,21 +1,133 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
DummyLandscapeImage,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import {
deleteImageService,
uploadImageService,
} from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function JobEdit() { export default function JobEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>({
title: "",
content: "",
deskripsi: "",
});
const [isLoadData, setIsLoadData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [imageUri, setImageUri] = useState<string | null>(null);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handlerOnUpdate = async () => {
if (!data.title || !data.content || !data.deskripsi) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
let newImageId = "";
if (imageUri) {
const responseUploadImage = await uploadImageService({
imageUri: imageUri,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
newImageId = responseUploadImage.data.id;
}
}
if (data?.imageId) {
const responseDeleteImage = await deleteImageService({
id: data.imageId,
});
if (!responseDeleteImage.success) {
console.log("[ERROR DELETE IMAGE]", responseDeleteImage.message);
}
}
const newData = {
title: data.title,
content: data.content,
deskripsi: data.deskripsi,
imageId: newImageId,
};
const response = await apiJobUpdateData({
id: id as string,
data: newData,
category: "edit",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} onPress={() => handlerOnUpdate()}>
Update
</ButtonCustom>
<Spacing /> <Spacing />
</> </>
); );
@@ -23,45 +135,64 @@ export default function JobEdit() {
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> {isLoadData ? (
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." /> <LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
<LandscapeFrameUploaded /> {imageUri ? (
<ButtonCenteredOnly <LandscapeFrameUploaded image={imageUri as any} />
onPress={() => { ) : (
router.push("/(application)/(image)/take-picture/123"); <BaseBox>
}} <DummyLandscapeImage imageId={data?.imageId} />
icon="upload" </BaseBox>
> )}
Upload
</ButtonCenteredOnly>
<Spacing /> <ButtonCenteredOnly
onPress={() => {
pickImage({
setImageUri,
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<TextInputCustom <Spacing />
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
/>
<TextAreaCustom <TextInputCustom
label="Syarat & Kualifikasi" label="Judul Lowongan"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja" placeholder="Masukan Judul Lowongan Kerja"
required required
showCount value={data.title}
maxLength={1000} onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Lowongan" label="Syarat & Kualifikasi"
placeholder="Masukan Deskripsi Lowongan Kerja" placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required required
showCount showCount
maxLength={1000} maxLength={1000}
/> value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/>
{buttonSubmit()} <TextAreaCustom
</StackCustom> label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
{buttonSubmit()}
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,25 +1,41 @@
import { /* eslint-disable react-hooks/exhaustive-deps */
ButtonCustom, import { ButtonCustom, LoaderCustom, Spacing, StackCustom, ViewWrapper } from "@/components";
Spacing,
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";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetOne } from "@/service/api-client/api-job";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard"; import * as Clipboard from "expo-clipboard";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Alert, Linking } from "react-native"; import { Alert, Linking } from "react-native";
export default function JobDetail() { export default function JobDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const jobDetail = jobDataDummy.find((e) => e.id === Number(id)); useEffect(() => {
onLoadData();
}, [id]);
const OpenLinkButton = () => { const onLoadData = async () => {
const jobUrl = try {
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657"; setIsLoading(true);
const response = await apiJobGetOne({ id: id as string });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const linkUrl = `http://192.168.1.83:3000/job-vacancy/`;
const OpenLinkButton = ({ id }: { id: string }) => {
const jobUrl = `${linkUrl}${id}`;
const openInBrowser = async () => { const openInBrowser = async () => {
const supported = await Linking.canOpenURL(jobUrl); const supported = await Linking.canOpenURL(jobUrl);
@@ -44,9 +60,8 @@ export default function JobDetail() {
); );
}; };
const CopyLinkButton = () => { const CopyLinkButton = ({ id }: { id: string }) => {
const jobUrl = const jobUrl = `${linkUrl}${id}`;
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657";
const copyToClipboard = async () => { const copyToClipboard = async () => {
await Clipboard.setStringAsync(jobUrl); await Clipboard.setStringAsync(jobUrl);
@@ -70,10 +85,18 @@ export default function JobDetail() {
return ( return (
<ViewWrapper> <ViewWrapper>
<Job_BoxDetailSection data={jobDetail}/> {isLoading ? (
<OpenLinkButton /> <LoaderCustom />
<Spacing /> ) : (
<CopyLinkButton /> <>
<Job_BoxDetailSection data={data} />
<StackCustom>
<OpenLinkButton id={id as string} />
<CopyLinkButton id={id as string} />
</StackCustom>
<Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -7,19 +7,99 @@ import {
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiJobCreate } from "@/service/api-client/api-job";
import { uploadImageService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function JobCreate() { export default function JobCreate() {
const nextUrl = "/(application)/(user)/job/(tabs)/status";
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState<string | null>(null);
const [data, setData] = useState({
title: "",
content: "",
deskripsi: "",
authorId: "",
});
const handlerOnSubmit = async () => {
let imageId = "";
const newData = {
title: data.title,
content: data.content,
deskripsi: data.deskripsi,
authorId: user?.id,
imageId: "",
};
if (!data.title || !data.content || !data.deskripsi || !user?.id) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
if (image === null || !image) {
const response = await apiJobCreate(newData);
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Lowongan berhasil dibuat",
});
router.replace(nextUrl);
}
return;
}
const responseUploadImage = await uploadImageService({
imageUri: image,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
imageId = responseUploadImage.data.id;
}
const fixData = {
...newData,
imageId: imageId,
};
const response = await apiJobCreate(fixData);
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Lowongan berhasil dibuat",
});
router.replace(nextUrl);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
onPress={() =>
router.replace("/(application)/(user)/job/(tabs)/status")
}
>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
<Spacing /> <Spacing />
@@ -32,10 +112,19 @@ export default function JobCreate() {
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." /> <InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
<LandscapeFrameUploaded /> {/* <BaseBox>
<Image
source={image ? { uri: image } : DUMMY_IMAGE.dummy_image}
style={{ width: "100%", height: 200 }}
/>
</BaseBox> */}
<LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); // router.push("/(application)/(image)/take-picture/123");
pickImage({
setImageUri: setImage,
});
}} }}
icon="upload" icon="upload"
> >
@@ -48,6 +137,8 @@ export default function JobCreate() {
label="Judul Lowongan" label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja" placeholder="Masukan Judul Lowongan Kerja"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -56,6 +147,8 @@ export default function JobCreate() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -64,6 +157,8 @@ export default function JobCreate() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
{buttonSubmit()} {buttonSubmit()}

View File

@@ -77,18 +77,16 @@ export default function Profile() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Profile", title: `Profile`,
headerLeft: () => <LeftButtonCustom />, headerLeft: () => <LeftButtonCustom />,
headerRight: () => headerRight: () => (
isUserCheck() && ( <ButtonnDot
<TouchableOpacity onPress={openDrawer}> id={id as string}
<Ionicons openDrawer={openDrawer}
name="ellipsis-vertical" isUserCheck={isUserCheck()}
size={20} logout={logout}
color={MainColor.yellow} />
/> ),
</TouchableOpacity>
),
headerStyle: GStyles.headerStyle, headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle, headerTitleStyle: GStyles.headerTitleStyle,
}} }}
@@ -124,3 +122,44 @@ export default function Profile() {
</> </>
); );
} }
const ButtonnDot = ({
id,
openDrawer,
isUserCheck,
logout,
}: {
id: string;
openDrawer: () => void;
isUserCheck: boolean;
logout: () => Promise<void>;
}) => {
const isId = id === undefined || id === null;
console.log("ID CHECK", id);
if (isId) {
console.log("ID UNDEFINED", id);
return (
<>
<TouchableOpacity onPress={logout}>
<Ionicons name="log-out" size={20} color={MainColor.red} />
</TouchableOpacity>
</>
);
}
return (
<>
{isUserCheck && (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
</TouchableOpacity>
)}
</>
);
};

View File

@@ -2,6 +2,7 @@ import {
AvatarComp, AvatarComp,
ClickableCustom, ClickableCustom,
Grid, Grid,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
@@ -19,6 +20,7 @@ import { useEffect, useState } from "react";
export default function UserSearch() { export default function UserSearch() {
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [search, setSearch] = useState<string>(""); const [search, setSearch] = useState<string>("");
const [isLoadList, setIsLoadList] = useState(false);
useEffect(() => { useEffect(() => {
onLoadData(search); onLoadData(search);
@@ -26,11 +28,14 @@ export default function UserSearch() {
const onLoadData = async (search: string) => { const onLoadData = async (search: string) => {
try { try {
setIsLoadList(true);
const response = await apiAllUser({ search: search }); const response = await apiAllUser({ search: search });
console.log("[DATA USER] >", JSON.stringify(response.data, null, 2)); console.log("[DATA USER] >", JSON.stringify(response.data, null, 2));
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.log("Error fetching data", error); console.log("Error fetching data", error);
} finally {
setIsLoadList(false);
} }
}; };
@@ -60,7 +65,9 @@ export default function UserSearch() {
} }
> >
<StackCustom> <StackCustom>
{!_.isEmpty(data) ? ( {isLoadList ? (
<LoaderCustom />
) : !_.isEmpty(data) ? (
data?.map((e, index) => { data?.map((e, index) => {
return ( return (
<ClickableCustom <ClickableCustom

View File

@@ -1,23 +1,63 @@
/* 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 { apiVotingGetAll } from "@/service/api-client/api-voting";
import { router } from "expo-router"; import { router } from "expo-router";
import _ from "lodash";
import { useEffect, 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("");
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 ( 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

@@ -1,20 +1,52 @@
/* 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 dayjs from "dayjs"; import { apiVotingGetByStatus } from "@/service/api-client/api-voting";
import { useState } from "react"; import { dateTimeView } from "@/utils/dateTimeView";
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 || "";
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!,
});
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 +66,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,13 +12,39 @@ 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";
export default function VotingDetailStatus() { export default function VotingDetailStatus() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
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 [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
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 handlePressDraft = (item: IMenuDrawerItem) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -33,7 +60,7 @@ export default function VotingDetailStatus() {
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: () => { onPressRight: () => {
console.log("Hapus"); console.log("Arsip voting");
router.back(); router.back();
}, },
}); });
@@ -57,8 +84,13 @@ export default function VotingDetailStatus() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Voting_BoxDetailSection /> <Voting_BoxDetailSection data={data as any} />
<Voting_ButtonStatusSection status={status as string} /> <Voting_ButtonStatusSection
isLoading={isLoading}
onSetLoading={setIsLoading}
id={id as string}
status={status as string}
/>
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>

View File

@@ -1,29 +1,183 @@
/* 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,
});
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 +188,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,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
@@ -5,20 +6,90 @@ import {
DotButton, DotButton,
DrawerCustom, DrawerCustom,
InformationBox, InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
StackCustom, StackCustom,
TextCustom,
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 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"; 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() { export default function VotingDetail() {
const { id } = useLocalSearchParams(); 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 [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(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) => { const handlePressPublish = (item: IMenuDrawerItem) => {
if (item.path === "") { if (item.path === "") {
AlertDefaultSystem({ AlertDefaultSystem({
@@ -49,15 +120,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"}>
{dateNow < new Date(data?.awalVote) && (
<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." />
)}
<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 ========= */}

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,31 +121,81 @@ 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.Col span={10}> {listVote.map((item, index) => (
<TextInputCustom <TextInputCustom
label="Pilihan" key={index}
placeholder="Masukan Pilihan" label="Pilihan"
required 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"
/> />
</Grid.Col> <ActionIcon
<Grid.Col disabled={listVote.length <= 2}
span={2} onPress={() => {
style={{ alignItems: "center", justifyContent: "center" }} const list = _.clone(listVote);
> list.pop();
<TouchableOpacity onPress={() => console.log("delete")}> setListVote(list);
<Ionicons name="trash" size={24} color={MainColor.red} /> }}
</TouchableOpacity> icon={
</Grid.Col> <Ionicons
</Grid> name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>

585
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@ export default function BaseBox({
return ( return (
<> <>
{onPress || href ? ( {onPress as any || href ? (
<TouchableOpacity <TouchableOpacity
activeOpacity={0.7} activeOpacity={0.7}
onPress={href ? () => router.navigate(href) : onPress} onPress={href ? () => router.navigate(href) : onPress}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_SMALL } from "@/constants/constans-value"; import { TEXT_SIZE_SMALL } from "@/constants/constans-value";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
@@ -5,19 +6,23 @@ import { IMenuDrawerItem } from "../_Interface/types";
import { Href } from "expo-router"; import { Href } from "expo-router";
type IMenuDrawerItemProps = { type IMenuDrawerItemProps = {
icon: React.ReactNode; icon: React.ReactNode;
label: string; label: string;
value?: string; value?: string;
path?: Href | string; path?: Href | string;
color?: string; color?: string;
} };
interface MenuDrawerDynamicGridProps { interface MenuDrawerDynamicGridProps {
data: IMenuDrawerItemProps[]; data: IMenuDrawerItemProps[];
columns?: number; columns?: number;
onPressItem?: (item: IMenuDrawerItemProps) => void; onPressItem?: (item: IMenuDrawerItemProps) => void;
} }
const MenuDrawerDynamicGrid = ({ data, columns = 4, onPressItem }: MenuDrawerDynamicGridProps) => { const MenuDrawerDynamicGrid = ({
data,
columns = 4,
onPressItem,
}: MenuDrawerDynamicGridProps) => {
const numColumns = columns; const numColumns = columns;
return ( return (

View File

@@ -2,7 +2,11 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Image } from "react-native"; import { Image } from "react-native";
import BaseBox from "../Box/BaseBox"; import BaseBox from "../Box/BaseBox";
export default function LandscapeFrameUploaded() { export default function LandscapeFrameUploaded({
image,
}: {
image?: string;
}) {
return ( return (
<BaseBox <BaseBox
style={{ style={{
@@ -11,7 +15,7 @@ export default function LandscapeFrameUploaded() {
}} }}
> >
<Image <Image
source={DUMMY_IMAGE.background} source={image ? { uri: image } : DUMMY_IMAGE.dummy_image}
resizeMode="cover" resizeMode="cover"
style={{ width: "100%", height: "100%", borderRadius: 10 }} style={{ width: "100%", height: "100%", borderRadius: 10 }}
/> />

View File

@@ -1,7 +1,6 @@
import { ImageSourcePropType } from "react-native";
import Divider from "../Divider/Divider"; import Divider from "../Divider/Divider";
import Grid from "../Grid/GridCustom"; import Grid from "../Grid/GridCustom";
import AvatarCustom from "../Image/AvatarCustom"; import AvatarComp from "../Image/AvatarComp";
import TextCustom from "../Text/TextCustom"; import TextCustom from "../Text/TextCustom";
const AvatarUsernameAndOtherComponent = ({ const AvatarUsernameAndOtherComponent = ({
@@ -12,7 +11,7 @@ const AvatarUsernameAndOtherComponent = ({
withBottomLine = false, withBottomLine = false,
}: { }: {
avatarHref?: string; avatarHref?: string;
avatar?: ImageSourcePropType; avatar?: string;
name?: string; name?: string;
rightComponent?: React.ReactNode; rightComponent?: React.ReactNode;
withBottomLine?: boolean; withBottomLine?: boolean;
@@ -21,7 +20,7 @@ const AvatarUsernameAndOtherComponent = ({
<> <>
<Grid containerStyle={{ zIndex: 10 }}> <Grid containerStyle={{ zIndex: 10 }}>
<Grid.Col span={2}> <Grid.Col span={2}>
<AvatarCustom source={avatar} href={avatarHref as any} /> <AvatarComp fileId={avatar} href={avatarHref as any} size="base" />
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
span={rightComponent ? 6 : 10} span={rightComponent ? 6 : 10}

View File

@@ -4,17 +4,33 @@ import { Image } from "expo-image";
import { StyleSheet } from "react-native"; import { StyleSheet } from "react-native";
import ClickableCustom from "../Clickable/ClickableCustom"; import ClickableCustom from "../Clickable/ClickableCustom";
import { router } from "expo-router"; import { router } from "expo-router";
import API_STRORAGE from "@/constants/base-url-api-strorage";
export default function DummyLandscapeImage({height, unClickPath}: {height?: number, unClickPath?: boolean}) { export default function DummyLandscapeImage({
height,
unClickPath,
imageId,
}: {
height?: number;
unClickPath?: boolean;
imageId?: string;
}) {
return ( return (
<ClickableCustom <ClickableCustom
onPress={() => { onPress={() => {
if (!unClickPath) { if (!unClickPath) {
router.push("/(application)/(image)/preview-image/1"); router.push(`/(application)/(image)/preview-image/${imageId}`);
} }
}} }}
> >
<Image source={DUMMY_IMAGE.background} style={[styles.backgroundImage, {height: height || 200}]} /> <Image
source={
imageId
? { uri: API_STRORAGE.GET({ fileId: imageId }) }
: DUMMY_IMAGE.dummy_image
}
style={[styles.backgroundImage, { height: height || 200 }]}
/>
</ClickableCustom> </ClickableCustom>
); );
} }

View File

@@ -11,6 +11,7 @@ interface SearchInputProps {
iconRight?: React.ReactNode; iconRight?: React.ReactNode;
containerStyle?: StyleProp<ViewStyle>; containerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<TextStyle>; style?: StyleProp<TextStyle>;
onChangeText?: (value: string) => void;
} }
export default function SearchInput({ export default function SearchInput({
placeholder, placeholder,
@@ -19,6 +20,7 @@ export default function SearchInput({
iconRight, iconRight,
containerStyle, containerStyle,
style, style,
onChangeText,
...props ...props
}: SearchInputProps) { }: SearchInputProps) {
return ( return (
@@ -30,6 +32,7 @@ export default function SearchInput({
color={MainColor.placeholder} color={MainColor.placeholder}
/> />
} }
onChangeText={onChangeText}
placeholder={placeholder} placeholder={placeholder}
borderRadius={50} borderRadius={50}
containerStyle={[containerStyle, { marginBottom: 0 }]} containerStyle={[containerStyle, { marginBottom: 0 }]}

18107
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,65 +11,69 @@
"lint": "expo lint" "lint": "expo lint"
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^14.1.0", "@expo/vector-icons": "^15.0.2",
"@react-native-async-storage/async-storage": "2.1.2", "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/datetimepicker": "8.4.1", "@react-native-community/datetimepicker": "8.4.4",
"@react-navigation/bottom-tabs": "^7.4.2", "@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/drawer": "^7.5.2", "@react-navigation/drawer": "^7.3.9",
"@react-navigation/elements": "^2.3.8", "@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6", "@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21", "@react-navigation/native-stack": "^7.3.10",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.20",
"@types/react-native-vector-icons": "^6.4.18", "@types/react-native-vector-icons": "^6.4.18",
"axios": "^1.11.0", "axios": "^1.11.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"expo": "53.0.17", "expo": "^54.0.0",
"expo-blur": "~14.1.5", "expo-blur": "~15.0.7",
"expo-camera": "~16.1.10", "expo-camera": "~17.0.7",
"expo-clipboard": "~7.1.5", "expo-clipboard": "~8.0.7",
"expo-constants": "~17.1.7", "expo-constants": "~18.0.8",
"expo-dev-client": "~5.2.4", "expo-dev-client": "~6.0.12",
"expo-document-picker": "~13.1.6", "expo-document-picker": "~14.0.7",
"expo-file-system": "~18.1.11", "expo-file-system": "~19.0.12",
"expo-font": "~13.3.2", "expo-font": "~14.0.8",
"expo-haptics": "~14.1.4", "expo-haptics": "~15.0.7",
"expo-image": "~2.3.2", "expo-image": "~3.0.8",
"expo-image-picker": "~16.1.4", "expo-image-picker": "~17.0.8",
"expo-linking": "~7.1.7", "expo-linking": "~8.0.8",
"expo-router": "~5.1.3", "expo-router": "~6.0.1",
"expo-splash-screen": "~0.30.10", "expo-splash-screen": "~31.0.9",
"expo-status-bar": "~2.2.3", "expo-status-bar": "~3.0.8",
"expo-symbols": "~0.4.5", "expo-symbols": "~1.0.7",
"expo-system-ui": "~5.0.10", "expo-system-ui": "~6.0.7",
"expo-web-browser": "~14.2.0", "expo-web-browser": "~15.0.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "19.0.0", "react": "19.1.0",
"react-dom": "19.0.0", "react-dom": "19.1.0",
"react-native": "0.79.5", "react-native": "0.81.4",
"react-native-dotenv": "^3.4.11", "react-native-dotenv": "^3.4.11",
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.28.0",
"react-native-international-phone-number": "^0.9.3", "react-native-international-phone-number": "^0.9.3",
"react-native-maps": "1.20.1", "react-native-maps": "1.20.1",
"react-native-otp-entry": "^1.8.5", "react-native-otp-entry": "^1.8.5",
"react-native-pager-view": "6.7.1", "react-native-pager-view": "6.9.1",
"react-native-paper": "^5.14.5", "react-native-paper": "^5.14.5",
"react-native-qrcode-svg": "^6.3.15", "react-native-qrcode-svg": "^6.3.15",
"react-native-reanimated": "~3.17.4", "react-native-reanimated": "~4.1.0",
"react-native-safe-area-context": "5.4.0", "react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.11.1", "react-native-screens": "~4.16.0",
"react-native-svg": "15.11.2", "react-native-svg": "15.12.1",
"react-native-toast-message": "^2.3.3", "react-native-toast-message": "^2.3.3",
"react-native-vector-icons": "^10.2.0", "react-native-vector-icons": "^10.2.0",
"react-native-web": "~0.20.0", "react-native-web": "^0.21.0",
"react-native-webview": "13.13.5" "react-native-webview": "13.15.0",
"react-native-worklets": "^0.5.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@types/react": "~19.0.10", "@types/react": "~19.1.10",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~9.2.0", "eslint-config-expo": "~10.0.0",
"expo-module-scripts": "^4.1.10", "expo-module-scripts": "^4.1.10",
"typescript": "~5.8.3" "typescript": "~5.9.2"
}, },
"private": true "private": true,
"engines": {
"bun": ">=1.0.0"
}
} }

View File

@@ -98,7 +98,7 @@ export default function LoginView() {
} }
if (token && !isAdmin) { if (token && !isAdmin) {
return <Redirect href={"/(application)/(user)/event/(tabs)"} />; return <Redirect href={"/(application)/(user)/home"} />;
} }
if (token && isAdmin) { if (token && isAdmin) {

View File

@@ -1,21 +1,49 @@
import { import { AvatarUsernameAndOtherComponent, BoxWithHeaderSection, Grid, StackCustom, TextCustom } from "@/components";
BaseBox, import { dateTimeView } from "@/utils/dateTimeView";
Grid,
StackCustom,
TextCustom
} from "@/components";
export default function Event_BoxDetailPublishSection({ export default function Event_BoxDetailPublishSection({
data,
footerButton, footerButton,
}: { }: {
data: any;
footerButton?: React.ReactNode; footerButton?: React.ReactNode;
}) { }) {
const listData = [
{
title: "Lokasi",
value: data?.lokasi || "-",
},
{
title: "Tipe Acara",
value: data?.EventMaster_TipeAcara?.name || "-",
},
{
title: "Tanggal Mulai",
value: dateTimeView({ date: data?.tanggal }) || "-",
},
{
title: "Tanggal Berakhir",
value: dateTimeView({ date: data?.tanggalSelesai }) || "-",
},
{
title: "Deskripsi",
value: data?.deskripsi || "-",
},
];
return ( return (
<> <>
<BaseBox> <BoxWithHeaderSection>
<StackCustom> <StackCustom>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${data?.Author?.Profile?.id}`}
name={data?.Author?.username || "-"}
avatar={data?.Author?.Profile?.imageId || ""}
/>
<TextCustom bold align="center" size="xlarge"> <TextCustom bold align="center" size="xlarge">
Judul event publish {data?.title || "-"}
</TextCustom> </TextCustom>
{listData.map((item, index) => ( {listData.map((item, index) => (
<Grid key={index}> <Grid key={index}>
@@ -28,34 +56,9 @@ export default function Event_BoxDetailPublishSection({
</Grid> </Grid>
))} ))}
</StackCustom> </StackCustom>
</BaseBox> </BoxWithHeaderSection>
{footerButton} {footerButton}
</> </>
); );
} }
const listData = [
{
title: "Lokasi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
},
{
title: "Tipe Acara",
value: "Workshop",
},
{
title: "Tanggal Mulai",
value: "Senin, 18 Juli 2025, 10:00 WIB",
},
{
title: "Tanggal Berakhir",
value: "Selasa, 19 Juli 2025, 12:00 WIB",
},
{
title: "Deskripsi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
},
];

View File

@@ -7,21 +7,15 @@ import {
import { Href } from "expo-router"; import { Href } from "expo-router";
export default function Event_BoxPublishSection({ export default function Event_BoxPublishSection({
id,
title,
username,
description,
href, href,
data,
// Avatar // Avatar
sourceAvatar, sourceAvatar,
rightComponentAvatar, rightComponentAvatar,
}: { }: {
id: string;
title?: string;
username?: string;
description?: string;
href: Href; href: Href;
data: any;
// Avatar // Avatar
sourceAvatar?: string; sourceAvatar?: string;
@@ -32,18 +26,15 @@ export default function Event_BoxPublishSection({
<BoxWithHeaderSection href={href}> <BoxWithHeaderSection href={href}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatarHref={`/profile/${id}`} avatarHref={`/profile/${data?.Author?.Profile?.id}`}
name={username || "Lorem ipsum dolor sit"} name={data?.Author?.username || "-"}
rightComponent={rightComponentAvatar} rightComponent={rightComponentAvatar}
avatar={sourceAvatar as any} avatar={data?.Author?.Profile?.imageId || ""}
/> />
<TextCustom truncate bold> <TextCustom truncate bold>
{title || "Lorem ipsum dolor sit"} {data?.title || "-"}
</TextCustom>
<TextCustom truncate={2}>
{description ||
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro sed doloremque tempora soluta. Dolorem ex quidem ipsum tempora, ipsa, obcaecati quia suscipit numquam, voluptates commodi porro impedit natus quos doloremque!"}
</TextCustom> </TextCustom>
<TextCustom truncate={2}>{data?.deskripsi || "-"}</TextCustom>
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
</> </>

View File

@@ -1,5 +1,8 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { apiEventUpdateStatus } from "@/service/api-client/api-event"; import {
apiEventDelete,
apiEventUpdateStatus,
} from "@/service/api-client/api-event";
import { router } from "expo-router"; import { router } from "expo-router";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -118,9 +121,26 @@ export default function Event_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(); const response = await apiEventDelete({ id: id });
if (response.success) {
Toast.show({
type: "success",
text1: "Data dihapus",
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
}
}, },
}); });
}; };

View File

@@ -1,28 +1,34 @@
import { BaseBox, StackCustom, DummyLandscapeImage, TextCustom } from "@/components"; import {
BaseBox,
StackCustom,
DummyLandscapeImage,
TextCustom,
} from "@/components";
export default function Job_BoxDetailSection({data}: {data: any}) { export default function Job_BoxDetailSection({ data }: { data: any }) {
return ( return (
<> <>
<BaseBox> <BaseBox>
<StackCustom gap={"lg"}> <StackCustom gap={"lg"}>
<DummyLandscapeImage /> {data && data.imageId && (
<DummyLandscapeImage imageId={data?.imageId} />
)}
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
{data?.posisi} {data?.title || "-"}
</TextCustom> </TextCustom>
<StackCustom gap={"sm"}> <StackCustom gap={"sm"}>
<TextCustom bold>Syarat & Ketentuan :</TextCustom> <TextCustom bold>Syarat & Ketentuan :</TextCustom>
<TextCustom>{data?.syaratKetentuan}</TextCustom> <TextCustom>{data?.content || "-"}</TextCustom>
</StackCustom>
<StackCustom gap={"sm"}>
<TextCustom bold>Deskripsi :</TextCustom>
<TextCustom>{data?.deskripsi}</TextCustom>
</StackCustom>
</StackCustom> </StackCustom>
</BaseBox>
</> <StackCustom gap={"sm"}>
); <TextCustom bold>Deskripsi :</TextCustom>
<TextCustom>{data?.deskripsi || "-"}</TextCustom>
</StackCustom>
</StackCustom>
</BaseBox>
</>
);
} }

View File

@@ -1,11 +1,24 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import {
apiJobDelete,
apiJobUpdateData,
apiJobUpdateStatus,
} from "@/service/api-client/api-job";
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 Job_ButtonStatusSection({ export default function Job_ButtonStatusSection({
id,
status, status,
isLoading,
onSetLoading,
isArchive,
}: { }: {
id: string;
status: string; status: string;
isLoading: boolean;
onSetLoading: (value: boolean) => void;
isArchive?: boolean;
}) { }) {
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
@@ -13,9 +26,33 @@ export default function Job_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"); try {
router.back(); onSetLoading(true);
const response = await apiJobUpdateStatus({
id: id,
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);
}
}, },
}); });
}; };
@@ -26,9 +63,33 @@ export default function Job_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 apiJobUpdateStatus({
id: id,
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);
}
}, },
}); });
}; };
@@ -39,9 +100,33 @@ export default function Job_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 apiJobUpdateStatus({
id: id,
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);
}
}, },
}); });
}; };
@@ -52,9 +137,71 @@ export default function Job_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 apiJobDelete({
id: id,
});
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);
}
},
});
};
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);
}
}, },
}); });
}; };
@@ -63,6 +210,7 @@ export default function Job_ButtonStatusSection({
return ( return (
<> <>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
backgroundColor="red" backgroundColor="red"
textColor="white" textColor="white"
onPress={handleOpenDeleteAlert} onPress={handleOpenDeleteAlert}
@@ -78,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
@@ -90,7 +238,7 @@ export default function Job_ButtonStatusSection({
case "review": case "review":
return ( return (
<ButtonCustom onPress={handleBatalkanReview}> <ButtonCustom isLoading={isLoading} onPress={handleBatalkanReview}>
Batalkan Review Batalkan Review
</ButtonCustom> </ButtonCustom>
); );
@@ -99,15 +247,14 @@ export default function Job_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={{ paddingLeft: 10 }}>
<View /> {DeleteButton()}
</Grid.Col> </Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid> </Grid>
</> </>
); );
@@ -116,15 +263,15 @@ export default function Job_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}>
<View /> <Grid.Col span={6} style={{ paddingRight: 10 }}>
{DeleteButton()}
</Grid.Col> </Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid> </Grid>
</> </>
); );

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,45 +1,95 @@
import { import {
BadgeCustom,
BoxWithHeaderSection, BoxWithHeaderSection,
ButtonCustom, ButtonCustom,
CenterCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom TextCustom,
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
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";
import { apiVotingVote } from "@/service/api-client/api-voting";
import { useAuth } from "@/hooks/use-auth";
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,
});
console.log("[RES VOTE]", response);
} 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 label={item?.value} value={item?.id} />
</View>
))}
</RadioGroup>
</StackCustom>
<ButtonCustom onPress={() => console.log("vote")}>Vote</ButtonCustom> <ButtonCustom disabled={value === ""} onPress={handlerSubmitVote}>
Vote
</ButtonCustom>
</>
)}
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
</> </>

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

@@ -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,11 +1,21 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
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,
status, status,
isLoading,
onSetLoading,
}: { }: {
id: string;
status: string; status: string;
isLoading: boolean;
onSetLoading: (value: boolean) => void;
}) { }) {
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
@@ -13,9 +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: () => { 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);
}
}, },
}); });
}; };
@@ -26,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);
}
}, },
}); });
}; };
@@ -39,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);
}
}, },
}); });
}; };
@@ -52,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);
}
}, },
}); });
}; };
@@ -63,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}
@@ -79,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>
); );
@@ -88,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>
</> </>
); );
@@ -105,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,21 +1,15 @@
import { BadgeCustom, TextCustom } from "@/components"; import { BadgeCustom, StackCustom, TextCustom } from "@/components";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs"; import { dateTimeView } from "@/utils/dateTimeView";
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 +17,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

@@ -44,15 +44,19 @@ export async function apiEventUpdateStatus({
}) { }) {
try { try {
const response = await apiConfig.put(`/mobile/event/${id}/${status}`); const response = await apiConfig.put(`/mobile/event/${id}/${status}`);
console.log("[SUCCESS]", response.data);
return response.data; return response.data;
} catch (error) { } catch (error) {
// console.log("[ERROR FECTH]", error);
throw error; throw error;
} }
} }
export async function apiEventUpdateData({ id, data }: { id: string; data: any }) { export async function apiEventUpdateData({
id,
data,
}: {
id: string;
data: any;
}) {
try { try {
const response = await apiConfig.put(`/mobile/event/${id}`, { const response = await apiConfig.put(`/mobile/event/${id}`, {
data: data, data: data,
@@ -62,3 +66,75 @@ export async function apiEventUpdateData({ id, data }: { id: string; data: any }
throw error; throw error;
} }
} }
export async function apiEventDelete({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/event/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiEventGetAll({
category,
userId,
}: {
category?: "beranda" | "contribution" | "all-history" | "my-history";
userId?: string;
}) {
try {
const categoryEvent = category ? `?category=${category}` : "";
const userIdCreator = userId ? `&userId=${userId}` : "";
const response = await apiConfig.get(
`/mobile/event${categoryEvent}${userIdCreator}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiEventJoin({
id,
userId,
}: {
id: string;
userId?: string;
}) {
try {
const response = await apiConfig.post(`/mobile/event/${id}/participants`, {
userId: userId,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiEventListOfParticipants({ id }: { id?: string }) {
try {
const response = await apiConfig.get(`/mobile/event/${id}/participants`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiEventCheckParticipants({
id,
userId,
}: {
id: string;
userId?: string;
}) {
try {
const selectUserId = userId ? `?userId=${userId}` : "";
const response = await apiConfig.get(
`/mobile/event/${id}/check-participants${selectUserId}`
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,104 @@
import { apiConfig } from "../api-config";
export async function apiJobCreate(data: any) {
try {
const response = await apiConfig.post(`/mobile/job`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiJobGetByStatus({
authorId,
status,
}: {
authorId: string;
status: string;
}) {
try {
const response = await apiConfig.get(`/mobile/job/${authorId}/${status}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiJobGetOne({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/job/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiJobUpdateStatus({
id,
status,
}: {
id: string;
status: "draft" | "review" | "publish" | "reject";
}) {
try {
const response = await apiConfig.put(`/mobile/job/${id}/${status}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiJobDelete({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/job/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiJobGetAll({
search,
category,
authorId,
}: {
search?: string;
category: "archive" | "beranda";
authorId?: string;
}) {
try {
let categoryText = category ? `?category=${category}` : "";
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;
} catch (error) {
throw error;
}
}
export async function apiJobUpdateData({
id,
data,
category,
}: {
id: string;
data: any;
category: "edit" | "archive";
}) {
try {
const categoryJob = category ? `?category=${category}` : "";
const response = await apiConfig.put(`/mobile/job/${id}${categoryJob}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,116 @@
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;
}
}
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;
}
}

View File

@@ -1,6 +1,6 @@
import { API_BASE_URL } from "@/service/api-config"; import { API_BASE_URL } from "@/service/api-config";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import { apiFileUpload } from "./api-client/api-file"; import { apiFileDelete, apiFileUpload } from "./api-client/api-file";
export async function uploadImageService({ export async function uploadImageService({
dirId, dirId,
@@ -49,3 +49,18 @@ export async function uploadImageService({
throw error; throw error;
} }
} }
export async function deleteImageService({ id }: { id: string }) {
const token = await AsyncStorage.getItem("authToken");
try {
const response = await apiFileDelete({
id: id,
token: token as string,
});
return response;
} catch (error) {
throw error;
}
}

View File

@@ -7,6 +7,8 @@ export const dateTimeView = ({
date: any; date: any;
withoutTime?: boolean; withoutTime?: boolean;
}) => { }) => {
const newDate = dayjs(date).format(`DD-MM-YYYY, ${withoutTime ? "" : "HH:mm"}`); const newDate = dayjs(date).format(
`DD-MM-YYYY ${withoutTime ? "" : ", HH:mm"}`
);
return newDate; return newDate;
}; };